diff --git a/build.gradle.kts b/build.gradle.kts index 82187aafe..4fd1f40de 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -93,6 +93,8 @@ dependencies { intiTestImplementation(libs.okio) intiTestImplementation(libs.apache.maven.modelBuilder) intiTestImplementation(libs.apache.maven.repositoryMetadata) + // TODO: this will be removed after we migrated all functional tests to Kotlin. + intiTestImplementation(sourceSets.main.get().output) lintChecks(libs.androidx.gradlePluginLints) lintChecks(libs.assertk.lint) diff --git a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/ApplicationSpec.groovy b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/ApplicationSpec.groovy deleted file mode 100644 index 939e3cbb6..000000000 --- a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/ApplicationSpec.groovy +++ /dev/null @@ -1,249 +0,0 @@ -package com.github.jengelman.gradle.plugins.shadow - -import org.apache.tools.zip.ZipFile -import org.gradle.testkit.runner.BuildResult -import spock.lang.Issue - -import java.util.jar.Attributes -import java.util.jar.JarFile - -class ApplicationSpec extends BasePluginSpecification { - - def 'integration with application plugin'() { - given: - repo.module('shadow', 'a', '1.0') - .insertFile('a.properties', 'a') - .insertFile('a2.properties', 'a2') - .publish() - - file('src/main/java/myapp/Main.java') << """ - package myapp; - public class Main { - public static void main(String[] args) { - System.out.println("TestApp: Hello World! (" + args[0] + ")"); - } - } - """.stripIndent() - - buildFile << """ - apply plugin: 'application' - - application { - mainClass = 'myapp.Main' - } - - dependencies { - implementation 'shadow:a:1.0' - } - - runShadow { - args 'foo' - } - """.stripIndent() - - settingsFile << "rootProject.name = 'myapp'" - - when: - BuildResult result = run('runShadow') - - then: 'tests that runShadow executed and exited' - assert result.output.contains('TestApp: Hello World! (foo)') - - and: 'Check that the proper jar file was installed' - File installedJar = getFile('build/install/myapp-shadow/lib/myapp-1.0-all.jar') - assert installedJar.exists() - - and: 'And that jar file as the correct files in it' - contains(installedJar, ['a.properties', 'a2.properties', 'myapp/Main.class']) - - and: 'Check the manifest attributes in the jar file are correct' - JarFile jar = new JarFile(installedJar) - Attributes attributes = jar.manifest.mainAttributes - assert attributes.getValue('Main-Class') == 'myapp.Main' - - then: 'Check that the start scripts is written out and has the correct Java invocation' - File startScript = getFile('build/install/myapp-shadow/bin/myapp') - assert startScript.exists() - assert startScript.text.contains("CLASSPATH=\$APP_HOME/lib/myapp-1.0-all.jar") - assert startScript.text.contains("-jar \"\\\"\$CLASSPATH\\\"\" \"\$APP_ARGS\"") - assert startScript.text.contains("exec \"\$JAVACMD\" \"\$@\"") - - cleanup: - jar?.close() - } - - def 'integration with application plugin and java toolchains'() { - given: - repo.module('shadow', 'a', '1.0') - .insertFile('a.properties', 'a') - .insertFile('a2.properties', 'a2') - .publish() - - file('src/main/java/myapp/Main.java') << """ - package myapp; - public class Main { - public static void main(String[] args) { - System.out.println("TestApp: Hello World! (" + args[0] + ")"); - } - } - """.stripIndent() - - buildFile << """ - apply plugin: 'application' - - application { - mainClass = 'myapp.Main' - } - - dependencies { - implementation 'shadow:a:1.0' - } - - java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } - } - - runShadow { - args 'foo' - doFirst { - logger.lifecycle("Running application with JDK \${it.javaLauncher.get().metadata.languageVersion.asInt()}") - } - } - """.stripIndent() - - settingsFile.write """ - plugins { - // https://docs.gradle.org/8.0.1/userguide/toolchains.html#sub:download_repositories - id("org.gradle.toolchains.foojay-resolver-convention") version("0.7.0") - } - - rootProject.name = 'myapp' - """.stripIndent() + getSettingsBuildScript(false) - - when: - BuildResult result = run('runShadow') - - then: 'tests that runShadow executed and exited' - assert result.output.contains('Running application with JDK 17') - assert result.output.contains('TestApp: Hello World! (foo)') - - and: 'Check that the proper jar file was installed' - File installedJar = getFile('build/install/myapp-shadow/lib/myapp-1.0-all.jar') - assert installedJar.exists() - - and: 'And that jar file as the correct files in it' - contains(installedJar, ['a.properties', 'a2.properties', 'myapp/Main.class']) - - and: 'Check the manifest attributes in the jar file are correct' - JarFile jar = new JarFile(installedJar) - Attributes attributes = jar.manifest.mainAttributes - assert attributes.getValue('Main-Class') == 'myapp.Main' - - then: 'Check that the start scripts is written out and has the correct Java invocation' - File startScript = getFile('build/install/myapp-shadow/bin/myapp') - assert startScript.exists() - assert startScript.text.contains("CLASSPATH=\$APP_HOME/lib/myapp-1.0-all.jar") - assert startScript.text.contains("-jar \"\\\"\$CLASSPATH\\\"\" \"\$APP_ARGS\"") - assert startScript.text.contains("exec \"\$JAVACMD\" \"\$@\"") - - cleanup: - jar?.close() - } - - @Issue('https://github.com/GradleUp/shadow/issues/89') - def 'shadow application distributions should use shadow jar'() { - given: - repo.module('shadow', 'a', '1.0') - .insertFile('a.properties', 'a') - .insertFile('a2.properties', 'a2') - .publish() - - file('src/main/java/myapp/Main.java') << """ - package myapp; - public class Main { - public static void main(String[] args) { - System.out.println("TestApp: Hello World! (" + args[0] + ")"); - } - } - """.stripIndent() - - buildFile << """ - apply plugin: 'application' - - application { - mainClass = 'myapp.Main' - } - - dependencies { - shadow 'shadow:a:1.0' - } - - runShadow { - args 'foo' - } - """.stripIndent() - - settingsFile << "rootProject.name = 'myapp'" - - when: - run('shadowDistZip') - - then: 'Check that the distribution zip was created' - File zip = getFile('build/distributions/myapp-shadow-1.0.zip') - assert zip.exists() - - and: 'Check that the zip contains the correct library files & scripts' - ZipFile zipFile = new ZipFile(zip) - println zipFile.entries.collect { it.name } - assert zipFile.entries.find { it.name == 'myapp-shadow-1.0/lib/myapp-1.0-all.jar' } - assert zipFile.entries.find { it.name == 'myapp-shadow-1.0/lib/a-1.0.jar' } - - cleanup: - zipFile?.close() - } - - @Issue('https://github.com/GradleUp/shadow/issues/90') - def 'installShadow does not execute dependent shadow task'() { - given: - repo.module('shadow', 'a', '1.0') - .insertFile('a.properties', 'a') - .insertFile('a2.properties', 'a2') - .publish() - - file('src/main/java/myapp/Main.java') << """ - package myapp; - public class Main { - public static void main(String[] args) { - System.out.println("TestApp: Hello World! (" + args[0] + ")"); - } - } - """.stripIndent() - - buildFile << """ - apply plugin: 'application' - - application { - mainClass = 'myapp.Main' - } - - dependencies { - implementation 'shadow:a:1.0' - } - - runShadow { - args 'foo' - } - """.stripIndent() - - settingsFile << "rootProject.name = 'myapp'" - - when: - run(ShadowApplicationPlugin.SHADOW_INSTALL_TASK_NAME) - - then: 'Check that the proper jar file was installed' - File installedJar = getFile('build/install/myapp-shadow/lib/myapp-1.0-all.jar') - assert installedJar.exists() - } -} diff --git a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheSpec.groovy b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheSpec.groovy deleted file mode 100644 index 2391e809d..000000000 --- a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheSpec.groovy +++ /dev/null @@ -1,166 +0,0 @@ -package com.github.jengelman.gradle.plugins.shadow - -class ConfigurationCacheSpec extends BasePluginSpecification { - - @Override - def setup() { - repo.module('shadow', 'a', '1.0') - .insertFile('a.properties', 'a') - .insertFile('a2.properties', 'a2') - .publish() - repo.module('shadow', 'b', '1.0') - .insertFile('b.properties', 'b') - .publish() - - buildFile << """ - dependencies { - implementation 'shadow:a:1.0' - implementation 'shadow:b:1.0' - } - """.stripIndent() - } - - def "supports configuration cache"() { - given: - file('src/main/java/myapp/Main.java') << """ - package myapp; - public class Main { - public static void main(String[] args) { - System.out.println("TestApp: Hello World! (" + args[0] + ")"); - } - } - """.stripIndent() - - buildFile << """ - apply plugin: 'application' - - application { - mainClass = 'myapp.Main' - } - - dependencies { - implementation 'shadow:a:1.0' - } - - runShadow { - args 'foo' - } - """.stripIndent() - - settingsFile << "rootProject.name = 'myapp'" - - when: - run('shadowJar') - def result = run('shadowJar') - - then: - result.output.contains("Reusing configuration cache.") - } - - def "configuration caching supports includes"() { - given: - buildFile << """ - $shadowJar { - exclude 'a2.properties' - } - """.stripIndent() - - when: - run('shadowJar') - output.delete() - def result = run('shadowJar') - - then: - contains(output, ['a.properties', 'b.properties']) - - and: - doesNotContain(output, ['a2.properties']) - result.output.contains("Reusing configuration cache.") - } - - def "configuration caching supports minimize"() { - given: - file('settings.gradle') << """ - include 'client', 'server' - """.stripIndent() - - and: - file('client/src/main/java/client/Client.java') << """ - package client; - public class Client {} - """.stripIndent() - file('client/build.gradle') << """ - apply plugin: 'java' - - dependencies { implementation 'junit:junit:3.8.2' } - """.stripIndent() - - and: - file('server/src/main/java/server/Server.java') << """ - package server; - public class Server {} - """.stripIndent() - file('server/build.gradle') << """ - $defaultBuildScript - - $shadowJar { - minimize { - exclude(dependency('junit:junit:.*')) - } - } - - dependencies { implementation project(':client') } - """.stripIndent() - - and: - def output = getFile('server/build/libs/server-all.jar') - - when: - run('shadowJar') - output.delete() - def result = run('shadowJar') - - then: - output.exists() - contains(output, [ - 'server/Server.class', - 'junit/framework/Test.class' - ]) - doesNotContain(output, ['client/Client.class']) - - and: - result.output.contains("Reusing configuration cache.") - } - - def "configuration caching of configurations is up-to-date"() { - given: - file('settings.gradle') << """ - include 'lib' - """.stripIndent() - - and: - file('lib/src/main/java/lib/Lib.java') << """ - package lib; - public class Lib {} - """.stripIndent() - file('lib/build.gradle') << """ - $defaultBuildScript - - dependencies { - implementation "junit:junit:3.8.2" - } - - $shadowJar { - configurations = [project.configurations.compileClasspath] - } - - """.stripIndent() - - when: - run('shadowJar') - def result = run('shadowJar') - - then: - result.output.contains(":lib:shadowJar UP-TO-DATE") - } -} diff --git a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/FilteringSpec.groovy b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/FilteringSpec.groovy deleted file mode 100644 index b365e6da2..000000000 --- a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/FilteringSpec.groovy +++ /dev/null @@ -1,414 +0,0 @@ -package com.github.jengelman.gradle.plugins.shadow - -import org.gradle.testkit.runner.BuildResult -import org.gradle.testkit.runner.TaskOutcome -import spock.lang.Issue - -class FilteringSpec extends BasePluginSpecification { - - @Override - def setup() { - repo.module('shadow', 'a', '1.0') - .insertFile('a.properties', 'a') - .insertFile('a2.properties', 'a2') - .publish() - repo.module('shadow', 'b', '1.0') - .insertFile('b.properties', 'b') - .publish() - - buildFile << """ - dependencies { - implementation 'shadow:a:1.0' - implementation 'shadow:b:1.0' - } - """.stripIndent() - - } - - def 'include all dependencies'() { - when: - run('shadowJar') - - then: - contains(output, ['a.properties', 'a2.properties', 'b.properties']) - } - - def 'exclude files'() { - given: - buildFile << """ - $shadowJar { - exclude 'a2.properties' - } - """.stripIndent() - - when: - run('shadowJar') - - then: - contains(output, ['a.properties', 'b.properties']) - - and: - doesNotContain(output, ['a2.properties']) - } - - def "exclude dependency"() { - given: - repo.module('shadow', 'c', '1.0') - .insertFile('c.properties', 'c') - .publish() - repo.module('shadow', 'd', '1.0') - .insertFile('d.properties', 'd') - .dependsOn('c') - .publish() - - buildFile << """ - dependencies { - implementation 'shadow:d:1.0' - } - - $shadowJar { - dependencies { - exclude(dependency('shadow:d:1.0')) - } - } - """.stripIndent() - - when: - run('shadowJar') - - then: - contains(output, ['a.properties', 'a2.properties', 'b.properties', 'c.properties']) - - and: - doesNotContain(output, ['d.properties']) - } - - @Issue('https://github.com/GradleUp/shadow/issues/83') - def "exclude dependency using wildcard syntax"() { - given: - repo.module('shadow', 'c', '1.0') - .insertFile('c.properties', 'c') - .publish() - repo.module('shadow', 'd', '1.0') - .insertFile('d.properties', 'd') - .dependsOn('c') - .publish() - - buildFile << """ - dependencies { - implementation 'shadow:d:1.0' - } - - $shadowJar { - dependencies { - exclude(dependency('shadow:d:.*')) - } - } - """.stripIndent() - - when: - run('shadowJar') - - then: - contains(output, ['a.properties', 'a2.properties', 'b.properties', 'c.properties']) - - and: - doesNotContain(output, ['d.properties']) - } - - @Issue("https://github.com/GradleUp/shadow/issues/54") - def "dependency exclusions affect UP-TO-DATE check"() { - given: - repo.module('shadow', 'c', '1.0') - .insertFile('c.properties', 'c') - .publish() - repo.module('shadow', 'd', '1.0') - .insertFile('d.properties', 'd') - .dependsOn('c') - .publish() - - buildFile << """ - dependencies { - implementation 'shadow:d:1.0' - } - - $shadowJar { - dependencies { - exclude(dependency('shadow:d:1.0')) - } - } - """.stripIndent() - - when: - run('shadowJar') - - then: - contains(output, ['a.properties', 'a2.properties', 'b.properties', 'c.properties']) - - and: - doesNotContain(output, ['d.properties']) - - when: 'Update build file shadowJar dependency exclusion' - buildFile.text = buildFile.text.replace('exclude(dependency(\'shadow:d:1.0\'))', - 'exclude(dependency(\'shadow:c:1.0\'))') - - BuildResult result = run('shadowJar') - - then: - assert result.task(':shadowJar').outcome == TaskOutcome.SUCCESS - - and: - contains(output, ['a.properties', 'a2.properties', 'b.properties', 'd.properties']) - - and: - doesNotContain(output, ['c.properties']) - } - - @Issue("https://github.com/GradleUp/shadow/issues/62") - def "project exclusions affect UP-TO-DATE check"() { - given: - repo.module('shadow', 'c', '1.0') - .insertFile('c.properties', 'c') - .publish() - repo.module('shadow', 'd', '1.0') - .insertFile('d.properties', 'd') - .dependsOn('c') - .publish() - - buildFile << """ - dependencies { - implementation 'shadow:d:1.0' - } - - $shadowJar { - dependencies { - exclude(dependency('shadow:d:1.0')) - } - } - """.stripIndent() - - when: - run('shadowJar') - - then: - contains(output, ['a.properties', 'a2.properties', 'b.properties', 'c.properties']) - - and: - doesNotContain(output, ['d.properties']) - - when: 'Update build file shadowJar dependency exclusion' - buildFile.text = buildFile.text.replace('exclude(dependency(\'shadow:d:1.0\'))', - 'exclude \'a.properties\'') - - BuildResult result = run('shadowJar') - - then: - assert result.task(':shadowJar').outcome == TaskOutcome.SUCCESS - - and: - contains(output, ['a2.properties', 'b.properties', 'c.properties', 'd.properties']) - - and: - doesNotContain(output, ['a.properties']) - } - - def "include dependency, excluding all others"() { - given: - repo.module('shadow', 'c', '1.0') - .insertFile('c.properties', 'c') - .publish() - repo.module('shadow', 'd', '1.0') - .insertFile('d.properties', 'd') - .dependsOn('c') - .publish() - - file('src/main/java/shadow/Passed.java') << ''' - package shadow; - public class Passed {} - '''.stripIndent() - - buildFile << """ - dependencies { - implementation 'shadow:d:1.0' - } - - $shadowJar { - dependencies { - include(dependency('shadow:d:1.0')) - } - } - """.stripIndent() - - when: - run('shadowJar') - - then: - contains(output, ['d.properties', 'shadow/Passed.class']) - - and: - doesNotContain(output, ['a.properties', 'a2.properties', 'b.properties', 'c.properties']) - } - - def 'filter project dependencies'() { - given: - buildFile.text = '' - - file('settings.gradle') << """ - include 'client', 'server' - """.stripIndent() - - file('client/src/main/java/client/Client.java') << """ - package client; - public class Client {} - """.stripIndent() - - file('client/build.gradle') << """ - ${getDefaultBuildScript('java', false, true)} - dependencies { implementation 'junit:junit:3.8.2' } - """.stripIndent() - - file('server/src/main/java/server/Server.java') << """ - package server; - import client.Client; - public class Server {} - """.stripIndent() - - file('server/build.gradle') << """ - ${getDefaultBuildScript('java', false, true)} - - dependencies { - implementation project(':client') - } - - $shadowJar { - dependencies { - exclude(project(':client')) - } - } - """.stripIndent() - - File serverOutput = getFile('server/build/libs/server-1.0-all.jar') - - when: - run(':server:shadowJar') - - then: - serverOutput.exists() - doesNotContain(serverOutput, [ - 'client/Client.class', - ]) - - and: - contains(serverOutput, ['server/Server.class', 'junit/framework/Test.class']) - } - - def 'exclude a transitive project dependency'() { - given: - buildFile.text = '' - - file('settings.gradle') << """ - include 'client', 'server' - """.stripIndent() - - file('client/src/main/java/client/Client.java') << """ - package client; - public class Client {} - """.stripIndent() - - file('client/build.gradle') << """ - ${getDefaultBuildScript('java', false, true)} - dependencies { implementation 'junit:junit:3.8.2' } - """.stripIndent() - - file('server/src/main/java/server/Server.java') << """ - package server; - import client.Client; - public class Server {} - """.stripIndent() - - file('server/build.gradle') << """ - ${getDefaultBuildScript('java', false, true)} - dependencies { implementation project(':client') } - - $shadowJar { - dependencies { - exclude(dependency { - it.moduleGroup == 'junit' - }) - } - } - """.stripIndent() - - File serverOutput = getFile('server/build/libs/server-1.0-all.jar') - - when: - run(':server:shadowJar') - - then: - serverOutput.exists() - doesNotContain(serverOutput, [ - 'junit/framework/Test.class' - ]) - - and: - contains(serverOutput, [ - 'client/Client.class', - 'server/Server.class']) - } - - //http://mail-archives.apache.org/mod_mbox/ant-user/200506.mbox/%3C001d01c57756$6dc35da0$dc00a8c0@CTEGDOMAIN.COM%3E - def 'verify exclude precedence over include'() { - given: - buildFile << """ - $shadowJar { - include '*.jar' - include '*.properties' - exclude 'a2.properties' - } - """.stripIndent() - - when: - run('shadowJar') - - then: - contains(output, ['a.properties', 'b.properties']) - - and: - doesNotContain(output, ['a2.properties']) - } - - @Issue("https://github.com/GradleUp/shadow/issues/69") - def "handle exclude with circular dependency"() { - given: - repo.module('shadow', 'c', '1.0') - .insertFile('c.properties', 'c') - .dependsOn('d') - .publish() - repo.module('shadow', 'd', '1.0') - .insertFile('d.properties', 'd') - .dependsOn('c') - .publish() - - buildFile << """ - dependencies { - implementation 'shadow:d:1.0' - } - - $shadowJar { - dependencies { - exclude(dependency('shadow:d:1.0')) - } - } - """.stripIndent() - - when: - run('shadowJar') - - then: - contains(output, ['a.properties', 'a2.properties', 'b.properties', 'c.properties']) - - and: - doesNotContain(output, ['d.properties']) - } - -} diff --git a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/RelocationSpec.groovy b/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/RelocationSpec.groovy deleted file mode 100644 index 8eb8c4d35..000000000 --- a/src/funcTest/groovy/com/github/jengelman/gradle/plugins/shadow/RelocationSpec.groovy +++ /dev/null @@ -1,348 +0,0 @@ -package com.github.jengelman.gradle.plugins.shadow - -import spock.lang.Issue - -import java.util.jar.Attributes -import java.util.jar.JarFile - -class RelocationSpec extends BasePluginSpecification { - - def "auto relocate plugin dependencies"() { - given: - buildFile << """ - $shadowJar { - enableRelocation = true - } - - dependencies { - implementation 'junit:junit:3.8.2' - } - """.stripIndent() - - when: - run('shadowJar') - - then: - contains(output, [ - 'META-INF/MANIFEST.MF', - 'shadow/junit/textui/ResultPrinter.class', - 'shadow/junit/textui/TestRunner.class', - 'shadow/junit/framework/Assert.class', - 'shadow/junit/framework/AssertionFailedError.class', - 'shadow/junit/framework/ComparisonCompactor.class', - 'shadow/junit/framework/ComparisonFailure.class', - 'shadow/junit/framework/Protectable.class', - 'shadow/junit/framework/Test.class', - 'shadow/junit/framework/TestCase.class', - 'shadow/junit/framework/TestFailure.class', - 'shadow/junit/framework/TestListener.class', - 'shadow/junit/framework/TestResult$1.class', - 'shadow/junit/framework/TestResult.class', - 'shadow/junit/framework/TestSuite$1.class', - 'shadow/junit/framework/TestSuite.class' - ]) - } - - @Issue('https://github.com/GradleUp/shadow/issues/58') - def "relocate dependency files"() { - given: - buildFile << """ - dependencies { - implementation 'junit:junit:3.8.2' - } - - $shadowJar { - relocate 'junit.textui', 'a' - relocate 'junit.framework', 'b' - manifest { - attributes 'TEST-VALUE': 'FOO' - } - } - """.stripIndent() - - when: - run('shadowJar') - - then: - contains(output, [ - 'META-INF/MANIFEST.MF', - 'a/ResultPrinter.class', - 'a/TestRunner.class', - 'b/Assert.class', - 'b/AssertionFailedError.class', - 'b/ComparisonCompactor.class', - 'b/ComparisonFailure.class', - 'b/Protectable.class', - 'b/Test.class', - 'b/TestCase.class', - 'b/TestFailure.class', - 'b/TestListener.class', - 'b/TestResult$1.class', - 'b/TestResult.class', - 'b/TestSuite$1.class', - 'b/TestSuite.class' - ]) - - and: - doesNotContain(output, [ - 'junit/textui/ResultPrinter.class', - 'junit/textui/TestRunner.class', - 'junit/framework/Assert.class', - 'junit/framework/AssertionFailedError.class', - 'junit/framework/ComparisonCompactor.class', - 'junit/framework/ComparisonFailure.class', - 'junit/framework/Protectable.class', - 'junit/framework/Test.class', - 'junit/framework/TestCase.class', - 'junit/framework/TestFailure.class', - 'junit/framework/TestListener.class', - 'junit/framework/TestResult$1.class', - 'junit/framework/TestResult.class', - 'junit/framework/TestSuite$1.class', - 'junit/framework/TestSuite.class' - ]) - - and: 'Test that manifest file exists with contents' - JarFile jar = new JarFile(output) - Attributes attributes = jar.manifest.getMainAttributes() - String val = attributes.getValue('TEST-VALUE') - assert val == 'FOO' - } - - def "relocate dependency files with filtering"() { - given: - buildFile << """ - dependencies { - implementation 'junit:junit:3.8.2' - } - - $shadowJar { - relocate('junit.textui', 'a') { - exclude 'junit.textui.TestRunner' - } - relocate('junit.framework', 'b') { - include 'junit.framework.Test*' - } - } - """.stripIndent() - - when: - run('shadowJar') - - then: - contains(output, [ - 'a/ResultPrinter.class', - 'b/Test.class', - 'b/TestCase.class', - 'b/TestFailure.class', - 'b/TestListener.class', - 'b/TestResult$1.class', - 'b/TestResult.class', - 'b/TestSuite$1.class', - 'b/TestSuite.class' - ]) - - and: - doesNotContain(output, [ - 'a/TestRunner.class', - 'b/Assert.class', - 'b/AssertionFailedError.class', - 'b/ComparisonCompactor.class', - 'b/ComparisonFailure.class', - 'b/Protectable.class' - ]) - - and: - contains(output, [ - 'junit/textui/TestRunner.class', - 'junit/framework/Assert.class', - 'junit/framework/AssertionFailedError.class', - 'junit/framework/ComparisonCompactor.class', - 'junit/framework/ComparisonFailure.class', - 'junit/framework/Protectable.class' - ]) - } - - @Issue([ - 'https://github.com/GradleUp/shadow/issues/55', - 'https://github.com/GradleUp/shadow/issues/53', - ]) - def "remap class names for relocated files in project source"() { - given: - buildFile << """ - dependencies { - implementation 'junit:junit:3.8.2' - } - - $shadowJar { - relocate 'junit.framework', 'shadow.junit' - } - """.stripIndent() - - file('src/main/java/shadow/ShadowTest.java') << ''' - package shadow; - - import junit.framework.Test; - import junit.framework.TestResult; - public class ShadowTest implements Test { - public int countTestCases() { return 0; } - public void run(TestResult result) { } - } - '''.stripIndent() - - when: - run('shadowJar') - - then: - contains(output, [ - 'shadow/ShadowTest.class', - 'shadow/junit/Test.class', - 'shadow/junit' - ]) - - and: - doesNotContain(output, [ - 'junit/framework', - 'junit/framework/Test.class' - ]) - - and: 'check that the class can be loaded. If the file was not relocated properly, we should get a NoDefClassFound' - // Isolated class loader with only the JVM system jars and the output jar from the test project - URLClassLoader classLoader = new URLClassLoader([output.toURI().toURL()] as URL[], - ClassLoader.systemClassLoader.parent) - classLoader.loadClass('shadow.ShadowTest') - } - - @Issue('https://github.com/GradleUp/shadow/issues/61') - def "relocate does not drop dependency resources"() { - given: 'Core project with dependency and resource' - file('core/build.gradle') << """ - apply plugin: 'java-library' - - dependencies { api 'junit:junit:3.8.2' } - """.stripIndent() - - file('core/src/main/resources/TEST') << 'TEST RESOURCE' - file('core/src/main/resources/test.properties') << 'name=test' - file('core/src/main/java/core/Core.java') << ''' - package core; - - import junit.framework.Test; - - public class Core {} - '''.stripIndent() - - and: 'App project with shadow, relocation, and project dependency' - file('app/build.gradle') << """ - $defaultBuildScript - - dependencies { implementation project(':core') } - - $shadowJar { - relocate 'core', 'app.core' - relocate 'junit.framework', 'app.junit.framework' - } - """.stripIndent() - - file('app/src/main/resources/APP-TEST') << 'APP TEST RESOURCE' - file('app/src/main/java/app/App.java') << ''' - package app; - - import core.Core; - import junit.framework.Test; - - public class App {} - '''.stripIndent() - - and: 'Configure multi-project build' - settingsFile << ''' - include 'core', 'app' - '''.stripIndent() - - when: - run(':app:shadowJar') - - then: - File appOutput = getFile('app/build/libs/app-all.jar') - assert appOutput.exists() - - and: - contains(appOutput, [ - 'TEST', - 'APP-TEST', - 'test.properties', - 'app/core/Core.class', - 'app/App.class', - 'app/junit/framework/Test.class' - ]) - } - - @Issue([ - 'https://github.com/GradleUp/shadow/issues/93', - 'https://github.com/GradleUp/shadow/issues/114', - ]) - def "relocate resource files"() { - given: - repo.module('shadow', 'dep', '1.0') - .insertFile('foo/dep.properties', 'c') - .publish() - file('src/main/java/foo/Foo.java') << ''' - package foo; - - class Foo {} - '''.stripIndent() - file('src/main/resources/foo/foo.properties') << 'name=foo' - - buildFile << """ - dependencies { - implementation 'shadow:dep:1.0' - } - - $shadowJar { - relocate 'foo', 'bar' - } - """.stripIndent() - - when: - run('shadowJar') - - then: - contains(output, [ - 'bar/Foo.class', - 'bar/foo.properties', - 'bar/dep.properties' - ]) - - and: - doesNotContain(output, [ - 'foo/Foo.class', - 'foo/foo.properties', - 'foo/dep.properties' - ]) - } - - @Issue("https://github.com/GradleUp/shadow/issues/294") - def "does not error on relocating java9 classes"() { - given: - buildFile << """ - dependencies { - implementation 'org.slf4j:slf4j-api:1.7.21' - implementation group: 'io.netty', name: 'netty-all', version: '4.0.23.Final' - implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '2.5.0' - implementation group: 'org.apache.zookeeper', name: 'zookeeper', version: '3.4.6' - } - - $shadowJar { - zip64 = true - relocate 'com.google.protobuf', 'shaded.com.google.protobuf' - relocate 'io.netty', 'shaded.io.netty' - } - """.stripIndent() - - when: - run('shadowJar') - - then: - noExceptionThrown() - } -} diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt new file mode 100644 index 000000000..5160d4a04 --- /dev/null +++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt @@ -0,0 +1,129 @@ +package com.github.jengelman.gradle.plugins.shadow + +import assertk.assertThat +import assertk.assertions.contains +import assertk.assertions.containsAtLeast +import assertk.assertions.exists +import assertk.assertions.isEqualTo +import java.util.jar.JarFile +import kotlin.io.path.appendText +import kotlin.io.path.readText +import kotlin.io.path.writeText +import org.apache.tools.zip.ZipFile +import org.junit.jupiter.api.Test + +class ApplicationTest : BasePluginTest() { + + @Test + fun integrationWithApplicationPluginAndJavaToolchains() { + prepare( + projectBlock = """ + java { + toolchain.languageVersion = JavaLanguageVersion.of(17) + } + """.trimIndent(), + settingsBlock = """ + plugins { + id('org.gradle.toolchains.foojay-resolver-convention') version '0.7.0' + } + """.trimIndent(), + runShadowBlock = """ + doFirst { + logger.lifecycle("Running application with JDK ${'$'}{it.javaLauncher.get().metadata.languageVersion.asInt()}") + } + """.trimIndent(), + ) + + val result = run(runShadowTask) + + assertThat(result.output).contains("Running application with JDK 17") + assertThat(result.output).contains("TestApp: Hello World! (foo)") + + val installedJar = path("build/install/myapp-shadow/lib/myapp-1.0-all.jar") + assertThat(installedJar).exists() + + assertContains(installedJar, listOf("a.properties", "a2.properties", "myapp/Main.class")) + + val jarFile = JarFile(installedJar.toFile()) + assertThat(jarFile.manifest.mainAttributes.getValue("Main-Class")) + .isEqualTo("myapp.Main") + + path("build/install/myapp-shadow/bin/myapp").let { startScript -> + assertThat(startScript).exists() + assertThat(startScript.readText()).contains("CLASSPATH=\$APP_HOME/lib/myapp-1.0-all.jar") + assertThat(startScript.readText()).contains("-jar \"\\\"\$CLASSPATH\\\"\" \"\$APP_ARGS\"") + assertThat(startScript.readText()).contains("exec \"\$JAVACMD\" \"\$@\"") + } + } + + @Test + fun shadowApplicationDistributionsShouldUseShadowJar() { + prepare( + projectBlock = """ + dependencies { + shadow 'shadow:a:1.0' + } + """.trimIndent(), + ) + + run("shadowDistZip") + + val zip = path("build/distributions/myapp-shadow-1.0.zip") + assertThat(zip).exists() + + val entries = ZipFile(zip.toFile()).entries.toList().map { it.name } + assertThat(entries).containsAtLeast( + "myapp-shadow-1.0/lib/myapp-1.0-all.jar", + "myapp-shadow-1.0/lib/a-1.0.jar", + ) + } + + @Test + fun installShadowDoesNotExecuteDependentShadowTask() { + prepare() + + run(ShadowApplicationPlugin.SHADOW_INSTALL_TASK_NAME) + + assertThat(path("build/install/myapp-shadow/lib/myapp-1.0-all.jar")).exists() + } + + private fun prepare( + projectBlock: String = "", + settingsBlock: String = "", + runShadowBlock: String = "", + ) { + publishArtifactA() + path("src/main/java/myapp/Main.java").appendText( + """ + package myapp; + public class Main { + public static void main(String[] args) { + System.out.println("TestApp: Hello World! (" + args[0] + ")"); + } + } + """.trimIndent(), + ) + buildScript.appendText( + """ + apply plugin: 'application' + $projectBlock + application { + mainClass = 'myapp.Main' + } + dependencies { + implementation 'shadow:a:1.0' + } + $runShadow { + args 'foo' + $runShadowBlock + } + """.trimIndent(), + ) + settingsScript.writeText( + getSettingsBuildScript( + startBlock = settingsBlock, + endBlock = "rootProject.name = 'myapp'", + ), + ) + } +} diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt new file mode 100644 index 000000000..ff828078a --- /dev/null +++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt @@ -0,0 +1,262 @@ +package com.github.jengelman.gradle.plugins.shadow + +import com.github.jengelman.gradle.plugins.shadow.ShadowApplicationPlugin.Companion.SHADOW_RUN_TASK_NAME +import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin.Companion.SHADOW_JAR_TASK_NAME +import com.github.jengelman.gradle.plugins.shadow.tasks.JavaJarExec +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import com.github.jengelman.gradle.plugins.shadow.util.AppendableMavenFileRepository +import java.nio.file.Path +import java.util.jar.JarFile +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.appendText +import kotlin.io.path.createDirectories +import kotlin.io.path.createFile +import kotlin.io.path.createTempDirectory +import kotlin.io.path.deleteRecursively +import kotlin.io.path.exists +import kotlin.io.path.readText +import kotlin.io.path.toPath +import kotlin.io.path.writeText +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +abstract class BasePluginTest { + private lateinit var root: Path + lateinit var repo: AppendableMavenFileRepository + + @BeforeEach + open fun setup() { + root = createTempDirectory() + + repo = repo() + repo.module("junit", "junit", "3.8.2") + .use(testJar) + .publish() + + buildScript.writeText( + getProjectBuildScript( + groupInfo = "group = 'shadow'", + versionInfo = "version = '1.0'", + ), + ) + settingsScript.writeText(getSettingsBuildScript()) + } + + @ExperimentalPathApi + @AfterEach + fun cleanup() { + runCatching { + // TODO: workaround for https://github.com/junit-team/junit5/issues/2811. + root.deleteRecursively() + } + + println(buildScript.readText()) + } + + fun getProjectBuildScript( + javaPlugin: String = "java", + groupInfo: String = "", + versionInfo: String = "", + ): String { + return """ + plugins { + id('$javaPlugin') + id('com.gradleup.shadow') + } + $groupInfo + $versionInfo + """.trimIndent() + System.lineSeparator() + } + + fun getSettingsBuildScript( + startBlock: String = "", + endBlock: String = "rootProject.name = 'shadow'", + ): String { + return """ + $startBlock + dependencyResolutionManagement { + repositories { + maven { url = '${repo.uri}' } + mavenCentral() + } + } + $endBlock + """.trimIndent() + System.lineSeparator() + } + + fun publishArtifactA() { + repo.module("shadow", "a", "1.0") + .insertFile("a.properties", "a") + .insertFile("a2.properties", "a2") + .publish() + } + + fun publishArtifactB() { + repo.module("shadow", "b", "1.0") + .insertFile("b.properties", "b") + .publish() + } + + fun publishArtifactCD(circular: Boolean = false) { + repo.module("shadow", "c", "1.0") + .insertFile("c.properties", "c") + .apply { if (circular) dependsOn("d") } + .publish() + repo.module("shadow", "d", "1.0") + .insertFile("d.properties", "d") + .dependsOn("c") + .publish() + } + + open val shadowJarTask = SHADOW_JAR_TASK_NAME + open val runShadowTask = SHADOW_RUN_TASK_NAME + + val buildScript: Path + get() = path("build.gradle") + + val settingsScript: Path + get() = path("settings.gradle") + + val outputShadowJar: Path + get() = path("build/libs/shadow-1.0-all.jar") + + fun path(path: String): Path { + return root.resolve(path).also { + if (!it.exists()) { + it.parent.createDirectories() + it.createFile() + } + } + } + + fun repo(path: String = "maven-repo"): AppendableMavenFileRepository { + return AppendableMavenFileRepository(root.resolve(path)) + } + + fun assertContains(jarPath: Path, paths: List) { + JarFile(jarPath.toFile()).use { jar -> + paths.forEach { path -> + assert(jar.getJarEntry(path) != null) { "Jar file $jarPath does not contain entry $path" } + } + } + } + + fun assertDoesNotContain(jarPath: Path, paths: List) { + JarFile(jarPath.toFile()).use { jar -> + paths.forEach { path -> + assert(jar.getJarEntry(path) == null) { "Jar file $jarPath contains entry $path" } + } + } + } + + private val runner: GradleRunner + get() { + return GradleRunner.create() + .withProjectDir(root.toFile()) + .forwardOutput() + .withPluginClasspath() + .withTestKitDir(testKitDir.toFile()) + } + + fun runner(arguments: Iterable): GradleRunner { + val allArguments = listOf( + "--warning-mode=fail", + "--configuration-cache", + "--stacktrace", + ) + arguments + return runner.withArguments(allArguments) + } + + fun run(vararg tasks: String): BuildResult { + return run(tasks.toList()) + } + + inline fun run( + tasks: Iterable, + block: (GradleRunner) -> GradleRunner = { it }, + ): BuildResult { + return block(runner(tasks)).build().also { + it.assertNoDeprecationWarnings() + } + } + + fun writeClientAndServerModules( + serverShadowBlock: String = "", + ) { + settingsScript.appendText( + """ + include 'client', 'server' + """.trimIndent(), + ) + buildScript.writeText("") + + path("client/src/main/java/client/Client.java").writeText( + """ + package client; + public class Client {} + """.trimIndent(), + ) + path("client/build.gradle").writeText( + """ + ${getProjectBuildScript("java", versionInfo = "version = '1.0'")} + dependencies { implementation 'junit:junit:3.8.2' } + """.trimIndent(), + ) + + path("server/src/main/java/server/Server.java").writeText( + """ + package server; + import client.Client; + public class Server {} + """.trimIndent(), + ) + path("server/build.gradle").writeText( + """ + ${getProjectBuildScript("java", versionInfo = "version = '1.0'")} + dependencies { + implementation project(':client') + } + $shadowJar { + $serverShadowBlock + } + """.trimIndent(), + ) + } + + companion object { + val testKitDir: Path = run { + var gradleUserHome = System.getenv("GRADLE_USER_HOME") + if (gradleUserHome == null) { + gradleUserHome = Path(System.getProperty("user.home"), ".gradle").absolutePathString() + } + Path(gradleUserHome, "testkit") + } + + val testJar: Path = requireNotNull(this::class.java.classLoader.getResource("junit-3.8.2.jar")).toURI().toPath() + + val shadowJar: String = """ + tasks.named('$SHADOW_JAR_TASK_NAME', ${ShadowJar::class.java.name}) + """.trimIndent() + + val runShadow = """ + tasks.named('$SHADOW_RUN_TASK_NAME', ${JavaJarExec::class.java.name}) + """.trimIndent() + + fun BuildResult.assertNoDeprecationWarnings() { + output.lines().forEach { + assert(!containsDeprecationWarning(it)) + } + } + + private fun containsDeprecationWarning(output: String): Boolean { + return output.contains("has been deprecated and is scheduled to be removed in Gradle") || + output.contains("has been deprecated. This is scheduled to be removed in Gradle") + } + } +} diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheSpec.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheSpec.kt new file mode 100644 index 000000000..851994739 --- /dev/null +++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheSpec.kt @@ -0,0 +1,154 @@ +package com.github.jengelman.gradle.plugins.shadow + +import assertk.assertThat +import assertk.assertions.contains +import assertk.assertions.exists +import assertk.assertions.isEqualTo +import assertk.assertions.isNotNull +import kotlin.io.path.appendText +import kotlin.io.path.deleteExisting +import kotlin.io.path.writeText +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.TaskOutcome +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class ConfigurationCacheSpec : BasePluginTest() { + + @BeforeEach + override fun setup() { + super.setup() + publishArtifactA() + publishArtifactB() + buildScript.appendText( + """ + dependencies { + implementation 'shadow:a:1.0' + implementation 'shadow:b:1.0' + } + """.trimIndent() + System.lineSeparator(), + ) + } + + @Test + fun supportsConfigurationCache() { + path("src/main/java/myapp/Main.java").writeText( + """ + package myapp; + public class Main { + public static void main(String[] args) { + System.out.println("TestApp: Hello World! (" + args[0] + ")"); + } + } + """.trimIndent(), + ) + + buildScript.appendText( + """ + apply plugin: 'application' + + application { + mainClass = 'myapp.Main' + } + $runShadow { + args 'foo' + } + """.trimIndent(), + ) + + run(shadowJarTask) + val result = run(shadowJarTask) + + result.assertCcReused() + } + + @Test + fun configurationCachingSupportsExcludes() { + buildScript.appendText( + """ + $shadowJar { + exclude 'a2.properties' + } + """.trimIndent(), + ) + + run(shadowJarTask) + outputShadowJar.deleteExisting() + val result = run(shadowJarTask) + + assertContains( + outputShadowJar, + listOf("a.properties", "b.properties"), + ) + assertDoesNotContain( + outputShadowJar, + listOf("a2.properties"), + ) + result.assertCcReused() + } + + @Test + fun configurationCachingSupportsMinimize() { + writeClientAndServerModules( + serverShadowBlock = """ + minimize { + exclude(dependency('junit:junit:.*')) + } + """.trimIndent(), + ) + val output = path("server/build/libs/server-1.0-all.jar") + + run(shadowJarTask) + output.deleteExisting() + val result = run(shadowJarTask) + + assertThat(output).exists() + assertContains( + output, + listOf("server/Server.class", "junit/framework/Test.class"), + ) + assertDoesNotContain( + output, + listOf("client/Client.class"), + ) + result.assertCcReused() + } + + @Test + fun configurationCachingOfConfigurationsIsUpToDate() { + settingsScript.appendText( + """ + include 'lib' + """.trimIndent(), + ) + + path("lib/src/main/java/lib/Lib.java").writeText( + """ + package lib; + public class Lib {} + """.trimIndent(), + ) + path("lib/build.gradle").writeText( + """ + ${getProjectBuildScript()} + dependencies { + implementation 'junit:junit:3.8.2' + } + $shadowJar { + configurations = [project.configurations.compileClasspath] + } + """.trimIndent(), + ) + + run(shadowJarTask) + val result = run(shadowJarTask) + + assertThat(result.task(":lib:shadowJar")).isNotNull() + .transform { it.outcome }.isEqualTo(TaskOutcome.UP_TO_DATE) + result.assertCcReused() + } + + private fun BuildResult.assertCcReused() { + assertThat(output).contains("Reusing configuration cache.") + } +} diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt new file mode 100644 index 000000000..a639f7b11 --- /dev/null +++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt @@ -0,0 +1,288 @@ +package com.github.jengelman.gradle.plugins.shadow + +import assertk.assertThat +import assertk.assertions.exists +import assertk.assertions.isEqualTo +import assertk.assertions.isNotNull +import kotlin.io.path.appendText +import kotlin.io.path.readText +import kotlin.io.path.writeText +import org.gradle.testkit.runner.TaskOutcome +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class FilteringTest : BasePluginTest() { + + @BeforeEach + override fun setup() { + super.setup() + publishArtifactA() + publishArtifactB() + + buildScript.appendText( + """ + dependencies { + implementation 'shadow:a:1.0' + implementation 'shadow:b:1.0' + } + """.trimIndent() + System.lineSeparator(), + ) + } + + @Test + fun includeAllDependencies() { + run(shadowJarTask) + assertContains( + outputShadowJar, + listOf("a.properties", "a2.properties", "b.properties"), + ) + } + + @Test + fun excludeFiles() { + buildScript.appendText( + """ + $shadowJar { + exclude 'a2.properties' + } + """.trimIndent(), + ) + + run(shadowJarTask) + + assertContains( + outputShadowJar, + listOf("a.properties", "b.properties"), + ) + assertDoesNotContain( + outputShadowJar, + listOf("a2.properties"), + ) + } + + @Test + fun excludeDependency() { + publishArtifactCD() + dependOnAndExcludeArtifactD() + + run(shadowJarTask) + + commonAssertions() + } + + @Test + fun excludeDependencyUsingWildcardSyntax() { + publishArtifactCD() + buildScript.appendText( + """ + dependencies { + implementation 'shadow:d:1.0' + } + $shadowJar { + dependencies { + exclude(dependency('shadow:d:.*')) + } + } + """.trimIndent(), + ) + + run(shadowJarTask) + + commonAssertions() + } + + @Test + fun dependencyExclusionsAffectUpToDateCheck() { + publishArtifactCD() + dependOnAndExcludeArtifactD() + + run(shadowJarTask) + + commonAssertions() + + val replaced = buildScript.readText() + .replace("exclude(dependency('shadow:d:1.0'))", "exclude(dependency('shadow:c:1.0'))") + buildScript.writeText(replaced) + val result = run(shadowJarTask) + + assertThat(result.task(":shadowJar")).isNotNull() + .transform { it.outcome }.isEqualTo(TaskOutcome.SUCCESS) + assertContains( + outputShadowJar, + listOf("a.properties", "a2.properties", "b.properties", "d.properties"), + ) + assertDoesNotContain( + outputShadowJar, + listOf("c.properties"), + ) + } + + @Test + fun projectExclusionsAffectUpToDateCheck() { + publishArtifactCD() + dependOnAndExcludeArtifactD() + + run(shadowJarTask) + + commonAssertions() + + val replaced = buildScript.readText() + .replace("exclude(dependency('shadow:d:1.0'))", "exclude 'a.properties'") + buildScript.writeText(replaced) + + val result = run(shadowJarTask) + + assertThat(result.task(":shadowJar")).isNotNull() + .transform { it.outcome }.isEqualTo(TaskOutcome.SUCCESS) + assertContains( + outputShadowJar, + listOf("a2.properties", "b.properties", "c.properties", "d.properties"), + ) + assertDoesNotContain( + outputShadowJar, + listOf("a.properties"), + ) + } + + @Test + fun includeDependencyAndExcludeOthers() { + publishArtifactCD() + buildScript.appendText( + """ + dependencies { + implementation 'shadow:d:1.0' + } + $shadowJar { + dependencies { + include(dependency('shadow:d:1.0')) + } + } + """.trimIndent(), + ) + path("src/main/java/shadow/Passed.java").writeText( + """ + package shadow; + public class Passed {} + """.trimIndent(), + ) + + run(shadowJarTask) + + assertContains( + outputShadowJar, + listOf("d.properties", "shadow/Passed.class"), + ) + assertDoesNotContain( + outputShadowJar, + listOf("a.properties", "a2.properties", "b.properties", "c.properties"), + ) + } + + @Test + fun filterProjectDependencies() { + writeClientAndServerModules( + serverShadowBlock = """ + dependencies { + exclude(project(':client')) + } + """.trimIndent(), + ) + + val serverOutput = path("server/build/libs/server-1.0-all.jar") + run(":server:$shadowJarTask") + + assertThat(serverOutput).exists() + assertDoesNotContain( + serverOutput, + listOf("client/Client.class"), + ) + assertContains( + serverOutput, + listOf("server/Server.class", "junit/framework/Test.class"), + ) + } + + @Test + fun excludeATransitiveProjectDependency() { + writeClientAndServerModules( + serverShadowBlock = """ + dependencies { + exclude(dependency { it.moduleGroup == 'junit' }) + } + """.trimIndent(), + ) + + val serverOutput = path("server/build/libs/server-1.0-all.jar") + run(":server:$shadowJarTask") + + assertThat(serverOutput).exists() + assertDoesNotContain( + serverOutput, + listOf("junit/framework/Test.class"), + ) + assertContains( + serverOutput, + listOf("client/Client.class", "server/Server.class"), + ) + } + + @Test + fun verifyExcludePrecedenceOverInclude() { + buildScript.appendText( + """ + $shadowJar { + include '*.jar' + include '*.properties' + exclude 'a2.properties' + } + """.trimIndent(), + ) + + run(shadowJarTask) + + assertContains( + outputShadowJar, + listOf("a.properties", "b.properties"), + ) + assertDoesNotContain( + outputShadowJar, + listOf("a2.properties"), + ) + } + + @Test + fun handleExcludeWithCircularDependency() { + publishArtifactCD(circular = true) + dependOnAndExcludeArtifactD() + + run(shadowJarTask) + + commonAssertions() + } + + private fun dependOnAndExcludeArtifactD() { + buildScript.appendText( + """ + dependencies { + implementation 'shadow:d:1.0' + } + $shadowJar { + dependencies { + exclude(dependency('shadow:d:1.0')) + } + } + """.trimIndent(), + ) + } + + private fun commonAssertions() { + assertContains( + outputShadowJar, + listOf("a.properties", "a2.properties", "b.properties", "c.properties"), + ) + assertDoesNotContain( + outputShadowJar, + listOf("d.properties"), + ) + } +} diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt new file mode 100644 index 000000000..e502abfe4 --- /dev/null +++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -0,0 +1,390 @@ +package com.github.jengelman.gradle.plugins.shadow + +import assertk.assertFailure +import assertk.assertThat +import assertk.assertions.exists +import assertk.assertions.isEqualTo +import assertk.assertions.isInstanceOf +import java.net.URLClassLoader +import java.util.jar.JarFile +import kotlin.io.path.appendText +import kotlin.io.path.writeText +import org.junit.jupiter.api.Test + +class RelocationTest : BasePluginTest() { + + @Test + fun defaultEnableRelocation() { + buildScript.appendText( + """ + dependencies { + implementation 'junit:junit:3.8.2' + } + $shadowJar { + enableRelocation = true + } + """.trimIndent(), + ) + run(shadowJarTask) + + assertContains( + outputShadowJar, + listOf( + "META-INF/MANIFEST.MF", + "shadow/junit/textui/ResultPrinter.class", + "shadow/junit/textui/TestRunner.class", + "shadow/junit/framework/Assert.class", + "shadow/junit/framework/AssertionFailedError.class", + "shadow/junit/framework/ComparisonCompactor.class", + "shadow/junit/framework/ComparisonFailure.class", + "shadow/junit/framework/Protectable.class", + "shadow/junit/framework/Test.class", + "shadow/junit/framework/TestCase.class", + "shadow/junit/framework/TestFailure.class", + "shadow/junit/framework/TestListener.class", + "shadow/junit/framework/TestResult$1.class", + "shadow/junit/framework/TestResult.class", + "shadow/junit/framework/TestSuite$1.class", + "shadow/junit/framework/TestSuite.class", + ), + ) + } + + /** + * https://github.com/GradleUp/shadow/issues/58 + */ + @Test + fun relocateDependencyFiles() { + buildScript.appendText( + """ + dependencies { + implementation 'junit:junit:3.8.2' + } + $shadowJar { + relocate 'junit.textui', 'a' + relocate 'junit.framework', 'b' + manifest { + attributes 'TEST-VALUE': 'FOO' + } + } + """.trimIndent(), + ) + + run(shadowJarTask) + + assertContains( + outputShadowJar, + listOf( + "META-INF/MANIFEST.MF", + "a/ResultPrinter.class", + "a/TestRunner.class", + "b/Assert.class", + "b/AssertionFailedError.class", + "b/ComparisonCompactor.class", + "b/ComparisonFailure.class", + "b/Protectable.class", + "b/Test.class", + "b/TestCase.class", + "b/TestFailure.class", + "b/TestListener.class", + "b/TestResult\$1.class", + "b/TestResult.class", + "b/TestSuite\$1.class", + "b/TestSuite.class", + ), + ) + + assertDoesNotContain( + outputShadowJar, + listOf( + "junit/textui/ResultPrinter.class", + "junit/textui/TestRunner.class", + "junit/framework/Assert.class", + "junit/framework/AssertionFailedError.class", + "junit/framework/ComparisonCompactor.class", + "junit/framework/ComparisonFailure.class", + "junit/framework/Protectable.class", + "junit/framework/Test.class", + "junit/framework/TestCase.class", + "junit/framework/TestFailure.class", + "junit/framework/TestListener.class", + "junit/framework/TestResult\$1.class", + "junit/framework/TestResult.class", + "junit/framework/TestSuite\$1.class", + "junit/framework/TestSuite.class", + ), + ) + + val jarFile = JarFile(outputShadowJar.toFile()) + assertThat(jarFile.manifest.mainAttributes.getValue("TEST-VALUE")).isEqualTo("FOO") + } + + @Test + fun relocateDependencyFilesWithFiltering() { + buildScript.appendText( + """ + dependencies { + implementation 'junit:junit:3.8.2' + } + $shadowJar { + relocate('junit.textui', 'a') { + exclude 'junit.textui.TestRunner' + } + relocate('junit.framework', 'b') { + include 'junit.framework.Test*' + } + } + """.trimIndent(), + ) + + run(shadowJarTask) + + assertContains( + outputShadowJar, + listOf( + "a/ResultPrinter.class", + "b/Test.class", + "b/TestCase.class", + "b/TestFailure.class", + "b/TestListener.class", + "b/TestResult\$1.class", + "b/TestResult.class", + "b/TestSuite\$1.class", + "b/TestSuite.class", + ), + ) + + assertDoesNotContain( + outputShadowJar, + listOf( + "a/TestRunner.class", + "b/Assert.class", + "b/AssertionFailedError.class", + "b/ComparisonCompactor.class", + "b/ComparisonFailure.class", + "b/Protectable.class", + ), + ) + + assertContains( + outputShadowJar, + listOf( + "junit/textui/TestRunner.class", + "junit/framework/Assert.class", + "junit/framework/AssertionFailedError.class", + "junit/framework/ComparisonCompactor.class", + "junit/framework/ComparisonFailure.class", + "junit/framework/Protectable.class", + ), + ) + } + + /** + * https://github.com/GradleUp/shadow/issues/53 + * https://github.com/GradleUp/shadow/issues/55 + */ + @Test + fun remapClassNamesForRelocatedFilesInProjectSource() { + buildScript.appendText( + """ + dependencies { + implementation 'junit:junit:3.8.2' + } + $shadowJar { + relocate 'junit.framework', 'shadow.junit' + } + """.trimIndent(), + ) + + path("src/main/java/shadow/ShadowTest.java").writeText( + """ + package shadow; + + import junit.framework.Test; + import junit.framework.TestResult; + + public class ShadowTest implements Test { + public int countTestCases() { return 0; } + public void run(TestResult result) { } + } + """.trimIndent(), + ) + + run(shadowJarTask) + + assertContains( + outputShadowJar, + listOf( + "shadow/ShadowTest.class", + "shadow/junit/Test.class", + "shadow/junit", + ), + ) + + assertDoesNotContain( + outputShadowJar, + listOf( + "junit/framework", + "junit/framework/Test.class", + ), + ) + + val classLoader = URLClassLoader( + arrayOf(outputShadowJar.toUri().toURL()), + ClassLoader.getSystemClassLoader().parent, + ) + assertFailure { + // check that the class can be loaded. If the file was not relocated properly, we should get a NoDefClassFound + // Isolated class loader with only the JVM system jars and the output jar from the test project + classLoader.loadClass("shadow.ShadowTest") + error("Should not reach here.") + }.isInstanceOf(IllegalStateException::class) + } + + @Test + fun relocateDoesNotDropDependencyResources() { + path("core/build.gradle").writeText( + """ + plugins { + id 'java-library' + } + dependencies { + api 'junit:junit:3.8.2' + } + """.trimIndent(), + ) + + path("core/src/main/resources/TEST").writeText("TEST RESOURCE") + path("core/src/main/resources/test.properties").writeText("name=test") + path("core/src/main/java/core/Core.java").writeText( + """ + package core; + + import junit.framework.Test; + + public class Core {} + """.trimIndent(), + ) + + path("app/build.gradle").writeText( + """ + ${getProjectBuildScript()} + dependencies { + implementation project(':core') + } + $shadowJar { + relocate 'core', 'app.core' + relocate 'junit.framework', 'app.junit.framework' + } + """.trimIndent(), + ) + path("app/src/main/resources/APP-TEST").writeText("APP TEST RESOURCE") + path("app/src/main/java/app/App.java").writeText( + """ + package app; + + import core.Core; + import junit.framework.Test; + + public class App {} + """.trimIndent(), + ) + + settingsScript.appendText( + """ + include 'core', 'app' + """.trimIndent(), + ) + + run(":app:$shadowJarTask") + + val appOutput = path("app/build/libs/app-all.jar") + assertThat(appOutput).exists() + + assertContains( + appOutput, + listOf( + "TEST", + "APP-TEST", + "test.properties", + "app/core/Core.class", + "app/App.class", + "app/junit/framework/Test.class", + ), + ) + } + + /** + * https://github.com/GradleUp/shadow/issues/93 + * https://github.com/GradleUp/shadow/issues/114 + */ + @Test + fun relocateResourceFiles() { + repo.module("shadow", "dep", "1.0") + .insertFile("foo/dep.properties", "c") + .publish() + path("src/main/java/foo/Foo.java").writeText( + """ + package foo; + + class Foo {} + """.trimIndent(), + ) + path("src/main/resources/foo/foo.properties").writeText("name=foo") + + buildScript.appendText( + """ + dependencies { + implementation 'shadow:dep:1.0' + } + $shadowJar { + relocate 'foo', 'bar' + } + """.trimIndent(), + ) + + run(shadowJarTask) + + assertContains( + outputShadowJar, + listOf( + "bar/Foo.class", + "bar/foo.properties", + "bar/dep.properties", + ), + ) + assertDoesNotContain( + outputShadowJar, + listOf( + "foo/Foo.class", + "foo/foo.properties", + "foo/dep.properties", + ), + ) + } + + /** + * https://github.com/GradleUp/shadow/issues/294 + */ + @Test + fun doesNotErrorOnRelocatingJava9Classes() { + buildScript.appendText( + """ + dependencies { + implementation 'org.slf4j:slf4j-api:1.7.21' + implementation group: 'io.netty', name: 'netty-all', version: '4.0.23.Final' + implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '2.5.0' + implementation group: 'org.apache.zookeeper', name: 'zookeeper', version: '3.4.6' + } + $shadowJar { + zip64 = true + relocate 'com.google.protobuf', 'shaded.com.google.protobuf' + relocate 'io.netty', 'shaded.io.netty' + } + """.trimIndent(), + ) + + run(shadowJarTask) + // No exception should be thrown + } +} diff --git a/src/intiTest/resources/junit-3.8.2.jar b/src/intiTest/resources/junit-3.8.2.jar new file mode 100644 index 000000000..c8f711d05 Binary files /dev/null and b/src/intiTest/resources/junit-3.8.2.jar differ diff --git a/src/intiTest/resources/test-artifact-1.0-SNAPSHOT.jar b/src/intiTest/resources/test-artifact-1.0-SNAPSHOT.jar new file mode 100644 index 000000000..009abad49 Binary files /dev/null and b/src/intiTest/resources/test-artifact-1.0-SNAPSHOT.jar differ diff --git a/src/intiTest/resources/test-project-1.0-SNAPSHOT.jar b/src/intiTest/resources/test-project-1.0-SNAPSHOT.jar new file mode 100644 index 000000000..f80e03f90 Binary files /dev/null and b/src/intiTest/resources/test-project-1.0-SNAPSHOT.jar differ