From 1611e458bd24f4b7a79ca38870d555fec6587af0 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Mon, 17 Nov 2025 09:54:38 +0100 Subject: [PATCH 01/14] `PropertiesFileTransformer` - add `MergeStrategy.Fail` --- .../shadow/transformers/PropertiesFileTransformer.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt index aed3c054a..553674953 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt @@ -11,6 +11,7 @@ import java.nio.charset.Charset import java.util.Properties import javax.inject.Inject import org.apache.tools.zip.ZipOutputStream +import org.gradle.api.GradleException import org.gradle.api.file.FileTreeElement import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.MapProperty @@ -62,6 +63,9 @@ import org.gradle.api.tasks.Internal * - key2 = value2;balue2 * - key3 = value3 * + * With `mergeStrategy = MergeStrategy.Fail` the transformation will fail if there + * are conflicting values. + * * There are three additional properties that can be set: [paths], [mappings], * and [keyTransformer]. * The first contains a list of strings or regexes that will be used to determine if @@ -103,6 +107,8 @@ public open class PropertiesFileTransformer @Inject constructor( ) : ResourceTransformer { private inline val charset get() = Charset.forName(charsetName.get()) + private val conflicts: MutableList = mutableListOf() + @get:Internal internal val propertiesEntries = mutableMapOf() @@ -156,6 +162,7 @@ public open class PropertiesFileTransformer @Inject constructor( props[key] = props.getProperty(key as String) + mergeSeparatorFor(context.path) + value } MergeStrategy.First -> Unit + MergeStrategy.Fail -> conflicts.add("${context.path}: Property $key in is duplicated in another resource and merge strategy is set to fail") } } else { props[key] = value @@ -215,6 +222,10 @@ public open class PropertiesFileTransformer @Inject constructor( override fun hasTransformedResource(): Boolean = propertiesEntries.isNotEmpty() override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) { + if (conflicts.isNotEmpty()) { + throw GradleException("The following properties files have conflicting property values and cannot be merged: ${conflicts.joinToString(separator = "\n * ", prefix = "\n * ")}") + } + // Cannot close the writer as the OutputStream needs to remain open. val zipWriter = os.writer(charset) propertiesEntries.forEach { (path, props) -> @@ -231,6 +242,7 @@ public open class PropertiesFileTransformer @Inject constructor( First, Latest, Append, + Fail, ; public companion object { From 3879b4e04ce27e0a45c9ccf4bd866b012a013f42 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Mon, 17 Nov 2025 12:17:53 +0100 Subject: [PATCH 02/14] abi + tests + missing annotation --- api/shadow.api | 1 + .../PropertiesFileTransformerTest.kt | 34 ++++++++++++++----- .../transformers/PropertiesFileTransformer.kt | 5 +-- .../PropertiesFileTransformerTest.kt | 18 ++++++++++ 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/api/shadow.api b/api/shadow.api index e6154e320..52c6fdbd0 100644 --- a/api/shadow.api +++ b/api/shadow.api @@ -434,6 +434,7 @@ public class com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesF public final class com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer$MergeStrategy : java/lang/Enum { public static final field Append Lcom/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer$MergeStrategy; public static final field Companion Lcom/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer$MergeStrategy$Companion; + public static final field Fail Lcom/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer$MergeStrategy; public static final field First Lcom/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer$MergeStrategy; public static final field Latest Lcom/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer$MergeStrategy; public static final fun from (Ljava/lang/String;)Lcom/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer$MergeStrategy; diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt index b12c96175..c265cd2e3 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt @@ -3,10 +3,14 @@ package com.github.jengelman.gradle.plugins.shadow.transformers import assertk.assertThat import assertk.assertions.contains import assertk.assertions.isEqualTo +import assertk.assertions.isSameAs +import assertk.assertions.isSameInstanceAs import com.github.jengelman.gradle.plugins.shadow.testkit.getContent import com.github.jengelman.gradle.plugins.shadow.transformers.PropertiesFileTransformer.MergeStrategy import com.github.jengelman.gradle.plugins.shadow.util.Issue import kotlin.io.path.appendText +import org.gradle.testkit.runner.TaskOutcome +import org.junit.jupiter.api.Assertions.fail import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.EnumSource @@ -33,15 +37,29 @@ class PropertiesFileTransformerTest : BaseTransformerTest() { ), ) - runWithSuccess(shadowJarPath) - - val expected = when (strategy) { - MergeStrategy.First -> arrayOf("key1=one", "key2=one", "key3=two") - MergeStrategy.Latest -> arrayOf("key1=one", "key2=two", "key3=two") - MergeStrategy.Append -> arrayOf("key1=one", "key2=one;two", "key3=two") + if (strategy == MergeStrategy.Fail) { + val run = runWithFailure(shadowJarPath) + val taskOutcome = run.task(":shadowJar")!! + assertThat(taskOutcome.outcome).isSameInstanceAs(TaskOutcome.FAILED) + assertThat(run.output).contains( + """ + Execution failed for task ':shadowJar'. + > The following properties files have conflicting property values and cannot be merged: + * META-INF/test.properties: Property key2 in is duplicated in another resource and merge strategy is set to fail + """.trimIndent(), + ) + } else { + runWithSuccess(shadowJarPath) + + val expected = when (strategy) { + MergeStrategy.First -> arrayOf("key1=one", "key2=one", "key3=two") + MergeStrategy.Latest -> arrayOf("key1=one", "key2=two", "key3=two") + MergeStrategy.Append -> arrayOf("key1=one", "key2=one;two", "key3=two") + else -> fail("Unexpected strategy: $strategy") + } + val content = outputShadowedJar.use { it.getContent("META-INF/test.properties") } + assertThat(content).contains(*expected) } - val content = outputShadowedJar.use { it.getContent("META-INF/test.properties") } - assertThat(content).contains(*expected) } @Test diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt index 553674953..891ee7470 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt @@ -107,7 +107,8 @@ public open class PropertiesFileTransformer @Inject constructor( ) : ResourceTransformer { private inline val charset get() = Charset.forName(charsetName.get()) - private val conflicts: MutableList = mutableListOf() + @get:Internal + internal val conflicts: MutableList = mutableListOf() @get:Internal internal val propertiesEntries = mutableMapOf() @@ -223,7 +224,7 @@ public open class PropertiesFileTransformer @Inject constructor( override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) { if (conflicts.isNotEmpty()) { - throw GradleException("The following properties files have conflicting property values and cannot be merged: ${conflicts.joinToString(separator = "\n * ", prefix = "\n * ")}") + throw GradleException("The following properties files have conflicting property values and cannot be merged:${conflicts.joinToString(separator = "\n * ", prefix = "\n * ")}") } // Cannot close the writer as the OutputStream needs to remain open. diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt index a51818829..0e410bfba 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt @@ -2,6 +2,7 @@ package com.github.jengelman.gradle.plugins.shadow.transformers import assertk.assertThat import assertk.assertions.contains +import assertk.assertions.containsExactly import assertk.assertions.isEqualTo import assertk.assertions.isFalse import assertk.assertions.isNotEmpty @@ -64,6 +65,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest, input2: Map, expectedOutput: Map, + expectedConflicts: List, ) { transformer.mergeStrategy.set(MergeStrategy.from(mergeStrategy)) transformer.mergeSeparator.set(mergeSeparator) @@ -74,6 +76,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest(), ), Arguments.of( "f.properties", @@ -271,6 +275,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest(), ), Arguments.of( "f.properties", @@ -279,6 +284,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest(), ), Arguments.of( "f.properties", @@ -287,6 +293,18 @@ class PropertiesFileTransformerTest : BaseTransformerTest(), + ), + Arguments.of( + "f.properties", + "fail", + ";", + mapOf("foo" to "foo"), + mapOf("foo" to "bar"), + mapOf("foo" to "foo"), + listOf( + "f.properties: Property foo in is duplicated in another resource and merge strategy is set to fail", + ), ), ) From 461a65a1ab1334df79478495b6bbb98ead55c87a Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Mon, 17 Nov 2025 12:43:17 +0100 Subject: [PATCH 03/14] changes --- docs/changes/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changes/README.md b/docs/changes/README.md index 8113e7a5f..398d0ba29 100644 --- a/docs/changes/README.md +++ b/docs/changes/README.md @@ -11,6 +11,7 @@ - Expose `patternSet` of `ApacheNoticeResourceTransformer` as `public`. ([#1850](https://github.com/GradleUp/shadow/pull/1850)) - Expose `patternSet` of `PreserveFirstFoundResourceTransformer` as `public`. ([#1855](https://github.com/GradleUp/shadow/pull/1855)) - Support overriding output path of `ApacheNoticeResourceTransformer`. ([#1851](https://github.com/GradleUp/shadow/pull/1851)) +- Add new merge strategy 'fail' to `PropertiesFileTransformer`. ([#1856](https://github.com/GradleUp/shadow/pull/1856)) ### Changed From ec45a056ad1d0d0981778aac56a50c7e24182893 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Mon, 17 Nov 2025 13:32:23 +0100 Subject: [PATCH 04/14] error message improvement --- .../PropertiesFileTransformerTest.kt | 3 ++- .../transformers/PropertiesFileTransformer.kt | 13 ++++++++++--- .../PropertiesFileTransformerTest.kt | 17 +++++++---------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt index c265cd2e3..8cc0fb24b 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt @@ -45,7 +45,8 @@ class PropertiesFileTransformerTest : BaseTransformerTest() { """ Execution failed for task ':shadowJar'. > The following properties files have conflicting property values and cannot be merged: - * META-INF/test.properties: Property key2 in is duplicated in another resource and merge strategy is set to fail + * META-INF/test.properties + * Property key2 is duplicated 2 times with different values """.trimIndent(), ) } else { diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt index 891ee7470..032bf7fb5 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt @@ -108,7 +108,7 @@ public open class PropertiesFileTransformer @Inject constructor( private inline val charset get() = Charset.forName(charsetName.get()) @get:Internal - internal val conflicts: MutableList = mutableListOf() + internal val conflicts: MutableMap> = mutableMapOf() @get:Internal internal val propertiesEntries = mutableMapOf() @@ -163,7 +163,10 @@ public open class PropertiesFileTransformer @Inject constructor( props[key] = props.getProperty(key as String) + mergeSeparatorFor(context.path) + value } MergeStrategy.First -> Unit - MergeStrategy.Fail -> conflicts.add("${context.path}: Property $key in is duplicated in another resource and merge strategy is set to fail") + MergeStrategy.Fail -> { + val conflictsForPath: MutableMap = conflicts.computeIfAbsent(context.path) { mutableMapOf() } + conflictsForPath.compute(key as String) { _, count -> (count ?: 1) + 1 } + } } } else { props[key] = value @@ -224,7 +227,11 @@ public open class PropertiesFileTransformer @Inject constructor( override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) { if (conflicts.isNotEmpty()) { - throw GradleException("The following properties files have conflicting property values and cannot be merged:${conflicts.joinToString(separator = "\n * ", prefix = "\n * ")}") + throw GradleException( + "The following properties files have conflicting property values and cannot be merged:${conflicts + .map { (path, conflicts) -> "$path${conflicts.map { "Property ${it.key} is duplicated ${it.value} times with different values" }.joinToString(separator = "\n * ", prefix = "\n * ")}" } + .joinToString(separator = "\n * ", prefix = "\n * ")}", + ) } // Cannot close the writer as the OutputStream needs to remain open. diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt index 0e410bfba..ce8daec1f 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt @@ -2,7 +2,6 @@ package com.github.jengelman.gradle.plugins.shadow.transformers import assertk.assertThat import assertk.assertions.contains -import assertk.assertions.containsExactly import assertk.assertions.isEqualTo import assertk.assertions.isFalse import assertk.assertions.isNotEmpty @@ -65,7 +64,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest, input2: Map, expectedOutput: Map, - expectedConflicts: List, + expectedConflicts: Map>, ) { transformer.mergeStrategy.set(MergeStrategy.from(mergeStrategy)) transformer.mergeSeparator.set(mergeSeparator) @@ -76,7 +75,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest(), + mapOf>(), ), Arguments.of( "f.properties", @@ -275,7 +274,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest(), + mapOf>(), ), Arguments.of( "f.properties", @@ -284,7 +283,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest(), + mapOf>(), ), Arguments.of( "f.properties", @@ -293,7 +292,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest(), + mapOf>(), ), Arguments.of( "f.properties", @@ -302,9 +301,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest( - "f.properties: Property foo in is duplicated in another resource and merge strategy is set to fail", - ), + mapOf("f.properties" to mapOf("foo" to 2)), ), ) From 90f2e0b363a6e0efc0ffa4ec110911533e1e1556 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Mon, 17 Nov 2025 13:51:38 +0100 Subject: [PATCH 05/14] windows happy now? --- .../shadow/transformers/PropertiesFileTransformerTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt index 8cc0fb24b..98f0f73e5 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt @@ -47,7 +47,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest() { > The following properties files have conflicting property values and cannot be merged: * META-INF/test.properties * Property key2 is duplicated 2 times with different values - """.trimIndent(), + """.trimIndent().replace("\n", System.lineSeparator()), ) } else { runWithSuccess(shadowJarPath) From c6590604945413bc830e3597e9554344a332dc0e Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Mon, 17 Nov 2025 14:33:32 +0100 Subject: [PATCH 06/14] maybe now?? --- .../PropertiesFileTransformerTest.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt index 98f0f73e5..abe6ec173 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt @@ -2,8 +2,8 @@ package com.github.jengelman.gradle.plugins.shadow.transformers import assertk.assertThat import assertk.assertions.contains +import assertk.assertions.containsSubList import assertk.assertions.isEqualTo -import assertk.assertions.isSameAs import assertk.assertions.isSameInstanceAs import com.github.jengelman.gradle.plugins.shadow.testkit.getContent import com.github.jengelman.gradle.plugins.shadow.transformers.PropertiesFileTransformer.MergeStrategy @@ -41,13 +41,14 @@ class PropertiesFileTransformerTest : BaseTransformerTest() { val run = runWithFailure(shadowJarPath) val taskOutcome = run.task(":shadowJar")!! assertThat(taskOutcome.outcome).isSameInstanceAs(TaskOutcome.FAILED) - assertThat(run.output).contains( - """ - Execution failed for task ':shadowJar'. - > The following properties files have conflicting property values and cannot be merged: - * META-INF/test.properties - * Property key2 is duplicated 2 times with different values - """.trimIndent().replace("\n", System.lineSeparator()), + assertThat(run.output.lines()).containsSubList( + listOf( + // Keep this list approach for Unix/Windows test compatibility. + "Execution failed for task ':shadowJar'.", + "> The following properties files have conflicting property values and cannot be merged:", + " * META-INF/test.properties", + " * Property key2 is duplicated 2 times with different values", + ), ) } else { runWithSuccess(shadowJarPath) From 2e8a7b0266bb182570a5a9cb4a657f4bf30acf49 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Tue, 18 Nov 2025 08:10:00 +0100 Subject: [PATCH 07/14] Update docs/changes/README.md Co-authored-by: Zongle Wang --- docs/changes/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changes/README.md b/docs/changes/README.md index 398d0ba29..7bf834a6e 100644 --- a/docs/changes/README.md +++ b/docs/changes/README.md @@ -11,7 +11,7 @@ - Expose `patternSet` of `ApacheNoticeResourceTransformer` as `public`. ([#1850](https://github.com/GradleUp/shadow/pull/1850)) - Expose `patternSet` of `PreserveFirstFoundResourceTransformer` as `public`. ([#1855](https://github.com/GradleUp/shadow/pull/1855)) - Support overriding output path of `ApacheNoticeResourceTransformer`. ([#1851](https://github.com/GradleUp/shadow/pull/1851)) -- Add new merge strategy 'fail' to `PropertiesFileTransformer`. ([#1856](https://github.com/GradleUp/shadow/pull/1856)) +- Add new merge strategy 'Fail' to `PropertiesFileTransformer`. ([#1856](https://github.com/GradleUp/shadow/pull/1856)) ### Changed From 16b62db7fee4354d68a0ca13140c368410ae80eb Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Tue, 18 Nov 2025 08:21:19 +0100 Subject: [PATCH 08/14] review --- .../shadow/transformers/PropertiesFileTransformerTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt index abe6ec173..3fd026c08 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt @@ -5,6 +5,7 @@ import assertk.assertions.contains import assertk.assertions.containsSubList import assertk.assertions.isEqualTo import assertk.assertions.isSameInstanceAs +import com.github.jengelman.gradle.plugins.shadow.BasePluginTest.Companion.taskOutcomeEquals import com.github.jengelman.gradle.plugins.shadow.testkit.getContent import com.github.jengelman.gradle.plugins.shadow.transformers.PropertiesFileTransformer.MergeStrategy import com.github.jengelman.gradle.plugins.shadow.util.Issue @@ -39,8 +40,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest() { if (strategy == MergeStrategy.Fail) { val run = runWithFailure(shadowJarPath) - val taskOutcome = run.task(":shadowJar")!! - assertThat(taskOutcome.outcome).isSameInstanceAs(TaskOutcome.FAILED) + assertThat(run).taskOutcomeEquals(shadowJarPath, TaskOutcome.FAILED) assertThat(run.output.lines()).containsSubList( listOf( // Keep this list approach for Unix/Windows test compatibility. From d4bbac722cdda5a8c48c4921aa8899fb7564fa99 Mon Sep 17 00:00:00 2001 From: Zongle Wang Date: Tue, 18 Nov 2025 15:29:12 +0800 Subject: [PATCH 09/14] Apply suggestion from @Goooler --- docs/changes/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changes/README.md b/docs/changes/README.md index 7bf834a6e..7fa5965ee 100644 --- a/docs/changes/README.md +++ b/docs/changes/README.md @@ -11,7 +11,7 @@ - Expose `patternSet` of `ApacheNoticeResourceTransformer` as `public`. ([#1850](https://github.com/GradleUp/shadow/pull/1850)) - Expose `patternSet` of `PreserveFirstFoundResourceTransformer` as `public`. ([#1855](https://github.com/GradleUp/shadow/pull/1855)) - Support overriding output path of `ApacheNoticeResourceTransformer`. ([#1851](https://github.com/GradleUp/shadow/pull/1851)) -- Add new merge strategy 'Fail' to `PropertiesFileTransformer`. ([#1856](https://github.com/GradleUp/shadow/pull/1856)) +- Add new merge strategy `Fail` to `PropertiesFileTransformer`. ([#1856](https://github.com/GradleUp/shadow/pull/1856)) ### Changed From 8a6e5c909acc9ff567314cf97f4712aff2f92ab0 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Tue, 18 Nov 2025 08:31:04 +0100 Subject: [PATCH 10/14] Windows experiment --- .../PropertiesFileTransformerTest.kt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt index 3fd026c08..0e1a5e206 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt @@ -64,6 +64,53 @@ class PropertiesFileTransformerTest : BaseTransformerTest() { } } + @ParameterizedTest + @EnumSource(MergeStrategy::class) + fun mergePropertiesWithDifferentStrategiesEXPERIMENT(strategy: MergeStrategy) { + val one = buildJarOne { + insert("META-INF/test.properties", "key1=one\nkey2=one") + } + val two = buildJarTwo { + insert("META-INF/test.properties", "key2=two\nkey3=two") + } + projectScript.appendText( + transform( + dependenciesBlock = implementationFiles(one, two), + transformerBlock = """ + mergeStrategy = $mergeStrategyClassName.$strategy + mergeSeparator = ";" + paths = ["META-INF/test.properties"] + """.trimIndent(), + ), + ) + + if (strategy == MergeStrategy.Fail) { + val run = runWithFailure(shadowJarPath) + assertThat(run).taskOutcomeEquals(shadowJarPath, TaskOutcome.FAILED) + assertThat(run.output.lines()).contains( + + // TODO EXPERIMENT + """ + Execution failed for task ':shadowJar'. + > The following properties files have conflicting property values and cannot be merged: + * META-INF/test.properties + * Property key2 is duplicated 2 times with different values + """.trimIndent().replace("\n", System.lineSeparator()), + ) + } else { + runWithSuccess(shadowJarPath) + + val expected = when (strategy) { + MergeStrategy.First -> arrayOf("key1=one", "key2=one", "key3=two") + MergeStrategy.Latest -> arrayOf("key1=one", "key2=two", "key3=two") + MergeStrategy.Append -> arrayOf("key1=one", "key2=one;two", "key3=two") + else -> fail("Unexpected strategy: $strategy") + } + val content = outputShadowedJar.use { it.getContent("META-INF/test.properties") } + assertThat(content).contains(*expected) + } + } + @Test fun mergePropertiesWithKeyTransformer() { val one = buildJarOne { From 7647370f2b6d27c080597aec9df38399269b523d Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 18 Nov 2025 15:34:12 +0800 Subject: [PATCH 11/14] Update `mergePropertiesWithDifferentStrategies` --- .../PropertiesFileTransformerTest.kt | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt index 0e1a5e206..e14a77fc6 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt @@ -2,10 +2,7 @@ package com.github.jengelman.gradle.plugins.shadow.transformers import assertk.assertThat import assertk.assertions.contains -import assertk.assertions.containsSubList import assertk.assertions.isEqualTo -import assertk.assertions.isSameInstanceAs -import com.github.jengelman.gradle.plugins.shadow.BasePluginTest.Companion.taskOutcomeEquals import com.github.jengelman.gradle.plugins.shadow.testkit.getContent import com.github.jengelman.gradle.plugins.shadow.transformers.PropertiesFileTransformer.MergeStrategy import com.github.jengelman.gradle.plugins.shadow.util.Issue @@ -39,16 +36,14 @@ class PropertiesFileTransformerTest : BaseTransformerTest() { ) if (strategy == MergeStrategy.Fail) { - val run = runWithFailure(shadowJarPath) - assertThat(run).taskOutcomeEquals(shadowJarPath, TaskOutcome.FAILED) - assertThat(run.output.lines()).containsSubList( - listOf( - // Keep this list approach for Unix/Windows test compatibility. - "Execution failed for task ':shadowJar'.", - "> The following properties files have conflicting property values and cannot be merged:", - " * META-INF/test.properties", - " * Property key2 is duplicated 2 times with different values", - ), + val result = runWithFailure(shadowJarPath) + + assertThat(result).taskOutcomeEquals(shadowJarPath, TaskOutcome.FAILED) + assertThat(result.output).contains( + "Execution failed for task ':shadowJar'.", + "> The following properties files have conflicting property values and cannot be merged:", + " * META-INF/test.properties", + " * Property key2 is duplicated 2 times with different values", ) } else { runWithSuccess(shadowJarPath) From ee5a28639911eafedba9fd902bf24ad154189caf Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Tue, 18 Nov 2025 08:47:55 +0100 Subject: [PATCH 12/14] oops --- .../shadow/transformers/PropertiesFileTransformerTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt index e14a77fc6..e4a6b9267 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt @@ -80,9 +80,9 @@ class PropertiesFileTransformerTest : BaseTransformerTest() { ) if (strategy == MergeStrategy.Fail) { - val run = runWithFailure(shadowJarPath) - assertThat(run).taskOutcomeEquals(shadowJarPath, TaskOutcome.FAILED) - assertThat(run.output.lines()).contains( + val result = runWithFailure(shadowJarPath) + assertThat(result).taskOutcomeEquals(shadowJarPath, TaskOutcome.FAILED) + assertThat(result.output).contains( // TODO EXPERIMENT """ From dc2f0354d941a6602c0edece59f58970121fb811 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 18 Nov 2025 15:49:03 +0800 Subject: [PATCH 13/14] Cleanups --- .../PropertiesFileTransformerTest.kt | 8 ++++---- .../transformers/PropertiesFileTransformer.kt | 17 ++++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt index e4a6b9267..d9cf4d40a 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt @@ -7,7 +7,7 @@ import com.github.jengelman.gradle.plugins.shadow.testkit.getContent import com.github.jengelman.gradle.plugins.shadow.transformers.PropertiesFileTransformer.MergeStrategy import com.github.jengelman.gradle.plugins.shadow.util.Issue import kotlin.io.path.appendText -import org.gradle.testkit.runner.TaskOutcome +import org.gradle.testkit.runner.TaskOutcome.FAILED import org.junit.jupiter.api.Assertions.fail import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest @@ -38,7 +38,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest() { if (strategy == MergeStrategy.Fail) { val result = runWithFailure(shadowJarPath) - assertThat(result).taskOutcomeEquals(shadowJarPath, TaskOutcome.FAILED) + assertThat(result).taskOutcomeEquals(shadowJarPath, FAILED) assertThat(result.output).contains( "Execution failed for task ':shadowJar'.", "> The following properties files have conflicting property values and cannot be merged:", @@ -81,9 +81,9 @@ class PropertiesFileTransformerTest : BaseTransformerTest() { if (strategy == MergeStrategy.Fail) { val result = runWithFailure(shadowJarPath) - assertThat(result).taskOutcomeEquals(shadowJarPath, TaskOutcome.FAILED) - assertThat(result.output).contains( + assertThat(result).taskOutcomeEquals(shadowJarPath, FAILED) + assertThat(result.output).contains( // TODO EXPERIMENT """ Execution failed for task ':shadowJar'. diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt index 032bf7fb5..d938cdb1a 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt @@ -11,7 +11,6 @@ import java.nio.charset.Charset import java.util.Properties import javax.inject.Inject import org.apache.tools.zip.ZipOutputStream -import org.gradle.api.GradleException import org.gradle.api.file.FileTreeElement import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.MapProperty @@ -63,8 +62,7 @@ import org.gradle.api.tasks.Internal * - key2 = value2;balue2 * - key3 = value3 * - * With `mergeStrategy = MergeStrategy.Fail` the transformation will fail if there - * are conflicting values. + * With `mergeStrategy = MergeStrategy.Fail` the transformation will fail if there are conflicting values. * * There are three additional properties that can be set: [paths], [mappings], * and [keyTransformer]. @@ -164,7 +162,7 @@ public open class PropertiesFileTransformer @Inject constructor( } MergeStrategy.First -> Unit MergeStrategy.Fail -> { - val conflictsForPath: MutableMap = conflicts.computeIfAbsent(context.path) { mutableMapOf() } + val conflictsForPath = conflicts.computeIfAbsent(context.path) { mutableMapOf() } conflictsForPath.compute(key as String) { _, count -> (count ?: 1) + 1 } } } @@ -227,11 +225,12 @@ public open class PropertiesFileTransformer @Inject constructor( override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) { if (conflicts.isNotEmpty()) { - throw GradleException( - "The following properties files have conflicting property values and cannot be merged:${conflicts - .map { (path, conflicts) -> "$path${conflicts.map { "Property ${it.key} is duplicated ${it.value} times with different values" }.joinToString(separator = "\n * ", prefix = "\n * ")}" } - .joinToString(separator = "\n * ", prefix = "\n * ")}", - ) + val message = "The following properties files have conflicting property values and cannot be merged:" + + conflicts.map { (path, props) -> + path + props.map { "Property ${it.key} is duplicated ${it.value} times with different values" } + .joinToString(separator = "\n * ", prefix = "\n * ") + }.joinToString(separator = "\n * ", prefix = "\n * ") + error(message) } // Cannot close the writer as the OutputStream needs to remain open. From b483f1bdc4330e4e8733649675ae1df1e903739c Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 18 Nov 2025 16:14:24 +0800 Subject: [PATCH 14/14] Remove `mergePropertiesWithDifferentStrategiesEXPERIMENT` --- .../PropertiesFileTransformerTest.kt | 56 ++----------------- 1 file changed, 5 insertions(+), 51 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt index d9cf4d40a..7ea154362 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt @@ -39,58 +39,12 @@ class PropertiesFileTransformerTest : BaseTransformerTest() { val result = runWithFailure(shadowJarPath) assertThat(result).taskOutcomeEquals(shadowJarPath, FAILED) - assertThat(result.output).contains( - "Execution failed for task ':shadowJar'.", - "> The following properties files have conflicting property values and cannot be merged:", - " * META-INF/test.properties", - " * Property key2 is duplicated 2 times with different values", - ) - } else { - runWithSuccess(shadowJarPath) - - val expected = when (strategy) { - MergeStrategy.First -> arrayOf("key1=one", "key2=one", "key3=two") - MergeStrategy.Latest -> arrayOf("key1=one", "key2=two", "key3=two") - MergeStrategy.Append -> arrayOf("key1=one", "key2=one;two", "key3=two") - else -> fail("Unexpected strategy: $strategy") - } - val content = outputShadowedJar.use { it.getContent("META-INF/test.properties") } - assertThat(content).contains(*expected) - } - } - - @ParameterizedTest - @EnumSource(MergeStrategy::class) - fun mergePropertiesWithDifferentStrategiesEXPERIMENT(strategy: MergeStrategy) { - val one = buildJarOne { - insert("META-INF/test.properties", "key1=one\nkey2=one") - } - val two = buildJarTwo { - insert("META-INF/test.properties", "key2=two\nkey3=two") - } - projectScript.appendText( - transform( - dependenciesBlock = implementationFiles(one, two), - transformerBlock = """ - mergeStrategy = $mergeStrategyClassName.$strategy - mergeSeparator = ";" - paths = ["META-INF/test.properties"] - """.trimIndent(), - ), - ) - - if (strategy == MergeStrategy.Fail) { - val result = runWithFailure(shadowJarPath) - - assertThat(result).taskOutcomeEquals(shadowJarPath, FAILED) - assertThat(result.output).contains( - // TODO EXPERIMENT + assertThat(result.output.replace(lineSeparator, "\n")).contains( """ - Execution failed for task ':shadowJar'. - > The following properties files have conflicting property values and cannot be merged: - * META-INF/test.properties - * Property key2 is duplicated 2 times with different values - """.trimIndent().replace("\n", System.lineSeparator()), + Caused by: java.lang.IllegalStateException: The following properties files have conflicting property values and cannot be merged: + * META-INF/test.properties + * Property key2 is duplicated 2 times with different values + """.trimIndent(), ) } else { runWithSuccess(shadowJarPath)