Programmatically Update Third-Party Modules

Yeah, not sure where the docs went, but the (easiest) part is still published in at least one SDK example.

Basically, duplicate the below in a new Maven subproject or whatever Maven calls it:

Then (I'll reference IntelliJ here, other IDEs you're on your own):

  1. Create a new "Run Configuration", using an appropriate Java version.
  2. Use your newly generated subproject as the classpath (so your IDE knows where to load up dependencies)
  3. Use com.inductiveautomation.ignition.designer.DesignerStartupHook as your main class
  4. You'll need a bunch of VM flags, none of which I'm going to go into detail to explain:
--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.desktop/java.awt=ALL-UNNAMED
--add-opens=java.desktop/java.awt.event=ALL-UNNAMED
--add-opens=java.desktop/javax.swing=ALL-UNNAMED
--add-opens=java.desktop/javax.swing.tree=ALL-UNNAMED
--add-opens=java.desktop/javax.swing.plaf=ALL-UNNAMED
--add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED
--add-opens=java.desktop/javax.swing.plaf.synth=ALL-UNNAMED
--add-exports=java.base/sun.security.action=ALL-UNNAMED
--add-exports=java.desktop/com.apple.eawt=ALL-UNNAMED
--add-exports=java.desktop/com.sun.awt=ALL-UNNAMED
--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED
--add-exports=java.desktop/sun.awt=ALL-UNNAMED
--add-exports=java.desktop/sun.awt.image=ALL-UNNAMED
--add-exports=java.desktop/sun.awt.shell=ALL-UNNAMED
--add-exports=java.desktop/sun.awt.windows=ALL-UNNAMED
--add-exports=java.desktop/sun.swing=ALL-UNNAMED
--add-exports=java.desktop/sun.swing.plaf.synth=ALL-UNNAMED
--add-exports=java.desktop/sun.swing.table=ALL-UNNAMED
--add-exports=java.desktop/sun.print=ALL-UNNAMED
-Dscadarail.version=dev
-Dsr.gateway=localhost:8088 

That should be about bare minimum to get you a succesful designer launch, and theoretically if you set your local designer-launcher POM up correctly it's going to load your in-dev module code, not what the gateway delivers. Make sure that your gateway is the same version as whatever your Maven setup is declaring as a dependency, or weird side effects will result.

Also, no guarantees this didn't work on my machine because of some weird locally cached Maven artifacts or something - but that's the theory and it works on my machine :person_shrugging:

1 Like

I worked through this earlier and got stuck, I ended up with something close.

I ended up with these flags:

--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.desktop/java.awt=ALL-UNNAMED
--add-opens=java.desktop/java.awt.event=ALL-UNNAMED
--add-opens=java.desktop/javax.swing=ALL-UNNAMED
--add-opens=java.desktop/javax.swing.tree=ALL-UNNAMED
--add-opens=java.desktop/javax.swing.plaf=ALL-UNNAMED
--add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED
--add-opens=java.desktop/javax.swing.plaf.synth=ALL-UNNAMED
--add-exports=java.base/sun.security.action=ALL-UNNAMED
--add-exports=java.desktop/com.apple.eawt=ALL-UNNAMED
--add-exports=java.desktop/com.sun.awt=ALL-UNNAMED
--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED
--add-exports=java.desktop/sun.awt=ALL-UNNAMED
--add-exports=java.desktop/sun.awt.image=ALL-UNNAMED
--add-exports=java.desktop/sun.awt.shell=ALL-UNNAMED
--add-exports=java.desktop/sun.awt.windows=ALL-UNNAMED
--add-exports=java.desktop/sun.swing=ALL-UNNAMED
--add-exports=java.desktop/sun.swing.plaf.synth=ALL-UNNAMED
--add-exports=java.desktop/sun.swing.table=ALL-UNNAMED
--add-exports=java.desktop/sun.print=ALL-UNNAMED
-Djavaws.ignition.debug=true
-Djavaws.ignition.loglevel=INFO
-Dsr.gateway=localhost:8088
-Dignition.version=dev

ignition.version vs scadarail.version being the only real difference it seems.

