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/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)) 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 { 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..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 @@ -136,7 +136,9 @@ class PropertiesFileTransformer implements Transformer { String charset = 'ISO_8859_1' @Internal - Closure keyTransformer = IDENTITY + Closure keyTransformer = new Closure("") { + String doCall(Object arguments) { arguments } // We can't use Closure#IDENTITY instead, as it is not compatible with Groovy 3 and 4. + } @Override boolean canTransformResource(FileTreeElement element) { @@ -192,6 +194,9 @@ class PropertiesFileTransformer implements Transformer { } private Properties transformKeys(Properties properties) { + if (keyTransformer == null) { + throw new IllegalStateException("keyTransformer must not be null.") + } if (keyTransformer == IDENTITY) { return properties } 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..34ace1f52 --- /dev/null +++ b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PropertiesFileTransformerSpec.groovy @@ -0,0 +1,212 @@ +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=one\nkey2=one').write() + + File two = buildJar('two.jar') + .insertFile('test.properties', + 'key2=two\nkey3=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 = ['test.properties'] + mergeSeparator = ";" + 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() + assert lines.size() == 3 + switch (mergeStrategy) { + case 'first': + assert lines.containsAll(['key1=one', 'key2=one', 'key3=two']) + break + case 'latest': + assert lines.containsAll(['key1=one', 'key2=two', 'key3=two']) + break + case 'append': + assert lines.containsAll(['key1=one', 'key2=one;two', 'key3=two']) + 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.replace('#', '').trim() == 'foo=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 2835108e8..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 @@ -251,7 +251,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 +349,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 +386,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 +400,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 +430,7 @@ two # NOTE: No newline terminates this line/file given: File xml1 = buildJar('xml1.jar').insertFile('properties.xml', ''' - + val1 @@ -439,7 +439,7 @@ two # NOTE: No newline terminates this line/file File xml2 = buildJar('xml2.jar').insertFile('properties.xml', ''' - + val2 @@ -485,9 +485,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 +499,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 +545,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 +559,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' @@ -762,12 +762,7 @@ staticExtensionClasses=com.acme.bar.SomeStaticExtension'''.stripIndent()).write( 'Log4j2PluginsCacheFileTransformer' | '' 'ManifestAppenderTransformer' | '' 'ManifestResourceTransformer' | '' - 'PropertiesFileTransformer' | '{ keyTransformer = { it.toLowerCase() } }' 'ServiceFileTransformer' | '' 'XmlAppendingTransformer' | '' } - - 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) {