Modules Dependencies

Is it possible to have a module to depend on another module and error if the module(s) that it depends on is missing?

I can do the following specify the module dependency in build.gradle.kts

    moduleDependencies.putAll(mapOf(
         "com.inductiveautomation.perspective" to "GD",
         "module.name" to "GD" )
    )

However, the above module dependency only logs a warning in the logs page but it doesn't error the module from installing, is there something i can add to the module startup script to have stricter dependency?

In your module hook's startup() method, use the saved gateway context (from setup())to retrieve the other module's hook instance. If null, the other module isn't installed.

1 Like

Also, for the record, in 8.3 you'll be able to add a required attribute to the depends declaration in the module.xml, in which case the platform will enforce the requirement for you, instead of requiring a soft runtime check. Default behavior without an explicit opt-in to this will be the same as in 8.1.

2 Likes

I've build module A and module B, and I want to have module B depending on module A, will it be possible to reuse resources, e.g. persistent record, from module A in module B?

Yes, module B's classloader with have access to module A's classes. But make sure nothing in your gateway hook references any of that until after you do the check I recommended.

That means your B hook class must not reference any of module A's classes, even transitively. That means there will have to be a "boundary" class in B that does access A's classes, and is itself loaded indirectly (using a class loader's method to load by string name).

I added this to my startup code in the gateway which checks for the module dependency, works well, thanks for this.

 if (this.context.getModuleManager().getModule("moduleAId") == null) {
            throw new IllegalStateException("Module A is required but not loaded.");
        }  

Now I have a resource (persistent record) in moduleA that I want to access from module B that can be found in the following package module.a.gateway.record.DatabaseRecord

How do I access this from module B, you mentioned having a boundry class and using class loader's method, could you elaborate more on how to achieve this?

Your boundary class (in B) would reference the module A code like any other (where module A code is marked as a compile dependency).

Make an interface definition that includes method declarations for all of the functionality you need to hit from your module hook, and create a field in your hook of the interface type. Use your hook class's classloader to load the boundary class by name and place an instance in the interface field.

The rest of your hook methods would call methods, as needed, on that instance, for module life cycle operations.

1 Like

Your boundary class (in B) would reference the module A code like any other (where module A code is marked as a compile dependency).

I'm assuming what this means is that the module a need to be included as a compile dependency in module b's build.gradle.kts file so I've done the following

dependencies {
    compileOnly("com.inductiveautomation.ignitionsdk:ignition-common:${rootProject.extra["sdk_version"]}")
    compileOnly("com.inductiveautomation.ignitionsdk:gateway-api:${rootProject.extra["sdk_version"]}")
    // add gateway scoped dependencies here
    compileOnly(project(":module-a")) // added this line
}

in my /moduleB/gateway/build.gradle.kts

but i get the following error on build


* Where:
Build file 'C:\Users\GraceGan\Documents\projects\ignition-modules\module-b\gateway\build.gradle.kts' line: 15

* What went wrong:
Project with path ':module-a' could not be found in project ':gateway'.

My monorepo structure is as follows and the build command is executed from folder /module-b

/ignition-modules
     /module-a
     /module-b

I don't use gradle (or maven), so someone else will have help you with that.

1 Like

This suggests module-a and module-b are being treated as separate projects.
To convince Gradle to include one as a dependency of the other, you need to either:

  1. Make a top level "super" Gradle project by injecting a build.gradle.kts at the ignition-modules\ directory and having it reference module-a and module-b's build.gradle.kts files. This effectively gives you a "monorepo" approach.
  2. Explicitly "publish" module-a for consumption into module-b, either to "maven local" (your local computer; complicates CI/CD), a self-hosted Maven repository compatible repository (complicates setup) or a 'real' Maven instance (complicates setup, may cost money for private setup).
1 Like

Embr is a good reference for this. I’m using type-safe project accessors, but the same logic applies.

Edit: Forgot I have this floating around too:

2 Likes

Yeah I would 1000% recommend the first approach if you're going to be making continuous updates to either half of this equation. I didn't see your edit earlier that explicitly mentions a monorepo, so it sounds like you're on the right path already.

I added the following

root build.gradle.kts

plugins {
    id("io.ia.sdk.modl") version("0.4.0")
}


allprojects {
    group = "cloud.thred.mesh"
    version = "0.0.1-SNAPSHOT"
}

and following is my root settings.gradle

pluginManagement {
    repositories {
        gradlePluginPortal()
        maven {
            url = "https://nexus.inductiveautomation.com/repository/public" 
        }
    }
}

rootProject.name = "mesh-ignition-modules"

dependencyResolutionManagement {
    repositories {
        // enable resolving dependencies from the inductive automation artifact repository
        maven {
            url "https://nexus.inductiveautomation.com/repository/public"
        }
        mavenCentral()
    }
}

include(
":",
    ":mesh-core",
    ":mesh-oee",
    ":mesh-data-model"
)

Seems to be able to detect the subprojects

However, adding the following will cause error during the build still

mesh-ignition-modules/mesh-data-model/gateway/build.gradle.kts

plugins {
    `java-library`
}

java {
    toolchain {
        languageVersion.set(org.gradle.jvm.toolchain.JavaLanguageVersion.of(11))
    }
}

dependencies {
    compileOnly("com.inductiveautomation.ignitionsdk:ignition-common:${rootProject.extra["sdk_version"]}")
    compileOnly("com.inductiveautomation.ignitionsdk:gateway-api:${rootProject.extra["sdk_version"]}")
    // add gateway scoped dependencies here
    implementation(project(":mesh-core")) // this line is causing the build to fail
}

error message as follows

* Where:
Build file 'C:\Users\GraceGan\Documents\projects\mesh-ignition-modules\mesh-data-model\gateway\build.gradle.kts' line: 15

* What went wrong:
Project with path ':mesh-core' could not be found in project ':gateway'.

Show the command you’re using to run the build.

gradle clean build at the /mesh-data-model folder level

You need to execute everything at the top of the monorepo.

From the root, try gradle :mesh-data-model:clean :mesh-data-model:build

1 Like

I think it's working now :crossed_fingers:

With this in place do I still need to reference the module A code (from module B) via boundary class and class loader or can I reference it directly (by just importing the package?)

1 Like

If you want to use the implementation from the installed version of Module A, then yes you need to safely-maybe-load at run time.

The simplest option is to move the code to a shared library, included it both modules, and make sure your users upgrade both at the same time.

Edit: For the use case of sharing PersistenceRecords, you absolutely want to share the same implementation across modules.

For PersistentRecord cases, this makes me cringe. Don't do it.

Yeah, great point.