Didn't get it working though - fails to load projects:

16:51:00.579 [Designer-Startup] ERROR com.inductiveautomation.ignition.client.util.gui.ErrorUtil -- Error loading project.
org.python.core.PyException: LookupError: no codec search functions registered: can't find encoding 'UTF-8'
	at org.python.core.codecs$CodecState.lookup(codecs.java:1701)
	at org.python.core.codecs$CodecState.setDefaultEncoding(codecs.java:1675)
	at org.python.core.codecs.setDefaultEncoding(codecs.java:42)
	at org.python.core.PySystemState.setdefaultencoding(PySystemState.java:544)
	at com.inductiveautomation.ignition.common.script.ScriptManager.createUtf8PySystemState(ScriptManager.java:261)
	at com.inductiveautomation.ignition.common.script.ScriptManager.<init>(ScriptManager.java:135)
	at com.inductiveautomation.ignition.common.script.ScriptManager.<init>(ScriptManager.java:114)
	at com.inductiveautomation.ignition.designer.DesignerContextImpl.<init>(DesignerContextImpl.java:164)
	at com.inductiveautomation.ignition.designer.IgnitionDesigner.loadProject(IgnitionDesigner.java:971)
	at com.inductiveautomation.ignition.designer.IgnitionDesigner$StartupProjectDialogHandler.lambda$new$2(IgnitionDesigner.java:2073)
	at java.base/java.lang.Thread.run(Thread.java:840)

not sure if it's just a problem with my Docker setup or what. I gave up.

Attached my run file if somebody wants to play with it.
Ignition Designer.run.xml (1.8 KB)

2 Likes

Ah, that's probably what
-DlibPath=/FullyQualifiedPathToIgnitionInstallDirectory/user-lib/pylib
is for in my saved run configurations. I stopped at a splash screen, didn't actually try to open a project.

yeah, that was my thought too, and then I didn't want to figure out how to point at a pylib folder and explain it :upside_down_face:

1 Like

Someone enterprising should be able to connect the dots. Either way, I'll talk to the docs team and see if we can't reconstruct that old page somewhere, at least for the designer; it's much better user experience.

What if I also need to load first-party modules?

Gradle config:

plugins {
    application
}

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

dependencies {
    implementation("com.inductiveautomation.ignitionsdk:designer-api:8.1.47")
}

application {
    mainClass.set("com.inductiveautomation.ignition.designer.DesignerStartupHook")
}

tasks.withType<JavaExec> {
    jvmArgs = listOf(
        "--add-opens=java.base/java.lang=ALL-UNNAMED",
        "--add-opens=java.base/java.util=ALL-UNNAMED",
        "--add-opens=java.desktop/java.awt=ALL-UNNAMED",
        "--add-opens=java.desktop/java.awt.event=ALL-UNNAMED",
        "--add-opens=java.desktop/javax.swing=ALL-UNNAMED",
        "--add-opens=java.desktop/javax.swing.tree=ALL-UNNAMED",
        "--add-opens=java.desktop/javax.swing.plaf=ALL-UNNAMED",
        "--add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED",
        "--add-opens=java.desktop/javax.swing.plaf.synth=ALL-UNNAMED",
        "--add-exports=java.base/sun.security.action=ALL-UNNAMED",
        "--add-exports=java.desktop/com.apple.eawt=ALL-UNNAMED",
        "--add-exports=java.desktop/com.sun.awt=ALL-UNNAMED",
        "--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED",
        "--add-exports=java.desktop/sun.awt=ALL-UNNAMED",
        "--add-exports=java.desktop/sun.awt.image=ALL-UNNAMED",
        "--add-exports=java.desktop/sun.awt.shell=ALL-UNNAMED",
        "--add-exports=java.desktop/sun.awt.windows=ALL-UNNAMED",
        "--add-exports=java.desktop/sun.swing=ALL-UNNAMED",
        "--add-exports=java.desktop/sun.swing.plaf.synth=ALL-UNNAMED",
        "--add-exports=java.desktop/sun.swing.table=ALL-UNNAMED",
        "--add-exports=java.desktop/sun.print=ALL-UNNAMED",
        "-Djavaws.ignition.debug=true",
        "-Djavaws.ignition.loglevel=INFO",
        "-Dsr.gateway=localhost:8088",
        "-Dignition.version=dev",
        "-Dautologin.username=admin",
        "-Dautologin.password=password",
        "-DlibPath=C:\\Program Files\\Inductive Automation\\Ignition\\user-lib\\pylib"
    )
}

