From 99594169ae2c1afd4399e5a3861a3d8e91fc64c3 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 16:19:19 +0800 Subject: [PATCH 01/19] Test `mergePropertiesWithDifferentStrategies` for `PropertiesFileTransformer` --- .../plugins/shadow/TransformerSpec.groovy | 84 +++++++++++++++---- 1 file changed, 70 insertions(+), 14 deletions(-) diff --git a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/TransformerSpec.groovy b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/TransformerSpec.groovy index 2835108e8..3972d51f6 100644 --- a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/TransformerSpec.groovy +++ b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/TransformerSpec.groovy @@ -2,6 +2,7 @@ package com.github.jengelman.gradle.plugins.shadow import com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.GroovyExtensionModuleTransformer +import com.github.jengelman.gradle.plugins.shadow.transformers.PropertiesFileTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.XmlAppendingTransformer import com.github.jengelman.gradle.plugins.shadow.util.PluginSpecification @@ -251,7 +252,7 @@ two # NOTE: No newline terminates this line/file'''.stripIndent() implementation 'shadow:two:1.0' implementation files('${escapedPath(one)}') } - + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { mergeServiceFiles() } @@ -349,9 +350,9 @@ two # NOTE: No newline terminates this line/file File main = file('src/main/java/shadow/Main.java') main << ''' package shadow; - + public class Main { - + public static void main(String[] args) { } } '''.stripIndent() @@ -386,9 +387,9 @@ two # NOTE: No newline terminates this line/file File main = file('src/main/java/shadow/Main.java') main << ''' package shadow; - + public class Main { - + public static void main(String[] args) { } } '''.stripIndent() @@ -400,7 +401,7 @@ two # NOTE: No newline terminates this line/file attributes 'Test-Entry': 'FAILED' } } - + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { manifest { attributes 'Test-Entry': 'PASSED' @@ -430,7 +431,7 @@ two # NOTE: No newline terminates this line/file given: File xml1 = buildJar('xml1.jar').insertFile('properties.xml', ''' - + val1 @@ -439,7 +440,7 @@ two # NOTE: No newline terminates this line/file File xml2 = buildJar('xml2.jar').insertFile('properties.xml', ''' - + val2 @@ -485,9 +486,9 @@ two # NOTE: No newline terminates this line/file File main = file('src/main/java/shadow/Main.java') main << ''' package shadow; - + public class Main { - + public static void main(String[] args) { } } '''.stripIndent() @@ -499,7 +500,7 @@ two # NOTE: No newline terminates this line/file attributes 'Test-Entry': 'FAILED' } } - + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { manifest { attributes 'Test-Entry': 'PASSED' @@ -545,9 +546,9 @@ two # NOTE: No newline terminates this line/file File main = file('src/main/java/shadow/Main.java') main << ''' package shadow; - + public class Main { - + public static void main(String[] args) { } } '''.stripIndent() @@ -559,7 +560,7 @@ two # NOTE: No newline terminates this line/file attributes 'Test-Entry': 'FAILED' } } - + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { manifest { attributes 'Test-Entry': 'PASSED' @@ -767,6 +768,61 @@ staticExtensionClasses=com.acme.bar.SomeStaticExtension'''.stripIndent()).write( 'XmlAppendingTransformer' | '' } + @Unroll + def 'merge properties with different strategies: #mergeStrategy'() { + given: + File one = buildJar('one.jar') + .insertFile('test.properties', + 'key1=val1\nkey2=val2\nkey3=val3').write() + + File two = buildJar('two.jar') + .insertFile('test.properties', + 'key1=VAL1\nkey2=VAL2\nkey4=val4').write() + + buildFile << """ + import ${PropertiesFileTransformer.name} + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + transform(PropertiesFileTransformer) { + paths = ['test.properties'] + mergeStrategy = '${mergeStrategy}' + } + } + """.stripIndent() + + when: + run('shadowJar') + + then: + assert output.exists() + + and: + String text = getJarFileContents(output, 'test.properties') + def lines = text.replace('#', '').trim().split("\\r?\\n").toList() + switch (mergeStrategy) { + case 'first': + assert lines.size() == 4 + assert lines.containsAll(['key1=val1', 'key2=val2', 'key3=val3', 'key4=val4']) + break + case 'latest': + assert lines.size() == 4 + assert lines.containsAll(['key1=VAL1', 'key2=VAL2', 'key3=val3', 'key4=val4']) + break + case 'append': + assert lines.size() == 4 + assert lines.containsAll(['key1=val1,VAL1', 'key2=val2,VAL2', 'key3=val3', 'key4=val4']) + break + default: + assert false : "Unknown mergeStrategy: $mergeStrategy" + } + + where: + mergeStrategy << ['first', 'latest', 'append'] + } + private String escapedPath(File file) { file.path.replaceAll('\\\\', '\\\\\\\\') } From b4e582c24e819fc3fad8915ec416902290e1c84a Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 16:22:46 +0800 Subject: [PATCH 02/19] Add 8.14.2 into test matrix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09a95beb4..fa77f708f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: # Always test on the latest version and some LTS. java: [ 17, 21, 24 ] # Test on the minimum Gradle version and the latest. - gradle: [ 8.3, current ] + gradle: [ 8.3, 8.14.2, current ] exclude: # Gradle 8.3 doesn't support Java 24. - gradle: 8.3 From 649a960956992c570ad70305df793d77cb45d742 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 16:36:16 +0800 Subject: [PATCH 03/19] Build on Gradle 8.14.2 but test on 9.0.0-rc-1 --- .github/workflows/ci.yml | 2 +- build.gradle.kts | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa77f708f..f50223a9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: # Always test on the latest version and some LTS. java: [ 17, 21, 24 ] # Test on the minimum Gradle version and the latest. - gradle: [ 8.3, 8.14.2, current ] + gradle: [ 8.3, current, 9.0.0-rc-1 ] exclude: # Gradle 8.3 doesn't support Java 24. - gradle: 8.3 diff --git a/build.gradle.kts b/build.gradle.kts index 2b0380c73..39d1ff205 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -85,7 +85,7 @@ dependencies { implementation("org.apache.logging.log4j:log4j-core:2.24.1") implementation("org.vafer:jdependency:2.13") - testImplementation("org.spockframework:spock-core:2.3-groovy-4.0") { + testImplementation("org.spockframework:spock-core:2.3-groovy-3.0") { exclude(group = "org.codehaus.groovy") exclude(group = "org.hamcrest") } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 407c905d9..ff23a68d7 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-9.0.0-rc-1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From c8aee025e3f920dfdb1fdd35bd04e79a32a827f1 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 16:43:20 +0800 Subject: [PATCH 04/19] Revert "Build on Gradle 8.14.2 but test on 9.0.0-rc-1" This reverts commit 649a960956992c570ad70305df793d77cb45d742. --- .github/workflows/ci.yml | 2 +- build.gradle.kts | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f50223a9e..fa77f708f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: # Always test on the latest version and some LTS. java: [ 17, 21, 24 ] # Test on the minimum Gradle version and the latest. - gradle: [ 8.3, current, 9.0.0-rc-1 ] + gradle: [ 8.3, 8.14.2, current ] exclude: # Gradle 8.3 doesn't support Java 24. - gradle: 8.3 diff --git a/build.gradle.kts b/build.gradle.kts index 39d1ff205..2b0380c73 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -85,7 +85,7 @@ dependencies { implementation("org.apache.logging.log4j:log4j-core:2.24.1") implementation("org.vafer:jdependency:2.13") - testImplementation("org.spockframework:spock-core:2.3-groovy-3.0") { + testImplementation("org.spockframework:spock-core:2.3-groovy-4.0") { exclude(group = "org.codehaus.groovy") exclude(group = "org.hamcrest") } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ff23a68d7..407c905d9 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.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-rc-1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 7f65d61fc7e1222f26f5b2bdf788c6c92e025b49 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 16:45:28 +0800 Subject: [PATCH 05/19] Private static `IDENTITY` --- .../transformers/PropertiesFileTransformer.groovy | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy index 044e3dbaa..c644e2336 100644 --- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy +++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy @@ -120,6 +120,17 @@ class PropertiesFileTransformer implements Transformer { private Map propertiesEntries = [:] + /** + * This is a copy of the Closure.IDENTITY from Groovy 4. + */ + private static final Closure IDENTITY = new Closure((Object)null) { + private static final long serialVersionUID = 730973623329943963L + + Object doCall(Object args) { + return args + } + } + @Input List paths = [] From bb6bc847d5137f5c26f2817fcd7566f80573b8b9 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 16:53:42 +0800 Subject: [PATCH 06/19] Revert "Add 8.14.2 into test matrix" This reverts commit b4e582c24e819fc3fad8915ec416902290e1c84a. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa77f708f..09a95beb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: # Always test on the latest version and some LTS. java: [ 17, 21, 24 ] # Test on the minimum Gradle version and the latest. - gradle: [ 8.3, 8.14.2, current ] + gradle: [ 8.3, current ] exclude: # Gradle 8.3 doesn't support Java 24. - gradle: 8.3 From 4d121e8cb0db0e49852de5042c488368ad2e090d Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 17:00:41 +0800 Subject: [PATCH 07/19] Reapply "Add 8.14.2 into test matrix" This reverts commit bb6bc847d5137f5c26f2817fcd7566f80573b8b9. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09a95beb4..fa77f708f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: # Always test on the latest version and some LTS. java: [ 17, 21, 24 ] # Test on the minimum Gradle version and the latest. - gradle: [ 8.3, current ] + gradle: [ 8.3, 8.14.2, current ] exclude: # Gradle 8.3 doesn't support Java 24. - gradle: 8.3 From e27b5b46d36ec3e1bfa4a5d51958bc0467ef50bb Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 17:03:54 +0800 Subject: [PATCH 08/19] Mark transient --- .../transformers/PropertiesFileTransformer.groovy | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy index c644e2336..8baeb614a 100644 --- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy +++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy @@ -120,17 +120,6 @@ class PropertiesFileTransformer implements Transformer { private Map propertiesEntries = [:] - /** - * This is a copy of the Closure.IDENTITY from Groovy 4. - */ - private static final Closure IDENTITY = new Closure((Object)null) { - private static final long serialVersionUID = 730973623329943963L - - Object doCall(Object args) { - return args - } - } - @Input List paths = [] @@ -147,7 +136,7 @@ class PropertiesFileTransformer implements Transformer { String charset = 'ISO_8859_1' @Internal - Closure keyTransformer = IDENTITY + transient Closure keyTransformer = IDENTITY @Override boolean canTransformResource(FileTreeElement element) { @@ -203,7 +192,7 @@ class PropertiesFileTransformer implements Transformer { } private Properties transformKeys(Properties properties) { - if (keyTransformer == IDENTITY) { + if (keyTransformer == IDENTITY || keyTransformer == null) { return properties } def result = new CleanProperties() From 758e900db7e441d223defbccda11b395296cf587 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 17:55:31 +0800 Subject: [PATCH 09/19] Mark `CleanProperties` as `@CompileStatic` --- .../gradle/plugins/shadow/internal/CleanProperties.groovy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/CleanProperties.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/CleanProperties.groovy index b54b5271b..689bac593 100644 --- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/CleanProperties.groovy +++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/CleanProperties.groovy @@ -1,5 +1,8 @@ package com.github.jengelman.gradle.plugins.shadow.internal +import groovy.transform.CompileStatic + +@CompileStatic class CleanProperties extends Properties { private static class StripCommentsWithTimestampBufferedWriter extends BufferedWriter { From b5f2db4627db7d695270b68a5735d952ab5dc395 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 17:58:07 +0800 Subject: [PATCH 10/19] Revert "Reapply "Add 8.14.2 into test matrix"" This reverts commit 4d121e8cb0db0e49852de5042c488368ad2e090d. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa77f708f..09a95beb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: # Always test on the latest version and some LTS. java: [ 17, 21, 24 ] # Test on the minimum Gradle version and the latest. - gradle: [ 8.3, 8.14.2, current ] + gradle: [ 8.3, current ] exclude: # Gradle 8.3 doesn't support Java 24. - gradle: 8.3 From 207a51dfec89ffebe8e1521969ff2b3b2d29217c Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 18:08:09 +0800 Subject: [PATCH 11/19] Update changelog --- src/docs/changes/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/docs/changes/README.md b/src/docs/changes/README.md index 3c0dcabd4..398928e25 100644 --- a/src/docs/changes/README.md +++ b/src/docs/changes/README.md @@ -3,6 +3,10 @@ ## [Unreleased] +**Fixed** + +- Fix the regression of `PropertiesFileTransformer` in `8.3.7`. ([#1493](https://github.com/GradleUp/shadow/pull/1493)) + **Changed** - Expose Ant as `compile` scope. ([#1488](https://github.com/GradleUp/shadow/pull/1488)) From 551f960efcd402f6fa3a2ddf2e39c8a36d1798c7 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 18:20:27 +0800 Subject: [PATCH 12/19] Add `PropertiesFileTransformerSpec` --- .../PropertiesFileTransformerSpec.groovy | 213 ++++++++++++++++++ .../plugins/shadow/ShadowPluginSpec.groovy | 8 +- .../plugins/shadow/TransformerSpec.groovy | 61 ----- .../shadow/caching/AbstractCachingSpec.groovy | 4 - .../shadow/util/PluginSpecification.groovy | 4 + 5 files changed, 219 insertions(+), 71 deletions(-) create mode 100644 src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PropertiesFileTransformerSpec.groovy diff --git a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PropertiesFileTransformerSpec.groovy b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PropertiesFileTransformerSpec.groovy new file mode 100644 index 000000000..c0d0106d1 --- /dev/null +++ b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PropertiesFileTransformerSpec.groovy @@ -0,0 +1,213 @@ +package com.github.jengelman.gradle.plugins.shadow + +import com.github.jengelman.gradle.plugins.shadow.transformers.PropertiesFileTransformer +import com.github.jengelman.gradle.plugins.shadow.util.PluginSpecification +import spock.lang.Issue +import spock.lang.Unroll + +class PropertiesFileTransformerSpec extends PluginSpecification { + + @Unroll + def 'merge properties with different strategies: #mergeStrategy'() { + given: + File one = buildJar('one.jar') + .insertFile('test.properties', + 'key1=val1\nkey2=val2\nkey3=val3').write() + + File two = buildJar('two.jar') + .insertFile('test.properties', + 'key1=VAL1\nkey2=VAL2\nkey4=val4').write() + + buildFile << """ + import ${PropertiesFileTransformer.name} + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + transform(PropertiesFileTransformer) { + paths = ['test.properties'] + mergeStrategy = '${mergeStrategy}' + } + } + """.stripIndent() + + when: + run('shadowJar') + + then: + assert output.exists() + + and: + String text = getJarFileContents(output, 'test.properties') + def lines = text.replace('#', '').trim().split("\\r?\\n").toList() + switch (mergeStrategy) { + case 'first': + assert lines.size() == 4 + assert lines.containsAll(['key1=val1', 'key2=val2', 'key3=val3', 'key4=val4']) + break + case 'latest': + assert lines.size() == 4 + assert lines.containsAll(['key1=VAL1', 'key2=VAL2', 'key3=val3', 'key4=val4']) + break + case 'append': + assert lines.size() == 4 + assert lines.containsAll(['key1=val1,VAL1', 'key2=val2,VAL2', 'key3=val3', 'key4=val4']) + break + default: + assert false : "Unknown mergeStrategy: $mergeStrategy" + } + + where: + mergeStrategy << ['first', 'latest', 'append'] + } + + def 'merge properties with key transformer'() { + given: + File one = buildJar('one.jar') + .insertFile('META-INF/test.properties', 'foo=bar') + .write() + + File two = buildJar('two.jar') + .insertFile('META-INF/test.properties', 'FOO=baz') + .write() + + buildFile << """ + import ${PropertiesFileTransformer.name} + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + transform(PropertiesFileTransformer) { + paths = ['META-INF/test.properties'] + mergeStrategy = 'append' + keyTransformer = { key -> key.toUpperCase() } + } + } + """.stripIndent() + + when: + run('shadowJar') + + then: + output.exists() + String text = getJarFileContents(output, 'META-INF/test.properties') + text.contains('FOO=bar,baz') + } + + def 'merge properties with specified charset'() { + given: + File one = buildJar('one.jar') + .insertFile('META-INF/utf8.properties', 'foo=第一') + .write() + + File two = buildJar('two.jar') + .insertFile('META-INF/utf8.properties', 'foo=第二') + .write() + + buildFile << """ + import ${PropertiesFileTransformer.name} + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + transform(PropertiesFileTransformer) { + paths = ['META-INF/utf8.properties'] + mergeStrategy = 'append' + charset = 'UTF-8' + } + } + """.stripIndent() + + when: + run('shadowJar') + + then: + output.exists() + String text = getJarFileContents(output, 'META-INF/utf8.properties') + text.contains('foo=第一,第二') + } + + def 'merge properties with mappings'() { + given: + File one = buildJar('one.jar') + .insertFile('META-INF/foo.properties', 'foo=1') + .insertFile('META-INF/bar.properties', 'bar=2') + .write() + + File two = buildJar('two.jar') + .insertFile('META-INF/foo.properties', 'foo=3') + .insertFile('META-INF/bar.properties', 'bar=4') + .write() + + buildFile << """ + import ${PropertiesFileTransformer.name} + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + transform(PropertiesFileTransformer) { + mappings = [ + 'META-INF/foo.properties': [mergeStrategy: 'append', mergeSeparator: ';'], + 'META-INF/bar.properties': [mergeStrategy: 'latest'] + ] + } + } + """.stripIndent() + + when: + run('shadowJar') + + then: + output.exists() + + and: + String fooText = getJarFileContents(output, 'META-INF/foo.properties') + fooText.contains('foo=1;3') + + and: + String barText = getJarFileContents(output, 'META-INF/bar.properties') + barText.contains('bar=4') + } + + @Issue( + ['https://github.com/GradleUp/shadow/issues/622', + 'https://github.com/GradleUp/shadow/issues/856' + ] + ) + def 'merged properties dont contain date comment'() { + given: + File one = buildJar('one.jar') + .insertFile('META-INF/test.properties', 'foo=one') + .write() + + File two = buildJar('two.jar') + .insertFile('META-INF/test.properties', 'foo=two') + .write() + + buildFile << """ + import ${PropertiesFileTransformer.name} + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + transform(PropertiesFileTransformer) { + paths = ['META-INF/test.properties'] + mergeStrategy = 'append' + } + } + """.stripIndent() + + when: + run('shadowJar') + + then: + output.exists() + String text = getJarFileContents(output, 'META-INF/test.properties') + text.trim() == '#\n\nfoo=one,two' + } +} diff --git a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowPluginSpec.groovy b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowPluginSpec.groovy index f4cdbb6fd..4f55f520b 100644 --- a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowPluginSpec.groovy +++ b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowPluginSpec.groovy @@ -387,7 +387,7 @@ class ShadowPluginSpec extends PluginSpecification { package client; import junit.framework.TestCase; public class Client extends TestCase { - public static void main(String[] args) {} + public static void main(String[] args) {} } """.stripIndent() @@ -1029,7 +1029,7 @@ class ShadowPluginSpec extends PluginSpecification { version = '1.0' repositories { maven { url = "${repo.uri}" } } dependencies { api project(':api') } - + shadowJar.minimize() """.stripIndent() @@ -1247,8 +1247,4 @@ class ShadowPluginSpec extends PluginSpecification { JarFile jar = new JarFile(output) assert jar.entries().collect().findAll { it.name.endsWith('.class') }.size() == 1 } - - private String escapedPath(File file) { - file.path.replaceAll('\\\\', '\\\\\\\\') - } } diff --git a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/TransformerSpec.groovy b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/TransformerSpec.groovy index 3972d51f6..aae6148d3 100644 --- a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/TransformerSpec.groovy +++ b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/TransformerSpec.groovy @@ -2,7 +2,6 @@ package com.github.jengelman.gradle.plugins.shadow import com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.GroovyExtensionModuleTransformer -import com.github.jengelman.gradle.plugins.shadow.transformers.PropertiesFileTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.XmlAppendingTransformer import com.github.jengelman.gradle.plugins.shadow.util.PluginSpecification @@ -763,67 +762,7 @@ staticExtensionClasses=com.acme.bar.SomeStaticExtension'''.stripIndent()).write( 'Log4j2PluginsCacheFileTransformer' | '' 'ManifestAppenderTransformer' | '' 'ManifestResourceTransformer' | '' - 'PropertiesFileTransformer' | '{ keyTransformer = { it.toLowerCase() } }' 'ServiceFileTransformer' | '' 'XmlAppendingTransformer' | '' } - - @Unroll - def 'merge properties with different strategies: #mergeStrategy'() { - given: - File one = buildJar('one.jar') - .insertFile('test.properties', - 'key1=val1\nkey2=val2\nkey3=val3').write() - - File two = buildJar('two.jar') - .insertFile('test.properties', - 'key1=VAL1\nkey2=VAL2\nkey4=val4').write() - - buildFile << """ - import ${PropertiesFileTransformer.name} - tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { - from('${escapedPath(one)}') - from('${escapedPath(two)}') - } - tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { - transform(PropertiesFileTransformer) { - paths = ['test.properties'] - mergeStrategy = '${mergeStrategy}' - } - } - """.stripIndent() - - when: - run('shadowJar') - - then: - assert output.exists() - - and: - String text = getJarFileContents(output, 'test.properties') - def lines = text.replace('#', '').trim().split("\\r?\\n").toList() - switch (mergeStrategy) { - case 'first': - assert lines.size() == 4 - assert lines.containsAll(['key1=val1', 'key2=val2', 'key3=val3', 'key4=val4']) - break - case 'latest': - assert lines.size() == 4 - assert lines.containsAll(['key1=VAL1', 'key2=VAL2', 'key3=val3', 'key4=val4']) - break - case 'append': - assert lines.size() == 4 - assert lines.containsAll(['key1=val1,VAL1', 'key2=val2,VAL2', 'key3=val3', 'key4=val4']) - break - default: - assert false : "Unknown mergeStrategy: $mergeStrategy" - } - - where: - mergeStrategy << ['first', 'latest', 'append'] - } - - private String escapedPath(File file) { - file.path.replaceAll('\\\\', '\\\\\\\\') - } } diff --git a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/caching/AbstractCachingSpec.groovy b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/caching/AbstractCachingSpec.groovy index bd33ca03d..d0e5e1cb6 100644 --- a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/caching/AbstractCachingSpec.groovy +++ b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/caching/AbstractCachingSpec.groovy @@ -45,10 +45,6 @@ abstract class AbstractCachingSpec extends PluginSpecification { return runner.withProjectDir(alternateDir.toFile()).withArguments(cacheArguments).build() } - private String escapedPath(File file) { - file.path.replaceAll('\\\\', '\\\\\\\\') - } - void assertShadowJarHasResult(TaskOutcome expectedOutcome) { def result = runWithCacheEnabled(shadowJarTask) assert result.task(shadowJarTask).outcome == expectedOutcome diff --git a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/PluginSpecification.groovy b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/PluginSpecification.groovy index aa7d6967c..59b68847b 100644 --- a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/PluginSpecification.groovy +++ b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/PluginSpecification.groovy @@ -180,6 +180,10 @@ abstract class PluginSpecification extends Specification { return new File(this.class.classLoader.getResource(name).toURI()) } + protected String escapedPath(File file) { + file.path.replaceAll('\\\\', '\\\\\\\\') + } + static File getTestKitDir() { def gradleUserHome = System.getenv("GRADLE_USER_HOME") if (!gradleUserHome) { From cf9aaaa8058f33561c7d9c7c9c808236adb11835 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 18:27:41 +0800 Subject: [PATCH 13/19] Assert `keyTransformer` non-null --- .../shadow/transformers/PropertiesFileTransformer.groovy | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy index 8baeb614a..f05d634df 100644 --- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy +++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy @@ -136,7 +136,7 @@ class PropertiesFileTransformer implements Transformer { String charset = 'ISO_8859_1' @Internal - transient Closure keyTransformer = IDENTITY + Closure keyTransformer = IDENTITY @Override boolean canTransformResource(FileTreeElement element) { @@ -192,7 +192,10 @@ class PropertiesFileTransformer implements Transformer { } private Properties transformKeys(Properties properties) { - if (keyTransformer == IDENTITY || keyTransformer == null) { + if (keyTransformer == null) { + throw new IllegalStateException("keyTransformer must not be null.") + } + if (keyTransformer == IDENTITY) { return properties } def result = new CleanProperties() From 0395e9f6098bfea37bfa407f97818ee8128b8c5c Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 18:33:29 +0800 Subject: [PATCH 14/19] Mark `PropertiesFileTransformer` as `Serializable` --- .../shadow/transformers/PropertiesFileTransformer.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy index f05d634df..140787dac 100644 --- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy +++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy @@ -115,7 +115,7 @@ import static groovy.lang.Closure.IDENTITY * @author Andres Almiray * @author Marc Philipp */ -class PropertiesFileTransformer implements Transformer { +class PropertiesFileTransformer implements Transformer, Serializable { private static final String PROPERTIES_SUFFIX = '.properties' private Map propertiesEntries = [:] From a6d267843195ac579f75e1c609c5a480632c0222 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 18:43:34 +0800 Subject: [PATCH 15/19] Fix `merged properties dont contain date comment` for Windows --- .../gradle/plugins/shadow/PropertiesFileTransformerSpec.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PropertiesFileTransformerSpec.groovy b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PropertiesFileTransformerSpec.groovy index c0d0106d1..a778c9ed2 100644 --- a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PropertiesFileTransformerSpec.groovy +++ b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PropertiesFileTransformerSpec.groovy @@ -208,6 +208,6 @@ class PropertiesFileTransformerSpec extends PluginSpecification { then: output.exists() String text = getJarFileContents(output, 'META-INF/test.properties') - text.trim() == '#\n\nfoo=one,two' + text.replace('#', '').trim() == 'foo=one,two' } } From 605f95f65b222a4d9d9b9b0d4f1856409ec3b0b5 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 18:49:48 +0800 Subject: [PATCH 16/19] Fix `keyTransformer` --- .github/workflows/ci.yml | 2 ++ .../shadow/transformers/PropertiesFileTransformer.groovy | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6b210bb6..13088e37d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,8 @@ on: branches: - 8.x pull_request: + branches: + - 8.x workflow_dispatch: jobs: diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy index 140787dac..258da5314 100644 --- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy +++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy @@ -115,7 +115,7 @@ import static groovy.lang.Closure.IDENTITY * @author Andres Almiray * @author Marc Philipp */ -class PropertiesFileTransformer implements Transformer, Serializable { +class PropertiesFileTransformer implements Transformer { private static final String PROPERTIES_SUFFIX = '.properties' private Map propertiesEntries = [:] @@ -136,7 +136,9 @@ class PropertiesFileTransformer implements Transformer, Serializable { String charset = 'ISO_8859_1' @Internal - Closure keyTransformer = IDENTITY + Closure keyTransformer = new Closure("") { + String doCall(Object arguments) { arguments } + } @Override boolean canTransformResource(FileTreeElement element) { From 4f63f0d72a2f812ce862d8df61b5281c46fb8c36 Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 1 Jul 2025 21:00:43 +0800 Subject: [PATCH 17/19] Align tests --- .../shadow/PropertiesFileTransformerSpec.groovy | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PropertiesFileTransformerSpec.groovy b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PropertiesFileTransformerSpec.groovy index a778c9ed2..34ace1f52 100644 --- a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PropertiesFileTransformerSpec.groovy +++ b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PropertiesFileTransformerSpec.groovy @@ -12,11 +12,11 @@ class PropertiesFileTransformerSpec extends PluginSpecification { given: File one = buildJar('one.jar') .insertFile('test.properties', - 'key1=val1\nkey2=val2\nkey3=val3').write() + 'key1=one\nkey2=one').write() File two = buildJar('two.jar') .insertFile('test.properties', - 'key1=VAL1\nkey2=VAL2\nkey4=val4').write() + 'key2=two\nkey3=two').write() buildFile << """ import ${PropertiesFileTransformer.name} @@ -27,6 +27,7 @@ class PropertiesFileTransformerSpec extends PluginSpecification { tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { transform(PropertiesFileTransformer) { paths = ['test.properties'] + mergeSeparator = ";" mergeStrategy = '${mergeStrategy}' } } @@ -41,21 +42,19 @@ class PropertiesFileTransformerSpec extends PluginSpecification { and: String text = getJarFileContents(output, 'test.properties') def lines = text.replace('#', '').trim().split("\\r?\\n").toList() + assert lines.size() == 3 switch (mergeStrategy) { case 'first': - assert lines.size() == 4 - assert lines.containsAll(['key1=val1', 'key2=val2', 'key3=val3', 'key4=val4']) + assert lines.containsAll(['key1=one', 'key2=one', 'key3=two']) break case 'latest': - assert lines.size() == 4 - assert lines.containsAll(['key1=VAL1', 'key2=VAL2', 'key3=val3', 'key4=val4']) + assert lines.containsAll(['key1=one', 'key2=two', 'key3=two']) break case 'append': - assert lines.size() == 4 - assert lines.containsAll(['key1=val1,VAL1', 'key2=val2,VAL2', 'key3=val3', 'key4=val4']) + assert lines.containsAll(['key1=one', 'key2=one;two', 'key3=two']) break default: - assert false : "Unknown mergeStrategy: $mergeStrategy" + assert false: "Unknown mergeStrategy: $mergeStrategy" } where: From 228e33682d16e0fe339d1c21edf3f3df3db91c65 Mon Sep 17 00:00:00 2001 From: Zongle Wang Date: Tue, 1 Jul 2025 21:07:00 +0800 Subject: [PATCH 18/19] Apply suggestions from code review --- .../shadow/transformers/PropertiesFileTransformer.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy index 258da5314..d2a662c94 100644 --- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy +++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy @@ -137,7 +137,7 @@ class PropertiesFileTransformer implements Transformer { @Internal Closure keyTransformer = new Closure("") { - String doCall(Object arguments) { arguments } + String doCall(Object arguments) { arguments } // We can't use Closure#IDENTITY instead, for being compatible with Groovy 3 and 4. } @Override From 6faa838509d23d56982dd64f24ae391d0f8e4dad Mon Sep 17 00:00:00 2001 From: Zongle Wang Date: Tue, 1 Jul 2025 21:09:31 +0800 Subject: [PATCH 19/19] Update src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy --- .../shadow/transformers/PropertiesFileTransformer.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy index d2a662c94..dde1869ec 100644 --- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy +++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy @@ -137,7 +137,7 @@ class PropertiesFileTransformer implements Transformer { @Internal Closure keyTransformer = new Closure("") { - String doCall(Object arguments) { arguments } // We can't use Closure#IDENTITY instead, for being compatible with Groovy 3 and 4. + String doCall(Object arguments) { arguments } // We can't use Closure#IDENTITY instead, as it is not compatible with Groovy 3 and 4. } @Override