From 855955b6429e0a9cb6b8e10890e03c15837be6a6 Mon Sep 17 00:00:00 2001 From: Vivian Zhang Date: Fri, 22 Aug 2025 14:34:17 -0700 Subject: [PATCH 1/4] Fail ignition build when the same jar in moduleContent folder with multiple versions detected Fixes: IGN-10168 --- gradle-module-plugin/build.gradle.kts | 1 + .../gradle/libs.versions.toml | 2 + .../ia/sdk/gradle/modl/task/ZipModuleTests.kt | 55 ++++++++++++++++++- .../io/ia/sdk/gradle/modl/task/ZipModule.kt | 37 +++++++++++++ 4 files changed, 93 insertions(+), 2 deletions(-) diff --git a/gradle-module-plugin/build.gradle.kts b/gradle-module-plugin/build.gradle.kts index d0ba81e..13c0118 100644 --- a/gradle-module-plugin/build.gradle.kts +++ b/gradle-module-plugin/build.gradle.kts @@ -61,6 +61,7 @@ dependencies { implementation(libs.kotlinXmlBuilder) api(libs.moduleSigner) testImplementation(libs.kotlinTestJunit) + testImplementation(libs.junit.jupiter.api) testImplementation("io.ia.sdk.tools.module.gen:generator-core") } diff --git a/gradle-module-plugin/gradle/libs.versions.toml b/gradle-module-plugin/gradle/libs.versions.toml index 7572a09..bc3e962 100644 --- a/gradle-module-plugin/gradle/libs.versions.toml +++ b/gradle-module-plugin/gradle/libs.versions.toml @@ -1,6 +1,7 @@ [versions] kotlin = "1.7.10" moshi = "1.9.3" +junit-jupiter = "5.11.4" [libraries] # Dependencies referencable in buildscripts. Note that dashes are replaced by periods in the buildscript reference. @@ -8,6 +9,7 @@ guava = { module = "com.google.guava:guava", version = "30.1.1-jre" } kotlinTest = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlinTestJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } kotlinXmlBuilder = { module = "org.redundent:kotlin-xml-builder", version = "1.7.2" } +junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit-jupiter" } moshi = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } moshiCodegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" } moduleSigner = { module = "com.inductiveautomation.ignitionsdk:module-signer", version = "0.0.1.ia" } diff --git a/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt b/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt index 7f180dd..3b04641 100644 --- a/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt +++ b/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt @@ -3,11 +3,62 @@ package io.ia.sdk.gradle.modl.task import io.ia.ignition.module.generator.ModuleGenerator import io.ia.sdk.gradle.modl.BaseTest import io.ia.sdk.gradle.modl.util.unsignedModuleName -import org.junit.Test -import kotlin.test.assertTrue +import org.gradle.api.Project +import org.gradle.api.internal.project.DefaultProject +import org.gradle.api.tasks.TaskExecutionException +import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import java.io.File class ZipModuleTests : BaseTest() { + @TempDir + lateinit var testProjectDir: File + private lateinit var project: Project + private lateinit var task: ZipModule + + @BeforeEach + fun setup() { + project = ProjectBuilder.builder().withProjectDir(testProjectDir).build() as DefaultProject + task = project.tasks.create("testTask", ZipModule::class.java) + + // Set up the task properties with the test directory + task.content.set(project.objects.directoryProperty().fileValue(File(testProjectDir, "content"))) + task.unsignedModule.set(project.objects.fileProperty().fileValue(File(testProjectDir, "output.modl"))) + } + + @Test + fun `task succeeds when no duplicate jars exist`() { + // Arrange + val contentDir = File(testProjectDir, "content").apply { mkdirs() } + File(contentDir, "my-lib-1.0.jar").createNewFile() + File(contentDir, "another-lib-2.0.jar").createNewFile() + + // Act & Assert: The task should not throw an exception + task.execute() + } + + @Test + fun `task fails when duplicate jars with different versions exist`() { + // Arrange + val contentDir = File(testProjectDir, "content").apply { mkdirs() } + File(contentDir, "my-lib-1.0.jar").createNewFile() + File(contentDir, "my-lib-2.0.jar").createNewFile() // This is the duplicate + + // Act & Assert: The task should throw a TaskExecutionException + val exception = assertThrows(TaskExecutionException::class.java) { + task.execute() + } + + // Verify the exception message + val expectedMessage = "Jar with 'my-lib' has multiple versions presented" + assertTrue(exception.message!!.contains(expectedMessage)) + } + @Test fun `unsigned module is built and has appropriate name`() { val name = "Some Thing" diff --git a/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt b/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt index 26e9c92..10011e4 100644 --- a/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt +++ b/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt @@ -13,6 +13,8 @@ import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.TaskExecutionException +import java.io.File import javax.inject.Inject /** @@ -46,10 +48,45 @@ open class ZipModule @Inject constructor(objects: ObjectFactory) : DefaultTask() val unsignedFile = unsignedModule.get() val contentDir = content.get().asFile + checkDuplicateJars(contentDir) + project.logger.info("Zipping '${contentDir.absolutePath}' into ' ${unsignedFile.asFile.absolutePath}'") project.ant.invokeMethod( "zip", mapOf("basedir" to contentDir, "destfile" to unsignedFile) ) } + + /** + * Try to detect jars in contentDir with the same name but have multiple + * versions existed. Fail the build when it's found. + **/ + fun checkDuplicateJars(contentDir: File) { + project.logger.info("Parsing file name in: ${contentDir.absolutePath}") + + val fileMap = mutableMapOf() + val regex = "^(.+?)-(\\d+.*)(?:\\.jar)".toRegex() + + contentDir.walk().filter { it.isFile }.forEach { file -> + val matchResult = regex.find(file.name) // Match all the jar files + + if (matchResult != null) { + val name = matchResult.groupValues[1] + val version = matchResult.groupValues[2] + + if (fileMap.containsKey(name)) { + throw TaskExecutionException( + this, + IllegalArgumentException( + """Jar with '$name' has multiple versions presented in ${contentDir.absolutePath} + Please ensure only one version exist (preferably the highest) and update lib.version.toml file if needed. + """.trimIndent() + ) + ) + } else { + fileMap[name] = version + } + } + } + } } From 6353e747a736e01aef5b6b59cac47846189da776 Mon Sep 17 00:00:00 2001 From: Vivian Zhang Date: Tue, 26 Aug 2025 09:59:56 -0700 Subject: [PATCH 2/4] Update after review --- gradle-module-plugin/build.gradle.kts | 3 +- .../gradle/libs.versions.toml | 2 - .../ia/sdk/gradle/modl/task/ZipModuleTests.kt | 42 +++++++++++-------- .../io/ia/sdk/gradle/modl/task/ZipModule.kt | 23 ++++------ 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/gradle-module-plugin/build.gradle.kts b/gradle-module-plugin/build.gradle.kts index 13c0118..a478986 100644 --- a/gradle-module-plugin/build.gradle.kts +++ b/gradle-module-plugin/build.gradle.kts @@ -20,7 +20,7 @@ repositories { } group = "io.ia.sdk" -version = "0.4.1" +version = "0.5.0-SNAPSHOT" configurations { val functionalTestImplementation by registering { @@ -61,7 +61,6 @@ dependencies { implementation(libs.kotlinXmlBuilder) api(libs.moduleSigner) testImplementation(libs.kotlinTestJunit) - testImplementation(libs.junit.jupiter.api) testImplementation("io.ia.sdk.tools.module.gen:generator-core") } diff --git a/gradle-module-plugin/gradle/libs.versions.toml b/gradle-module-plugin/gradle/libs.versions.toml index bc3e962..7572a09 100644 --- a/gradle-module-plugin/gradle/libs.versions.toml +++ b/gradle-module-plugin/gradle/libs.versions.toml @@ -1,7 +1,6 @@ [versions] kotlin = "1.7.10" moshi = "1.9.3" -junit-jupiter = "5.11.4" [libraries] # Dependencies referencable in buildscripts. Note that dashes are replaced by periods in the buildscript reference. @@ -9,7 +8,6 @@ guava = { module = "com.google.guava:guava", version = "30.1.1-jre" } kotlinTest = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlinTestJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } kotlinXmlBuilder = { module = "org.redundent:kotlin-xml-builder", version = "1.7.2" } -junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit-jupiter" } moshi = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } moshiCodegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" } moduleSigner = { module = "com.inductiveautomation.ignitionsdk:module-signer", version = "0.0.1.ia" } diff --git a/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt b/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt index 3b04641..11d49bd 100644 --- a/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt +++ b/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt @@ -7,55 +7,61 @@ import org.gradle.api.Project import org.gradle.api.internal.project.DefaultProject import org.gradle.api.tasks.TaskExecutionException import org.gradle.testfixtures.ProjectBuilder -import org.junit.jupiter.api.Assertions.assertThrows -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.io.TempDir +import kotlin.io.path.createTempDirectory +import kotlin.test.BeforeTest +import kotlin.test.AfterTest +import kotlin.test.Test +import kotlin.test.assertTrue +import kotlin.test.assertFailsWith import java.io.File class ZipModuleTests : BaseTest() { - @TempDir - lateinit var testProjectDir: File + private lateinit var tempDir: File private lateinit var project: Project private lateinit var task: ZipModule - @BeforeEach + @BeforeTest fun setup() { - project = ProjectBuilder.builder().withProjectDir(testProjectDir).build() as DefaultProject + tempDir = createTempDirectory().toFile() + project = ProjectBuilder.builder().withProjectDir(tempDir).build() as DefaultProject task = project.tasks.create("testTask", ZipModule::class.java) - // Set up the task properties with the test directory - task.content.set(project.objects.directoryProperty().fileValue(File(testProjectDir, "content"))) - task.unsignedModule.set(project.objects.fileProperty().fileValue(File(testProjectDir, "output.modl"))) + // Set up the task properties with the temp directory + task.content.set(project.objects.directoryProperty().fileValue(File(tempDir, "content"))) + task.unsignedModule.set(project.objects.fileProperty().fileValue(File(tempDir, "output.modl"))) + } + + @AfterTest + fun cleanup() { + tempDir.deleteRecursively() } @Test fun `task succeeds when no duplicate jars exist`() { // Arrange - val contentDir = File(testProjectDir, "content").apply { mkdirs() } + val contentDir = task.content.asFile.get().apply { mkdirs() } File(contentDir, "my-lib-1.0.jar").createNewFile() File(contentDir, "another-lib-2.0.jar").createNewFile() - // Act & Assert: The task should not throw an exception + // Act: The task should not throw an exception task.execute() } @Test fun `task fails when duplicate jars with different versions exist`() { // Arrange - val contentDir = File(testProjectDir, "content").apply { mkdirs() } + val contentDir = task.content.asFile.get().apply { mkdirs() } File(contentDir, "my-lib-1.0.jar").createNewFile() File(contentDir, "my-lib-2.0.jar").createNewFile() // This is the duplicate - // Act & Assert: The task should throw a TaskExecutionException - val exception = assertThrows(TaskExecutionException::class.java) { + // Act & Assert: The task should throw a IllegalArgumentException + val exception = assertFailsWith{ task.execute() } // Verify the exception message - val expectedMessage = "Jar with 'my-lib' has multiple versions presented" + val expectedMessage = "Library 'my-lib' exists in multiple versions" assertTrue(exception.message!!.contains(expectedMessage)) } diff --git a/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt b/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt index 10011e4..f102993 100644 --- a/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt +++ b/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt @@ -58,33 +58,26 @@ open class ZipModule @Inject constructor(objects: ObjectFactory) : DefaultTask() } /** - * Try to detect jars in contentDir with the same name but have multiple - * versions existed. Fail the build when it's found. + * Fail the build when jars in the contentDir with the same name has + * multiple versions detected. **/ fun checkDuplicateJars(contentDir: File) { project.logger.info("Parsing file name in: ${contentDir.absolutePath}") - val fileMap = mutableMapOf() - val regex = "^(.+?)-(\\d+.*)(?:\\.jar)".toRegex() + val fileSet = mutableSetOf() + val regex = "^(.+?)-(?:\\d+.*)(?:\\.jar)".toRegex() contentDir.walk().filter { it.isFile }.forEach { file -> val matchResult = regex.find(file.name) // Match all the jar files if (matchResult != null) { val name = matchResult.groupValues[1] - val version = matchResult.groupValues[2] - if (fileMap.containsKey(name)) { - throw TaskExecutionException( - this, - IllegalArgumentException( - """Jar with '$name' has multiple versions presented in ${contentDir.absolutePath} - Please ensure only one version exist (preferably the highest) and update lib.version.toml file if needed. - """.trimIndent() - ) - ) + if (fileSet.contains(name)) { + throw IllegalArgumentException( + "Library '$name' exists in multiple versions in ${contentDir.absolutePath}") } else { - fileMap[name] = version + fileSet.add(name) } } } From 12af95a4e85f3e63242db3a0da8978cada0a46dd Mon Sep 17 00:00:00 2001 From: Vivian Zhang Date: Wed, 27 Aug 2025 09:19:34 -0700 Subject: [PATCH 3/4] Update plugin version --- gradle-module-plugin/build.gradle.kts | 2 +- .../kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt | 1 - .../src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/gradle-module-plugin/build.gradle.kts b/gradle-module-plugin/build.gradle.kts index a478986..31f0908 100644 --- a/gradle-module-plugin/build.gradle.kts +++ b/gradle-module-plugin/build.gradle.kts @@ -20,7 +20,7 @@ repositories { } group = "io.ia.sdk" -version = "0.5.0-SNAPSHOT" +version = "0.5.0" configurations { val functionalTestImplementation by registering { diff --git a/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt b/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt index 11d49bd..dd225c0 100644 --- a/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt +++ b/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt @@ -5,7 +5,6 @@ import io.ia.sdk.gradle.modl.BaseTest import io.ia.sdk.gradle.modl.util.unsignedModuleName import org.gradle.api.Project import org.gradle.api.internal.project.DefaultProject -import org.gradle.api.tasks.TaskExecutionException import org.gradle.testfixtures.ProjectBuilder import kotlin.io.path.createTempDirectory import kotlin.test.BeforeTest diff --git a/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt b/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt index f102993..7d16824 100644 --- a/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt +++ b/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt @@ -13,7 +13,6 @@ import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction -import org.gradle.api.tasks.TaskExecutionException import java.io.File import javax.inject.Inject From f6a784a5b21396c6e131394efd9e59b04d8e0683 Mon Sep 17 00:00:00 2001 From: Vivian Zhang Date: Wed, 27 Aug 2025 10:15:31 -0700 Subject: [PATCH 4/4] Fix spotless apply --- .../kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt | 8 ++++---- .../main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt b/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt index dd225c0..57d0424 100644 --- a/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt +++ b/gradle-module-plugin/src/functionalTest/kotlin/io/ia/sdk/gradle/modl/task/ZipModuleTests.kt @@ -6,13 +6,13 @@ import io.ia.sdk.gradle.modl.util.unsignedModuleName import org.gradle.api.Project import org.gradle.api.internal.project.DefaultProject import org.gradle.testfixtures.ProjectBuilder +import java.io.File import kotlin.io.path.createTempDirectory -import kotlin.test.BeforeTest import kotlin.test.AfterTest +import kotlin.test.BeforeTest import kotlin.test.Test -import kotlin.test.assertTrue import kotlin.test.assertFailsWith -import java.io.File +import kotlin.test.assertTrue class ZipModuleTests : BaseTest() { @@ -55,7 +55,7 @@ class ZipModuleTests : BaseTest() { File(contentDir, "my-lib-2.0.jar").createNewFile() // This is the duplicate // Act & Assert: The task should throw a IllegalArgumentException - val exception = assertFailsWith{ + val exception = assertFailsWith { task.execute() } diff --git a/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt b/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt index 7d16824..a395276 100644 --- a/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt +++ b/gradle-module-plugin/src/main/kotlin/io/ia/sdk/gradle/modl/task/ZipModule.kt @@ -74,7 +74,8 @@ open class ZipModule @Inject constructor(objects: ObjectFactory) : DefaultTask() if (fileSet.contains(name)) { throw IllegalArgumentException( - "Library '$name' exists in multiple versions in ${contentDir.absolutePath}") + "Library '$name' exists in multiple versions in ${contentDir.absolutePath}" + ) } else { fileSet.add(name) }