I'm trying to include third-party dependencies in my Ignition module. I attempted this using the perspective-component from the Ignition SDK GitHub by adding the dependencies I need to the build.gradle.kts file in the common scope but it didn't import the dependencies correctly.
Could anyone provide examples or suggest convenient ways to do this? Thanks in advance for your help! @GeraldB
What did you actually try, and what was the actual result?
Note that you need to use our explicit modlApi dependency marker to tell the module plugin to actually include your dependency in the final output module. implementation just tells Gradle to use it for compilation, assuming that you will have it already in your final destination.
Thanks for the answers, loading the dependencies with implementation is exactly what i tried that's why it didn't include them in the .modl file. I now tried to use modlApi like this:
plugins {
`java-library`
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
repositories {
mavenCentral()
maven {
url = uri("https://company/com/maven2")
}
mavenLocal()
flatDir {
dirs("libs")
}
}
dependencies {
// compileOnly is the gradle equivalent to "provided" scope. Here we resolve the dependencies via the
// declarations in the gradle/libs.versions.toml file
compileOnly(libs.ignition.common)
compileOnly(libs.ignition.perspective.common)
compileOnly(libs.google.guava)
compileOnly(libs.ia.gson)
// https://mvnrepository.com/artifact/io.netty/netty-transport-native-epoll
modlApi("io.netty:netty-transport-native-epoll:4.2.0.Final")
// https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common
modlApi("org.apache.hadoop:hadoop-common:3.4.1")
// https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-client
modlApi("org.apache.hadoop:hadoop-client:3.4.1")
// https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-hdfs
modlApi("org.apache.hadoop:hadoop-hdfs:3.4.1")
}
Now it correctly includes the hadoop jar files in the .modl.
when i install the module to my Gateway i get the following error:
ModuleManager 18Apr2025 11:30:35 Error starting module org.fakester.radcomponent
java.lang.NoClassDefFoundError: com/inductiveautomation/perspective/gateway/api/PerspectiveContext
at org.fakester.gateway.RadGatewayHook.startup(RadGatewayHook.java:39)
at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl$LoadedModule.startup(ModuleManagerImpl.java:2446)
at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.startupModule(ModuleManagerImpl.java:1188)
at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl$2.call(ModuleManagerImpl.java:734)
at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.executeModuleOperation(ModuleManagerImpl.java:913)
at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.installModuleInternal(ModuleManagerImpl.java:700)
at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl$InstallCommand.doExecute(ModuleManagerImpl.java:1915)
at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl$AbstractModuleCommand.execute(ModuleManagerImpl.java:1864)
at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl$Receiver.receiveCall(ModuleManagerImpl.java:1820)
at com.inductiveautomation.ignition.gateway.redundancy.QueueableMessageReceiver.receiveCall(QueueableMessageReceiver.java:47)
at com.inductiveautomation.ignition.gateway.redundancy.RedundancyManagerImpl.dispatchMessage(RedundancyManagerImpl.java:1044)
at com.inductiveautomation.ignition.gateway.redundancy.RedundancyManagerImpl$ExecuteTask.run(RedundancyManagerImpl.java:1112)
at com.inductiveautomation.ignition.common.execution.impl.BasicExecutionEngine$ThrowableCatchingRunnable.run(BasicExecutionEngine.java:550)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ClassNotFoundException: com.inductiveautomation.perspective.gateway.api.PerspectiveContext
at java.base/java.net.URLClassLoader.findClass(Unknown Source)
at com.inductiveautomation.ignition.gateway.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:37)
at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
at com.inductiveautomation.ignition.gateway.modules.ModuleClassLoader.loadClass(ModuleClassLoader.java:85)
at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
... 19 common frames omitted
what i don't really understand because i didn't change anything else than the build.gradle.ks in my common scope as shown above
I just checked it and it's there, as i mentioned i didn't change anything besides my build.gradle.kts in the common folder. Here the main build.gradle.kts of the project:
import java.util.concurrent.TimeUnit
plugins {
base
// the ignition module plugin: https://github.com/inductiveautomation/ignition-module-tools
id("io.ia.sdk.modl") version("0.1.1")
id("org.barfuin.gradle.taskinfo") version "1.3.0"
}
allprojects {
version = "1.0.0"
group = "org.fakester"
}
ignitionModule {
// name of the .modl file to build
fileName.set("HadoopDependencies")
// module xml configuration
name.set("HadoopDependencies")
id.set("org.fakester.radcomponent")
moduleVersion.set("${project.version}")
moduleDescription.set("A module that adds Hadoop Dependencies.")
requiredIgnitionVersion.set("8.1.8")
license.set("license.html")
// If we depend on other module being loaded/available, then we specify IDs of the module we depend on,
// and specify the Ignition Scope that applies. "G" for gateway, "D" for designer, "C" for VISION client
// (this module does not run in the scope of a Vision client, so we don't need a "C" entry here)
moduleDependencies.put("com.inductiveautomation.perspective", "DG")
// map of 'Gradle Project Path' to Ignition Scope in which the project is relevant. This is is combined with
// the dependency declarations within the subproject's build.gradle.kts in order to determine which
// dependencies need to be bundled with the module and added to the module.xml.
projectScopes.putAll(
mapOf(
":gateway" to "G",
":web" to "G",
":designer" to "D",
":common" to "GD"
)
)
// 'hook classes' are the things that Ignition loads and runs when your module is installed. This map tells
// Ignition which classes should be loaded in a given scope.
hooks.putAll(
mapOf(
"org.fakester.gateway.RadGatewayHook" to "G",
"org.fakester.designer.RadDesignerHook" to "D"
)
)
skipModlSigning.set(true)
}
val deepClean by tasks.registering {
dependsOn(allprojects.map { "${it.path}:clean" })
description = "Executes clean tasks and remove node plugin caches."
doLast {
delete(file(".gradle"))
}
}
Hi Kevin, a further question appeard. I installed the modl to the Gateway and tried to access my 3rd party dependencies through the scripting console like this:
Modules get loaded in their own isolated ClassLoader, lower in the hierarchy than the platform.
The classes brought in by your module and its dependencies are not available to Gateway-scoped scripting. It only works by chance in Client/Designer scope because there is no ClassLoader hierarchy there - it's entirely flat.
The supported approach is to add scripting functions system.whatever.foo() that in turn use the libraries your module includes.