From fb19ad9740291883c052a764073f6c0201b7b7d3 Mon Sep 17 00:00:00 2001 From: Paul Griffith Date: Wed, 5 Nov 2025 16:11:45 -0800 Subject: [PATCH 01/10] 8.3.0 API updates --- .../client/ClientProjectExtensions.kt | 8 +++---- .../common/UtilitiesExtensions.java | 12 +++++----- .../extensions/common/DatasetExtensions.kt | 20 ++++++++--------- .../extensions/common/ExtensionDocProvider.kt | 16 +++++++++----- .../extensions/common/ProjectExtensions.kt | 5 +++-- .../imdc/extensions/common/TagExtensions.kt | 12 +++++----- .../designer/DesignerProjectExtensions.kt | 8 +++---- .../gateway/GatewayProjectExtensions.kt | 22 +++++++++++-------- .../imdc/extensions/gateway/HistoryServlet.kt | 6 ++--- gradle/libs.versions.toml | 14 ++++++------ 10 files changed, 67 insertions(+), 56 deletions(-) diff --git a/client/src/main/kotlin/org/imdc/extensions/client/ClientProjectExtensions.kt b/client/src/main/kotlin/org/imdc/extensions/client/ClientProjectExtensions.kt index 828b638..f85d88e 100644 --- a/client/src/main/kotlin/org/imdc/extensions/client/ClientProjectExtensions.kt +++ b/client/src/main/kotlin/org/imdc/extensions/client/ClientProjectExtensions.kt @@ -1,13 +1,13 @@ package org.imdc.extensions.client import com.inductiveautomation.ignition.client.model.ClientContext -import com.inductiveautomation.ignition.common.project.Project -import com.inductiveautomation.ignition.common.script.hints.ScriptFunction +import com.inductiveautomation.ignition.common.resourcecollection.ResourceCollection +import com.inductiveautomation.ignition.common.script.hints.JythonElement import org.imdc.extensions.common.ProjectExtensions class ClientProjectExtensions(private val context: ClientContext) : ProjectExtensions { - @ScriptFunction(docBundlePrefix = "ClientProjectExtensions") - override fun getProject(): Project { + @JythonElement(docBundlePrefix = "ClientProjectExtensions") + override fun getProject(): ResourceCollection { return requireNotNull(context.project) } } diff --git a/common/src/main/java/org/imdc/extensions/common/UtilitiesExtensions.java b/common/src/main/java/org/imdc/extensions/common/UtilitiesExtensions.java index c6b66e6..8d068a4 100644 --- a/common/src/main/java/org/imdc/extensions/common/UtilitiesExtensions.java +++ b/common/src/main/java/org/imdc/extensions/common/UtilitiesExtensions.java @@ -21,7 +21,7 @@ import com.inductiveautomation.ignition.common.script.PyArgParser; import com.inductiveautomation.ignition.common.script.ScriptContext; import com.inductiveautomation.ignition.common.script.builtin.KeywordArgs; -import com.inductiveautomation.ignition.common.script.hints.ScriptFunction; +import com.inductiveautomation.ignition.common.script.hints.JythonElement; import com.inductiveautomation.ignition.common.tags.model.TagPath; import com.inductiveautomation.ignition.common.tags.paths.parser.TagPathParser; import org.apache.commons.lang3.tuple.Pair; @@ -41,13 +41,13 @@ public UtilitiesExtensions(CommonContext context) { this.context = context; } - @ScriptFunction(docBundlePrefix = "UtilitiesExtensions") + @JythonElement(docBundlePrefix = "UtilitiesExtensions") @UnsafeExtension public CommonContext getContext() { return context; } - @ScriptFunction(docBundlePrefix = "UtilitiesExtensions") + @JythonElement(docBundlePrefix = "UtilitiesExtensions") @KeywordArgs(names = {"object"}, types = {PyObject.class}) public PyObject deepCopy(PyObject[] args, String[] keywords) { PyArgParser parsedArgs = PyArgParser.parseArgs(args, keywords, this.getClass(), "deepCopy"); @@ -76,7 +76,7 @@ private static PyObject recursiveConvert(@NotNull PyObject object) { } } - @ScriptFunction(docBundlePrefix = "UtilitiesExtensions") + @JythonElement(docBundlePrefix = "UtilitiesExtensions") @KeywordArgs(names = {"expression"}, types = {String.class}) public QualifiedValue evalExpression(PyObject[] args, String[] keywords) throws Exception { if (args.length == 0) { @@ -116,7 +116,7 @@ public Expression createBoundExpression(String reference) throws RuntimeExceptio return new ConstantExpression(keywords.get(reference)); } else { try { - TagPath path = TagPathParser.parse(ScriptContext.defaultTagProvider(), reference); + TagPath path = TagPathParser.parse(ScriptContext.getDefaultTagProvider().orElseThrow(), reference); var tagValues = context.getTagManager().readAsync(List.of(path)).get(30, TimeUnit.SECONDS); return new ConstantExpression(tagValues.get(0).getValue()); } catch (IOException | InterruptedException | ExecutionException | TimeoutException e) { @@ -131,7 +131,7 @@ public FunctionFactory getFunctionFactory() { } } - @ScriptFunction(docBundlePrefix = "UtilitiesExtensions") + @JythonElement(docBundlePrefix = "UtilitiesExtensions") public UUID getUUID4() { return UUID.randomUUID(); } diff --git a/common/src/main/kotlin/org/imdc/extensions/common/DatasetExtensions.kt b/common/src/main/kotlin/org/imdc/extensions/common/DatasetExtensions.kt index 5b31d60..d1e35fe 100644 --- a/common/src/main/kotlin/org/imdc/extensions/common/DatasetExtensions.kt +++ b/common/src/main/kotlin/org/imdc/extensions/common/DatasetExtensions.kt @@ -6,7 +6,7 @@ import com.inductiveautomation.ignition.common.TypeUtilities import com.inductiveautomation.ignition.common.script.PyArgParser import com.inductiveautomation.ignition.common.script.builtin.KeywordArgs import com.inductiveautomation.ignition.common.script.hints.ScriptArg -import com.inductiveautomation.ignition.common.script.hints.ScriptFunction +import com.inductiveautomation.ignition.common.script.hints.JythonElement import com.inductiveautomation.ignition.common.util.DatasetBuilder import com.inductiveautomation.ignition.common.xmlserialization.ClassNameResolver import org.apache.poi.ss.usermodel.Cell @@ -35,7 +35,7 @@ import kotlin.streams.asSequence object DatasetExtensions { @Suppress("unused") - @ScriptFunction(docBundlePrefix = "DatasetExtensions") + @JythonElement(docBundlePrefix = "DatasetExtensions") @KeywordArgs( names = ["dataset", "mapper", "preserveColumnTypes"], types = [Dataset::class, PyFunction::class, Boolean::class], @@ -74,7 +74,7 @@ object DatasetExtensions { } @Suppress("unused") - @ScriptFunction(docBundlePrefix = "DatasetExtensions") + @JythonElement(docBundlePrefix = "DatasetExtensions") @KeywordArgs( names = ["dataset", "filter"], types = [Dataset::class, PyFunction::class], @@ -122,7 +122,7 @@ object DatasetExtensions { } @Suppress("unused") - @ScriptFunction(docBundlePrefix = "DatasetExtensions") + @JythonElement(docBundlePrefix = "DatasetExtensions") @KeywordArgs( names = ["dataset", "output", "includeTypes"], types = [Dataset::class, Appendable::class, Boolean::class], @@ -221,7 +221,7 @@ object DatasetExtensions { } @Suppress("unused") - @ScriptFunction(docBundlePrefix = "DatasetExtensions") + @JythonElement(docBundlePrefix = "DatasetExtensions") @KeywordArgs( names = ["dataset", "filterNull"], types = [Dataset::class, Boolean::class], @@ -253,7 +253,7 @@ object DatasetExtensions { } @Suppress("unused") - @ScriptFunction(docBundlePrefix = "DatasetExtensions") + @JythonElement(docBundlePrefix = "DatasetExtensions") @KeywordArgs( names = ["input", "headerRow", "sheetNumber", "firstRow", "lastRow", "firstColumn", "lastColumn", "typeOverrides"], types = [ByteArray::class, Int::class, Int::class, Int::class, Int::class, Int::class, Int::class, PyStringMap::class], @@ -412,7 +412,7 @@ object DatasetExtensions { } @Suppress("unused") - @ScriptFunction(docBundlePrefix = "DatasetExtensions") + @JythonElement(docBundlePrefix = "DatasetExtensions") fun equals( @ScriptArg("dataset1") ds1: Dataset, @ScriptArg("dataset2") ds2: Dataset, @@ -421,7 +421,7 @@ object DatasetExtensions { } @Suppress("unused") - @ScriptFunction(docBundlePrefix = "DatasetExtensions") + @JythonElement(docBundlePrefix = "DatasetExtensions") fun valuesEqual( @ScriptArg("dataset1") ds1: Dataset, @ScriptArg("dataset2") ds2: Dataset, @@ -440,7 +440,7 @@ object DatasetExtensions { } @Suppress("unused") - @ScriptFunction(docBundlePrefix = "DatasetExtensions") + @JythonElement(docBundlePrefix = "DatasetExtensions") @JvmOverloads fun columnsEqual( @ScriptArg("dataset1") ds1: Dataset, @@ -473,7 +473,7 @@ object DatasetExtensions { } } - @ScriptFunction(docBundlePrefix = "DatasetExtensions") + @JythonElement(docBundlePrefix = "DatasetExtensions") @KeywordArgs( names = ["**columns"], types = [KeywordArgs::class], diff --git a/common/src/main/kotlin/org/imdc/extensions/common/ExtensionDocProvider.kt b/common/src/main/kotlin/org/imdc/extensions/common/ExtensionDocProvider.kt index 85bcb02..0f7a960 100644 --- a/common/src/main/kotlin/org/imdc/extensions/common/ExtensionDocProvider.kt +++ b/common/src/main/kotlin/org/imdc/extensions/common/ExtensionDocProvider.kt @@ -2,6 +2,7 @@ package org.imdc.extensions.common import com.inductiveautomation.ignition.common.script.hints.PropertiesFileDocProvider import com.inductiveautomation.ignition.common.script.hints.ScriptFunctionDocProvider +import com.inductiveautomation.ignition.common.script.typing.CompletionDescriptor import java.lang.reflect.Method private val propertiesFileDocProvider = PropertiesFileDocProvider() @@ -13,20 +14,23 @@ private val WARNING = """ """.trimIndent() object ExtensionDocProvider : ScriptFunctionDocProvider by propertiesFileDocProvider { - override fun getMethodDescription(path: String, method: Method): String { - val methodDescription: String? = propertiesFileDocProvider.getMethodDescription(path, method) + override fun getMethodDescriptor(path: String, method: Method): CompletionDescriptor.Method? { + val base = propertiesFileDocProvider.getMethodDescriptor(path, method) + val unsafeAnnotation = method.getAnnotation() - return buildString { - if (unsafeAnnotation != null) { + return if (unsafeAnnotation != null) { + base?.copy(description = buildString { append("") append(WARNING) if (unsafeAnnotation.note.isNotEmpty()) { append("
").append(unsafeAnnotation.note) } append("


") - } - append(methodDescription.orEmpty()) + append(base.description.orEmpty()) + }) + } else { + base } } } diff --git a/common/src/main/kotlin/org/imdc/extensions/common/ProjectExtensions.kt b/common/src/main/kotlin/org/imdc/extensions/common/ProjectExtensions.kt index d229648..65e955b 100644 --- a/common/src/main/kotlin/org/imdc/extensions/common/ProjectExtensions.kt +++ b/common/src/main/kotlin/org/imdc/extensions/common/ProjectExtensions.kt @@ -1,7 +1,8 @@ package org.imdc.extensions.common -import com.inductiveautomation.ignition.common.project.Project +import com.inductiveautomation.ignition.common.resourcecollection.ResourceCollection + interface ProjectExtensions { - fun getProject(): Project + fun getProject(): ResourceCollection } diff --git a/common/src/main/kotlin/org/imdc/extensions/common/TagExtensions.kt b/common/src/main/kotlin/org/imdc/extensions/common/TagExtensions.kt index 589e9be..758e4c3 100644 --- a/common/src/main/kotlin/org/imdc/extensions/common/TagExtensions.kt +++ b/common/src/main/kotlin/org/imdc/extensions/common/TagExtensions.kt @@ -5,16 +5,17 @@ import com.inductiveautomation.ignition.common.config.PyTagList import com.inductiveautomation.ignition.common.script.PyArgParser import com.inductiveautomation.ignition.common.script.ScriptContext import com.inductiveautomation.ignition.common.script.builtin.KeywordArgs -import com.inductiveautomation.ignition.common.script.hints.ScriptFunction +import com.inductiveautomation.ignition.common.script.hints.JythonElement import com.inductiveautomation.ignition.common.tags.config.TagConfigurationModel import com.inductiveautomation.ignition.common.tags.model.TagPath import com.inductiveautomation.ignition.common.tags.paths.parser.TagPathParser import org.python.core.PyDictionary import org.python.core.PyObject +import kotlin.jvm.optionals.getOrNull abstract class TagExtensions { @UnsafeExtension - @ScriptFunction(docBundlePrefix = "TagExtensions") + @JythonElement(docBundlePrefix = "TagExtensions") @KeywordArgs( names = ["basePath", "recursive"], types = [String::class, Boolean::class], @@ -36,9 +37,10 @@ abstract class TagExtensions { } protected open fun parseTagPath(path: String): TagPath { - val parsed = TagPathParser.parse(ScriptContext.defaultTagProvider(), path) - if (TagPathParser.isRelativePath(parsed) && ScriptContext.relativeTagPathRoot() != null) { - return TagPathParser.derelativize(parsed, ScriptContext.relativeTagPathRoot()) + val parsed = TagPathParser.parse(ScriptContext.getDefaultTagProvider().orElseThrow(), path) + val tagPathRoot = ScriptContext.getRelativeTagPathRoot().getOrNull() + if (TagPathParser.isRelativePath(parsed) && tagPathRoot != null) { + return TagPathParser.derelativize(parsed, tagPathRoot) } return parsed } diff --git a/designer/src/main/kotlin/org/imdc/extensions/designer/DesignerProjectExtensions.kt b/designer/src/main/kotlin/org/imdc/extensions/designer/DesignerProjectExtensions.kt index df22708..0e366de 100644 --- a/designer/src/main/kotlin/org/imdc/extensions/designer/DesignerProjectExtensions.kt +++ b/designer/src/main/kotlin/org/imdc/extensions/designer/DesignerProjectExtensions.kt @@ -1,6 +1,6 @@ package org.imdc.extensions.designer -import com.inductiveautomation.ignition.common.script.hints.ScriptFunction +import com.inductiveautomation.ignition.common.script.hints.JythonElement import com.inductiveautomation.ignition.designer.IgnitionDesigner import com.inductiveautomation.ignition.designer.model.DesignerContext import com.inductiveautomation.ignition.designer.project.DesignableProject @@ -9,13 +9,13 @@ import org.imdc.extensions.common.ProjectExtensions import org.imdc.extensions.common.UnsafeExtension class DesignerProjectExtensions(private val context: DesignerContext) : ProjectExtensions { - @ScriptFunction(docBundlePrefix = "DesignerProjectExtensions") + @JythonElement(docBundlePrefix = "DesignerProjectExtensions") @UnsafeExtension override fun getProject(): DesignableProject { return requireNotNull(context.project) } - @ScriptFunction(docBundlePrefix = "DesignerProjectExtensions") + @JythonElement(docBundlePrefix = "DesignerProjectExtensions") @UnsafeExtension fun save() { MethodUtils.invokeMethod( @@ -30,7 +30,7 @@ class DesignerProjectExtensions(private val context: DesignerContext) : ProjectE ) } - @ScriptFunction(docBundlePrefix = "DesignerProjectExtensions") + @JythonElement(docBundlePrefix = "DesignerProjectExtensions") @UnsafeExtension fun update() { (context.frame as IgnitionDesigner).updateProject() diff --git a/gateway/src/main/kotlin/org/imdc/extensions/gateway/GatewayProjectExtensions.kt b/gateway/src/main/kotlin/org/imdc/extensions/gateway/GatewayProjectExtensions.kt index 69950a2..0ffea3f 100644 --- a/gateway/src/main/kotlin/org/imdc/extensions/gateway/GatewayProjectExtensions.kt +++ b/gateway/src/main/kotlin/org/imdc/extensions/gateway/GatewayProjectExtensions.kt @@ -1,24 +1,28 @@ package org.imdc.extensions.gateway -import com.inductiveautomation.ignition.common.project.RuntimeProject +import com.inductiveautomation.ignition.common.resourcecollection.RuntimeResourceCollection import com.inductiveautomation.ignition.common.script.ScriptContext +import com.inductiveautomation.ignition.common.script.hints.JythonElement import com.inductiveautomation.ignition.common.script.hints.ScriptArg -import com.inductiveautomation.ignition.common.script.hints.ScriptFunction import com.inductiveautomation.ignition.gateway.model.GatewayContext import org.imdc.extensions.common.ProjectExtensions import org.python.core.Py class GatewayProjectExtensions(private val context: GatewayContext) : ProjectExtensions { - @ScriptFunction(docBundlePrefix = "GatewayProjectExtensions") - override fun getProject(): RuntimeProject { - val defaultProject = ScriptContext.defaultProject() ?: throw Py.EnvironmentError("No context project populated") - return requireNotNull(getProject(defaultProject)) { "No such project $defaultProject" } + @JythonElement(docBundlePrefix = "GatewayProjectExtensions") + override fun getProject(): RuntimeResourceCollection { + val defaultProject = ScriptContext.getDefaultProject() + .orElseThrow { Py.EnvironmentError("No context project populated") } + + return requireNotNull(getProject(defaultProject)) { + "No such project $defaultProject" + } } - @ScriptFunction(docBundlePrefix = "GatewayProjectExtensions") + @JythonElement(docBundlePrefix = "GatewayProjectExtensions") fun getProject( @ScriptArg("project", optional = true) project: String, - ): RuntimeProject? { - return context.projectManager.getProject(project).orElse(null) + ): RuntimeResourceCollection? { + return context.projectManager.find(project).orElse(null) } } diff --git a/gateway/src/main/kotlin/org/imdc/extensions/gateway/HistoryServlet.kt b/gateway/src/main/kotlin/org/imdc/extensions/gateway/HistoryServlet.kt index 725639f..18a0d44 100644 --- a/gateway/src/main/kotlin/org/imdc/extensions/gateway/HistoryServlet.kt +++ b/gateway/src/main/kotlin/org/imdc/extensions/gateway/HistoryServlet.kt @@ -9,6 +9,9 @@ import com.inductiveautomation.ignition.common.sqltags.history.BasicTagHistoryQu import com.inductiveautomation.ignition.common.sqltags.history.ReturnFormat import com.inductiveautomation.ignition.common.util.LoggerEx import com.inductiveautomation.ignition.gateway.model.GatewayContext +import jakarta.servlet.http.HttpServlet +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.apache.http.entity.ContentType import java.io.PrintWriter import java.text.SimpleDateFormat @@ -16,9 +19,6 @@ import java.time.Instant import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit import java.util.Date -import javax.servlet.http.HttpServlet -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse class HistoryServlet : HttpServlet() { private lateinit var context: GatewayContext diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0a9f094..df3b500 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,13 @@ [versions] -java = "11" -kotlin = "1.9.22" -kotest = "5.8.0" -ignition = "8.1.0" +java = "17" +kotlin = "2.2.21" +kotest = "5.9.1" +ignition = "8.3.0" [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -modl = { id = "io.ia.sdk.modl", version = "0.1.1" } -ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "12.1.0" } +modl = { id = "io.ia.sdk.modl", version = "0.5.0" } +ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "12.3.0" } [libraries] ignition-common = { group = "com.inductiveautomation.ignitionsdk", name = "ignition-common", version.ref = "ignition" } @@ -21,7 +21,7 @@ kotest-junit = { group = "io.kotest", name = "kotest-runner-junit5", version.ref kotest-assertions-core = { group = "io.kotest", name = "kotest-assertions-core", version.ref = "kotest" } kotest-property = { group = "io.kotest", name = "kotest-property", version.ref = "kotest" } kotest-data = { group = "io.kotest", name = "kotest-framework-datatest", version.ref = "kotest" } -mockk = { group = "io.mockk", name = "mockk", version = "1.13.9" } +mockk = { group = "io.mockk", name = "mockk", version = "1.14.6" } [bundles] gateway = [ From c6974d8d137c3f6bcf79893ff4a4e565a4da1cb6 Mon Sep 17 00:00:00 2001 From: Paul Griffith Date: Wed, 5 Nov 2025 16:18:01 -0800 Subject: [PATCH 02/10] Update `upload-artifact` action --- .github/workflows/pr-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 8cb9527..698e0fb 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -13,7 +13,7 @@ jobs: - name: Build run: ./gradlew build - name: Upload Unsigned Module - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v5 with: name: ignition-extensions-unsigned path: build/Ignition-Extensions.unsigned.modl From 637561b5c2ed60f1bdf7fda694cd577b2ce7e8b3 Mon Sep 17 00:00:00 2001 From: Ben Musson <58087114+benmusson@users.noreply.github.com> Date: Wed, 5 Nov 2025 21:39:37 -0500 Subject: [PATCH 03/10] Revise `8.3` updates. (#1) --- .../kotlin/org/imdc/extensions/common/TagExtensions.kt | 7 ++++--- .../imdc/extensions/designer/DesignerProjectExtensions.kt | 2 ++ docker-compose.yml | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/common/src/main/kotlin/org/imdc/extensions/common/TagExtensions.kt b/common/src/main/kotlin/org/imdc/extensions/common/TagExtensions.kt index 758e4c3..453014a 100644 --- a/common/src/main/kotlin/org/imdc/extensions/common/TagExtensions.kt +++ b/common/src/main/kotlin/org/imdc/extensions/common/TagExtensions.kt @@ -38,9 +38,10 @@ abstract class TagExtensions { protected open fun parseTagPath(path: String): TagPath { val parsed = TagPathParser.parse(ScriptContext.getDefaultTagProvider().orElseThrow(), path) - val tagPathRoot = ScriptContext.getRelativeTagPathRoot().getOrNull() - if (TagPathParser.isRelativePath(parsed) && tagPathRoot != null) { - return TagPathParser.derelativize(parsed, tagPathRoot) + ScriptContext.getRelativeTagPathRoot().getOrNull()?.let { tagPathRoot -> + if(TagPathParser.isRelativePath(parsed)) { + return TagPathParser.derelativize(parsed, tagPathRoot) + } } return parsed } diff --git a/designer/src/main/kotlin/org/imdc/extensions/designer/DesignerProjectExtensions.kt b/designer/src/main/kotlin/org/imdc/extensions/designer/DesignerProjectExtensions.kt index 0e366de..37c5f1a 100644 --- a/designer/src/main/kotlin/org/imdc/extensions/designer/DesignerProjectExtensions.kt +++ b/designer/src/main/kotlin/org/imdc/extensions/designer/DesignerProjectExtensions.kt @@ -1,5 +1,6 @@ package org.imdc.extensions.designer +import com.inductiveautomation.ignition.common.gui.progress.DummyTaskProgressListener import com.inductiveautomation.ignition.common.script.hints.JythonElement import com.inductiveautomation.ignition.designer.IgnitionDesigner import com.inductiveautomation.ignition.designer.model.DesignerContext @@ -22,6 +23,7 @@ class DesignerProjectExtensions(private val context: DesignerContext) : ProjectE context.frame, true, // forceAccess "handleSave", + DummyTaskProgressListener(), // pl (ProgressListener) false, // saveAs null, // newName false, // commitOnly diff --git a/docker-compose.yml b/docker-compose.yml index 79350cf..d2ed05b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: gateway: - image: inductiveautomation/ignition:8.1.37 + image: inductiveautomation/ignition:8.3.0 ports: - 18088:8088 - 18000:8000 From 0d53b152ad3ff23c057053ea50a362aaee8a3043 Mon Sep 17 00:00:00 2001 From: Paul Griffith Date: Thu, 6 Nov 2025 11:14:14 -0800 Subject: [PATCH 04/10] Limit Kotlin stdlib to platform provided 1.9.20 --- .github/workflows/pr-build.yml | 2 +- .github/workflows/release.yml | 2 +- build.gradle.kts | 2 +- client/build.gradle.kts | 10 +++++++++- common/build.gradle.kts | 10 +++++++++- designer/build.gradle.kts | 10 +++++++++- gateway/build.gradle.kts | 4 ---- gradle.properties | 2 ++ gradle/libs.versions.toml | 2 +- settings.gradle.kts | 4 ++++ 10 files changed, 37 insertions(+), 11 deletions(-) diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 698e0fb..097195e 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -8,7 +8,7 @@ jobs: - uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: 11 + java-version: 17 cache: 'gradle' - name: Build run: ./gradlew build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 70629ed..e096668 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: 11 + java-version: 17 cache: 'gradle' - name: Deserialize signing certs run: | diff --git a/build.gradle.kts b/build.gradle.kts index d8bf480..2e9a5e9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - alias(libs.plugins.kotlin) + alias(libs.plugins.kotlin) apply false alias(libs.plugins.modl) } diff --git a/client/build.gradle.kts b/client/build.gradle.kts index 59ea172..69738bd 100644 --- a/client/build.gradle.kts +++ b/client/build.gradle.kts @@ -4,7 +4,15 @@ plugins { } kotlin { - jvmToolchain(libs.versions.java.map(String::toInt).get()) + jvmToolchain { + languageVersion = libs.versions.java.map(JavaLanguageVersion::of) + } +} + +java { + toolchain { + languageVersion = libs.versions.java.map(JavaLanguageVersion::of) + } } dependencies { diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 8df1fca..1afac21 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -4,7 +4,15 @@ plugins { } kotlin { - jvmToolchain(libs.versions.java.map(String::toInt).get()) + jvmToolchain { + languageVersion = libs.versions.java.map(JavaLanguageVersion::of) + } +} + +java { + toolchain { + languageVersion = libs.versions.java.map(JavaLanguageVersion::of) + } } dependencies { diff --git a/designer/build.gradle.kts b/designer/build.gradle.kts index 4e658fb..69dcbca 100644 --- a/designer/build.gradle.kts +++ b/designer/build.gradle.kts @@ -4,7 +4,15 @@ plugins { } kotlin { - jvmToolchain(libs.versions.java.map(String::toInt).get()) + jvmToolchain { + languageVersion = libs.versions.java.map(JavaLanguageVersion::of) + } +} + +java { + toolchain { + languageVersion = libs.versions.java.map(JavaLanguageVersion::of) + } } dependencies { diff --git a/gateway/build.gradle.kts b/gateway/build.gradle.kts index 1783cc1..d0d1a22 100644 --- a/gateway/build.gradle.kts +++ b/gateway/build.gradle.kts @@ -3,10 +3,6 @@ plugins { kotlin("jvm") } -kotlin { - jvmToolchain(libs.versions.java.map(String::toInt).get()) -} - dependencies { compileOnly(libs.bundles.gateway) compileOnly(projects.common) diff --git a/gradle.properties b/gradle.properties index 6b09920..1920a3b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,3 @@ version=0.0.1-SNAPSHOT + +kotlin.stdlib.default.dependency=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index df3b500..7f583d8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] java = "17" -kotlin = "2.2.21" +kotlin = "1.9.20" kotest = "5.9.1" ignition = "8.3.0" diff --git a/settings.gradle.kts b/settings.gradle.kts index 43734e1..5bef0e1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,3 +15,7 @@ include( "designer", "client", ) + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} From bcb17e36506eb12b29993e6ad18345983556cf33 Mon Sep 17 00:00:00 2001 From: Paul Griffith Date: Thu, 6 Nov 2025 13:58:09 -0800 Subject: [PATCH 05/10] Pin mockk and Kotest to versions requiring Kotlin 1.9.x --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7f583d8..7cc6687 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] java = "17" kotlin = "1.9.20" -kotest = "5.9.1" +kotest = "5.8.0" ignition = "8.3.0" [plugins] @@ -21,7 +21,7 @@ kotest-junit = { group = "io.kotest", name = "kotest-runner-junit5", version.ref kotest-assertions-core = { group = "io.kotest", name = "kotest-assertions-core", version.ref = "kotest" } kotest-property = { group = "io.kotest", name = "kotest-property", version.ref = "kotest" } kotest-data = { group = "io.kotest", name = "kotest-framework-datatest", version.ref = "kotest" } -mockk = { group = "io.mockk", name = "mockk", version = "1.14.6" } +mockk = { group = "io.mockk", name = "mockk", version = "1.13.9" } [bundles] gateway = [ From 0d9c058a35b83c2997e2f60184f2bb2a448cc417 Mon Sep 17 00:00:00 2001 From: Paul Griffith Date: Thu, 6 Nov 2025 14:02:44 -0800 Subject: [PATCH 06/10] Add toolchain to gateway bundle (TODO: buildSrc) --- gateway/build.gradle.kts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gateway/build.gradle.kts b/gateway/build.gradle.kts index d0d1a22..9b1f8ba 100644 --- a/gateway/build.gradle.kts +++ b/gateway/build.gradle.kts @@ -3,6 +3,18 @@ plugins { kotlin("jvm") } +kotlin { + jvmToolchain { + languageVersion = libs.versions.java.map(JavaLanguageVersion::of) + } +} + +java { + toolchain { + languageVersion = libs.versions.java.map(JavaLanguageVersion::of) + } +} + dependencies { compileOnly(libs.bundles.gateway) compileOnly(projects.common) From 566d5f6a82d582afc624018c45248b272199bc69 Mon Sep 17 00:00:00 2001 From: Ben Musson <58087114+benmusson@users.noreply.github.com> Date: Fri, 7 Nov 2025 19:36:32 -0500 Subject: [PATCH 07/10] introduce build-logic project (#2) --- .gitignore | 1 + build-logic/build.gradle.kts | 3 +++ build-logic/conventions/build.gradle.kts | 3 +++ .../main/kotlin/imdc/build/base.gradle.kts | 12 +++++++++ .../build/ignition-module-scope.gradle.kts | 26 +++++++++++++++++++ .../kotlin/imdc/test/junit-tests.gradle.kts | 8 ++++++ build-logic/settings.gradle.kts | 19 ++++++++++++++ build.gradle.kts | 13 ---------- client/build.gradle.kts | 15 +---------- common/build.gradle.kts | 25 +++--------------- designer/build.gradle.kts | 15 +---------- gateway/build.gradle.kts | 15 +---------- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle.kts | 1 + 14 files changed, 80 insertions(+), 78 deletions(-) create mode 100644 build-logic/build.gradle.kts create mode 100644 build-logic/conventions/build.gradle.kts create mode 100644 build-logic/conventions/src/main/kotlin/imdc/build/base.gradle.kts create mode 100644 build-logic/conventions/src/main/kotlin/imdc/build/ignition-module-scope.gradle.kts create mode 100644 build-logic/conventions/src/main/kotlin/imdc/test/junit-tests.gradle.kts create mode 100644 build-logic/settings.gradle.kts diff --git a/.gitignore b/.gitignore index 1a00f40..df59032 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .gradle/ local.properties +/common/.jython_cache diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 0000000..d418805 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + `kotlin-dsl` apply false +} \ No newline at end of file diff --git a/build-logic/conventions/build.gradle.kts b/build-logic/conventions/build.gradle.kts new file mode 100644 index 0000000..46f9c3f --- /dev/null +++ b/build-logic/conventions/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + `kotlin-dsl` +} \ No newline at end of file diff --git a/build-logic/conventions/src/main/kotlin/imdc/build/base.gradle.kts b/build-logic/conventions/src/main/kotlin/imdc/build/base.gradle.kts new file mode 100644 index 0000000..de35cf0 --- /dev/null +++ b/build-logic/conventions/src/main/kotlin/imdc/build/base.gradle.kts @@ -0,0 +1,12 @@ +package imdc.build + +group = "org.imdc.extensions" + +plugins { base } + +repositories { + mavenCentral() + gradlePluginPortal() + maven(url = "https://nexus.inductiveautomation.com/repository/public/") + maven(url = "https://nexus.inductiveautomation.com/repository/inductiveautomation-releases/") +} diff --git a/build-logic/conventions/src/main/kotlin/imdc/build/ignition-module-scope.gradle.kts b/build-logic/conventions/src/main/kotlin/imdc/build/ignition-module-scope.gradle.kts new file mode 100644 index 0000000..137005a --- /dev/null +++ b/build-logic/conventions/src/main/kotlin/imdc/build/ignition-module-scope.gradle.kts @@ -0,0 +1,26 @@ +package imdc.build + +import libs +import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension + +plugins { + id("imdc.build.base") + alias(libs.plugins.kotlin) + `java-library` +} + +version = project.parent?.version ?: "0.0.0-SNAPSHOT" + +val jvmLanguageVersion = libs.versions.java.map { JavaLanguageVersion.of(it) } + +configure { + jvmToolchain { + languageVersion = jvmLanguageVersion + } +} + +configure { + toolchain { + languageVersion = jvmLanguageVersion + } +} diff --git a/build-logic/conventions/src/main/kotlin/imdc/test/junit-tests.gradle.kts b/build-logic/conventions/src/main/kotlin/imdc/test/junit-tests.gradle.kts new file mode 100644 index 0000000..e4986c9 --- /dev/null +++ b/build-logic/conventions/src/main/kotlin/imdc/test/junit-tests.gradle.kts @@ -0,0 +1,8 @@ +package imdc.test + +tasks { + withType { + useJUnitPlatform() + jvmArgs = listOf("--add-opens", "java.base/java.io=ALL-UNNAMED") + } +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 0000000..6522961 --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,19 @@ +rootProject.name = "build-logic" + +plugins { + id("dev.panuszewski.typesafe-conventions") version "0.10.0" +} + +dependencyResolutionManagement { + repositories { + mavenCentral() + gradlePluginPortal() + maven(url = "https://nexus.inductiveautomation.com/repository/public/") + maven(url = "https://nexus.inductiveautomation.com/repository/inductiveautomation-releases/") + maven(url = "https://nexus.inductiveautomation.com/repository/inductiveautomation-snapshots/") + } +} + +include( + "conventions", +) \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 2e9a5e9..278c1cf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,20 +1,7 @@ plugins { - alias(libs.plugins.kotlin) apply false alias(libs.plugins.modl) } -allprojects { - repositories { - mavenCentral() - maven("https://nexus.inductiveautomation.com/repository/public") - } -} - -subprojects { - // cascade version, which will be set at command line in CI, down to subprojects - version = rootProject.version -} - ignitionModule { name = "Ignition Extensions" fileName = "Ignition-Extensions.modl" diff --git a/client/build.gradle.kts b/client/build.gradle.kts index 69738bd..d69cfb2 100644 --- a/client/build.gradle.kts +++ b/client/build.gradle.kts @@ -1,18 +1,5 @@ plugins { - `java-library` - kotlin("jvm") -} - -kotlin { - jvmToolchain { - languageVersion = libs.versions.java.map(JavaLanguageVersion::of) - } -} - -java { - toolchain { - languageVersion = libs.versions.java.map(JavaLanguageVersion::of) - } + id("imdc.build.ignition-module-scope") } dependencies { diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 1afac21..1550c55 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -1,18 +1,6 @@ plugins { - `java-library` - kotlin("jvm") -} - -kotlin { - jvmToolchain { - languageVersion = libs.versions.java.map(JavaLanguageVersion::of) - } -} - -java { - toolchain { - languageVersion = libs.versions.java.map(JavaLanguageVersion::of) - } + id("imdc.build.ignition-module-scope") + id("imdc.test.junit-tests") } dependencies { @@ -20,11 +8,4 @@ dependencies { testImplementation(libs.ignition.common) testImplementation(libs.bundles.kotest) testImplementation(libs.mockk) -} - -tasks { - withType { - useJUnitPlatform() - jvmArgs = listOf("--add-opens", "java.base/java.io=ALL-UNNAMED") - } -} +} \ No newline at end of file diff --git a/designer/build.gradle.kts b/designer/build.gradle.kts index 69dcbca..850c947 100644 --- a/designer/build.gradle.kts +++ b/designer/build.gradle.kts @@ -1,18 +1,5 @@ plugins { - `java-library` - kotlin("jvm") -} - -kotlin { - jvmToolchain { - languageVersion = libs.versions.java.map(JavaLanguageVersion::of) - } -} - -java { - toolchain { - languageVersion = libs.versions.java.map(JavaLanguageVersion::of) - } + id("imdc.build.ignition-module-scope") } dependencies { diff --git a/gateway/build.gradle.kts b/gateway/build.gradle.kts index 9b1f8ba..8c8d8b4 100644 --- a/gateway/build.gradle.kts +++ b/gateway/build.gradle.kts @@ -1,18 +1,5 @@ plugins { - `java-library` - kotlin("jvm") -} - -kotlin { - jvmToolchain { - languageVersion = libs.versions.java.map(JavaLanguageVersion::of) - } -} - -java { - toolchain { - languageVersion = libs.versions.java.map(JavaLanguageVersion::of) - } + id("imdc.build.ignition-module-scope") } dependencies { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22c..b82aa23 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle.kts b/settings.gradle.kts index 5bef0e1..4ea0b47 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,6 +9,7 @@ pluginManagement { rootProject.name = "ignition-extensions" +includeBuild("build-logic") include( "common", "gateway", From dbc675ddfdd1949a44b0ddde9f7182b8d153d6db Mon Sep 17 00:00:00 2001 From: Paul Griffith Date: Fri, 7 Nov 2025 16:46:28 -0800 Subject: [PATCH 08/10] Swap in spotless for ktlint (and actually configure it)... --- .../main/kotlin/imdc/build/base.gradle.kts | 5 ++++- .../build/ignition-module-scope.gradle.kts | 20 +++++++++++++++++++ common/build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 +- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/build-logic/conventions/src/main/kotlin/imdc/build/base.gradle.kts b/build-logic/conventions/src/main/kotlin/imdc/build/base.gradle.kts index de35cf0..9c9c3d3 100644 --- a/build-logic/conventions/src/main/kotlin/imdc/build/base.gradle.kts +++ b/build-logic/conventions/src/main/kotlin/imdc/build/base.gradle.kts @@ -1,8 +1,11 @@ package imdc.build + group = "org.imdc.extensions" -plugins { base } +plugins { + base +} repositories { mavenCentral() diff --git a/build-logic/conventions/src/main/kotlin/imdc/build/ignition-module-scope.gradle.kts b/build-logic/conventions/src/main/kotlin/imdc/build/ignition-module-scope.gradle.kts index 137005a..ddf5b81 100644 --- a/build-logic/conventions/src/main/kotlin/imdc/build/ignition-module-scope.gradle.kts +++ b/build-logic/conventions/src/main/kotlin/imdc/build/ignition-module-scope.gradle.kts @@ -7,6 +7,8 @@ plugins { id("imdc.build.base") alias(libs.plugins.kotlin) `java-library` + + alias(libs.plugins.spotless) } version = project.parent?.version ?: "0.0.0-SNAPSHOT" @@ -24,3 +26,21 @@ configure { languageVersion = jvmLanguageVersion } } + +spotless { + format("misc") { + target("*.gradle", ".gitattributes", ".gitignore") + trimTrailingWhitespace() + endWithNewline() + } + java { + palantirJavaFormat() + formatAnnotations() + } + kotlin { + ktlint() + } + kotlinGradle { + ktlint() + } +} diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 1550c55..2fdee14 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -8,4 +8,4 @@ dependencies { testImplementation(libs.ignition.common) testImplementation(libs.bundles.kotest) testImplementation(libs.mockk) -} \ No newline at end of file +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7cc6687..a69fcd9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ ignition = "8.3.0" [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } modl = { id = "io.ia.sdk.modl", version = "0.5.0" } -ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "12.3.0" } +spotless = { id = "com.diffplug.spotless", version = "8.0.0" } [libraries] ignition-common = { group = "com.inductiveautomation.ignitionsdk", name = "ignition-common", version.ref = "ignition" } From 43dc0d0e3154b35ec504dab64e7fba66ab867841 Mon Sep 17 00:00:00 2001 From: Paul Griffith Date: Fri, 7 Nov 2025 16:53:53 -0800 Subject: [PATCH 09/10] Add ratchetFrom --- .../src/main/kotlin/imdc/build/ignition-module-scope.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build-logic/conventions/src/main/kotlin/imdc/build/ignition-module-scope.gradle.kts b/build-logic/conventions/src/main/kotlin/imdc/build/ignition-module-scope.gradle.kts index ddf5b81..6dbe6c7 100644 --- a/build-logic/conventions/src/main/kotlin/imdc/build/ignition-module-scope.gradle.kts +++ b/build-logic/conventions/src/main/kotlin/imdc/build/ignition-module-scope.gradle.kts @@ -28,6 +28,7 @@ configure { } spotless { + ratchetFrom("HEAD") format("misc") { target("*.gradle", ".gitattributes", ".gitignore") trimTrailingWhitespace() From 44aae3ec85d72602df40d4e4afd8daa15c545b61 Mon Sep 17 00:00:00 2001 From: Paul Griffith Date: Fri, 7 Nov 2025 17:01:29 -0800 Subject: [PATCH 10/10] Put history servlet behind auth --- .../org/imdc/extensions/gateway/HistoryServlet.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/gateway/src/main/kotlin/org/imdc/extensions/gateway/HistoryServlet.kt b/gateway/src/main/kotlin/org/imdc/extensions/gateway/HistoryServlet.kt index 18a0d44..31346ee 100644 --- a/gateway/src/main/kotlin/org/imdc/extensions/gateway/HistoryServlet.kt +++ b/gateway/src/main/kotlin/org/imdc/extensions/gateway/HistoryServlet.kt @@ -8,6 +8,11 @@ import com.inductiveautomation.ignition.common.sqltags.history.AggregationMode import com.inductiveautomation.ignition.common.sqltags.history.BasicTagHistoryQueryParams import com.inductiveautomation.ignition.common.sqltags.history.ReturnFormat import com.inductiveautomation.ignition.common.util.LoggerEx +import com.inductiveautomation.ignition.gateway.dataroutes.AccessControlStrategy +import com.inductiveautomation.ignition.gateway.dataroutes.PermissionType +import com.inductiveautomation.ignition.gateway.dataroutes.PermissionType.getStrategies +import com.inductiveautomation.ignition.gateway.dataroutes.RequestContext +import com.inductiveautomation.ignition.gateway.dataroutes.RouteAccess import com.inductiveautomation.ignition.gateway.model.GatewayContext import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest @@ -27,7 +32,15 @@ class HistoryServlet : HttpServlet() { context = servletContext.getAttribute(GatewayContext.SERVLET_CONTEXT_KEY) as GatewayContext } + private val strategies = PermissionType.getStrategies(PermissionType.READ) + override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) { + val requestContext = RequestContext(req, req.servletPath) + val routeAccess = AccessControlStrategy.or(strategies).canAccess(requestContext) + if (routeAccess != RouteAccess.GRANTED) { + resp.sendError(HttpServletResponse.SC_FORBIDDEN) + return + } resp.contentType = ContentType.APPLICATION_JSON.toString() resp.writer.use { writer -> val historyQuery: BasicTagHistoryQueryParams =