Those should be available by actually being installed in the Ignition Gateway you're launching against.

I'm not really sure what you've got going on with that Gradle file. We're talking about creating an IntelliJ run configuration and launching from that.

The Maven module or Gradle module would only be used as a target for the run config for its class path - to get the designer-api and vision-designer-api artifacts from the SDK and any of the dependencies of your local code you actually want to be running.

1 Like

Gotcha, I’m mixing and matching concepts here.

I’ll revisit this later when I’m not rushing.

The config does start a “working” designer, it just doesn’t load any modules from the gateway :man_shrugging:t2:.

Hmm, yeah, once I fix my pylib problem I think I get a similar kind of error even via the launch configs.

Only Vision works, and I wonder if that's because its DesignerModuleHook is on the class path via these two SDK artifacts.

I know this dependency is relevant, but I for some reason my IntelliJ cannot resolve it:

<dependency>
            <groupId>com.inductiveautomation.ignition.examples</groupId>
            <artifactId>ce-designer</artifactId>
            <version>1.8.0</version>
        </dependency>

I tried running the configuration without it and including everything else, but I get this error:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.CharSequence.length()" because "this.text" is null
	at java.base/java.util.regex.Matcher.getTextLength(Matcher.java:1806)
	at java.base/java.util.regex.Matcher.reset(Matcher.java:459)
	at java.base/java.util.regex.Matcher.<init>(Matcher.java:255)
	at java.base/java.util.regex.Pattern.matcher(Pattern.java:1180)
	at com.inductiveautomation.ignition.client.launch.GatewayAddress.parse(GatewayAddress.java:57)
	at com.inductiveautomation.ignition.designer.DesignerStartupHook.main(DesignerStartupHook.java:77)

That's not related to the error.

ce-designer is the artifact ID of that example module - client example - designer scope.

The error you're getting is because we can't parse a gateway address:
at com.inductiveautomation.ignition.client.launch.GatewayAddress.parse(GatewayAddress.java:57)
Which most likely means you didn't provide one. Notice the long list of VM options (not program arguments!) - at the end is -Dsr.gateway=localhost:8088; make sure you're both passing that parameter and giving it a real address.

So am I ok to remove the errored dependency if I am not using anything with the example designer module?

I have, honestly, no idea what you're trying to do. We're way down in the weeds here. Kevin and I went on a tangent where we very vaguely described the process someone else could follow to get an IDE launcher designer as a third party. If you're just trying to do that, as is, sure, delete the dependency, I guess? But it seems much more likely that you're trying to use that capability while also loading your custom module code, in which case you should copy the entire pom.xml and subproject structure I linked above, but bring it in to your own build and make the necessary changes to bring in your artifact.

I don't have a concrete set of steps to guide you through this because I've never had to do it, and the manual page that, while heavily outdated, at least walked through the process...no longer exists.

I am trying to launch the Designer and load my custom module code on the fly so I don't have to manually install the module on the Gateway. I copied the pom.xml (besides the errant dependency, which seems like it doesn't apply to this use case?) and the submodule structure into my Maven module. I also added all the VM arguments (with corrected port) into a run configuration in IntelliJ and ran it. This gets me to the login screen of the Designer, but no further, citing a 'version mismatch' error as shown below:

com.inductiveautomation.ignition.client.gateway_interface.GatewayException: Version mismatch
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.newGatewayException(GatewayInterface.java:341)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.sendMessage(GatewayInterface.java:315)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.sendMessage(GatewayInterface.java:268)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.invoke(GatewayInterface.java:905)
	at com.inductiveautomation.ignition.designer.DesignerGatewayConnection.loginDesigner(DesignerGatewayConnection.java:119)
	at com.inductiveautomation.ignition.designer.DesignerGatewayConnection.doLogin(DesignerGatewayConnection.java:40)
	at com.inductiveautomation.ignition.client.gateway_interface.AbstractGatewayConnection.login(AbstractGatewayConnection.java:886)
	at com.inductiveautomation.ignition.designer.DesignerSplash.doLogin(DesignerSplash.java:469)
	at com.inductiveautomation.ignition.designer.DesignerSplash$5.actionPerformed(DesignerSplash.java:458)
	at java.desktop/javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1972)
	at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2314)
	at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:407)
	at java.desktop/javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:262)
	at java.desktop/javax.swing.AbstractButton.doClick(AbstractButton.java:374)
	at java.desktop/javax.swing.plaf.basic.BasicRootPaneUI$Actions.actionPerformed(BasicRootPaneUI.java:259)
	at java.desktop/javax.swing.SwingUtilities.notifyAction(SwingUtilities.java:1810)
	at java.desktop/javax.swing.JComponent.processKeyBinding(JComponent.java:2956)
	at java.desktop/javax.swing.KeyboardManager.fireBinding(KeyboardManager.java:309)
	at java.desktop/javax.swing.KeyboardManager.fireKeyboardAction(KeyboardManager.java:251)
	at java.desktop/javax.swing.JComponent.processKeyBindingsForAllComponents(JComponent.java:3049)
	at java.desktop/javax.swing.JComponent.processKeyBindings(JComponent.java:3041)
	at java.desktop/javax.swing.JComponent.processKeyEvent(JComponent.java:2918)
	at java.desktop/java.awt.Component.processEvent(Component.java:6398)
	at java.desktop/java.awt.Container.processEvent(Container.java:2266)
	at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4996)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4828)
	at java.desktop/java.awt.KeyboardFocusManager.redispatchEvent(KeyboardFocusManager.java:1942)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(DefaultKeyboardFocusManager.java:883)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(DefaultKeyboardFocusManager.java:1146)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:1020)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:848)
	at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4877)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
	at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2780)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4828)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:775)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:98)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:747)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:744)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Ignition v8.0.1 (b2019050718)
Java: Oracle Corporation 22.0.1

The gateway version I have installed is 8.0.17 (I know, don't judge) and the Ignition SDK version is the same. Is there another version of something I should be worried about?

:magnifying_glass_tilted_right:

That said, the point of the -Dscadarail.version=dev VM option in my snippet above is precisely to bypass version checking logic. If you're explicitly setting the version to exactly dev, none of the version verifications are performed, and whatever undefined behavior you hit is on your own head. That you're getting a version mismatch error suggests strongly that you're not setting the appropriate VM options in the appropriate places.

The point of the errant dependency is to tell Maven what the dependency graph of that subproject should be. IntelliJ then uses that information to build its classpath, which is crucial, if you actually want to load your custom module code. You don't need that dependency, but you do need to tell Maven something about the rest of your project, or it won't be loading your code at all.

You should have mentioned this up front. I, at least, could then have completely ignored this topic.

Sorry, no can not do. You've wasted a bunch of my time following this and [expletive]'d me off. IA staff will be more polite, but I don't have to be.

Wow.

I am also attempting to perform the same module-designer hotload in more modern versions (8.1.42) but the most convenient module to try this on is built in version 8.0.17. I disagree that this is a waste of time because the gateway interface and other relevant bits (as far as module development) seem to be relatively unchanged. I apologize for any irritation I have caused. Try to understand that I am also attempting to mitigate my own frustration with module development by seeking answers in this forum.

Is there a way to view where the version mismatch might be happening if I launch in debug mode (from IntelliJ)? I have combed through my code and the VM flags to see if there is any versioning that does not match, and I have been unable to find anything. I have even tried manually setting the -Dscadarail.version=8.0.17

Set the version explicitly to dev in your VM options. You just need the designer you launch out of the IDE to not care about versions. Take a screenshot to show what you have configured in your run configuration; in my first attempt I was accidentally putting things in as runtime arguments, rather than VM options, due to IntelliJ's frankly terrible "new" run configuration UI.