├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md ├── renovate.json5 └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── build.gradle ├── buildSrc └── build.gradle ├── gradle.properties ├── gradle ├── dependencies.gradle ├── docs.gradle ├── ghPages.gradle ├── publish.gradle ├── vuepress.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── package.json ├── settings.gradle ├── src ├── docs │ ├── .vuepress │ │ ├── components │ │ │ └── ApiLink.vue │ │ ├── config.js │ │ ├── override.styl │ │ ├── public │ │ │ ├── logo+type.orig.svg │ │ │ ├── logo+type.svg │ │ │ ├── logo.orig.svg │ │ │ └── logo.svg │ │ └── style.styl │ ├── README.md │ ├── about │ │ └── README.md │ ├── application-plugin │ │ └── README.md │ ├── changes │ │ └── README.md │ ├── configuration │ │ ├── README.md │ │ ├── dependencies │ │ │ └── README.md │ │ ├── filtering │ │ │ └── README.md │ │ ├── merging │ │ │ └── README.md │ │ ├── minimizing │ │ │ └── README.md │ │ ├── relocation │ │ │ └── README.md │ │ └── reproducible-builds │ │ │ └── README.md │ ├── custom-tasks │ │ └── README.md │ ├── getting-started │ │ └── README.md │ ├── introduction │ │ └── README.md │ ├── multi-project │ │ └── README.md │ ├── plugins │ │ └── README.md │ └── publishing │ │ └── README.md ├── main │ ├── groovy │ │ └── com │ │ │ └── github │ │ │ └── jengelman │ │ │ └── gradle │ │ │ └── plugins │ │ │ └── shadow │ │ │ ├── ShadowApplicationPlugin.groovy │ │ │ ├── ShadowBasePlugin.groovy │ │ │ ├── ShadowExtension.groovy │ │ │ ├── ShadowJavaPlugin.groovy │ │ │ ├── ShadowPlugin.groovy │ │ │ ├── ShadowStats.groovy │ │ │ ├── impl │ │ │ └── RelocatorRemapper.groovy │ │ │ ├── internal │ │ │ ├── AbstractDependencyFilter.groovy │ │ │ ├── CleanProperties.groovy │ │ │ ├── DefaultDependencyFilter.groovy │ │ │ ├── DefaultZipCompressor.groovy │ │ │ ├── DependencyFilter.groovy │ │ │ ├── GradleVersionUtil.groovy │ │ │ ├── JavaJarExec.groovy │ │ │ ├── MinimizeDependencyFilter.groovy │ │ │ ├── RelocationUtil.groovy │ │ │ ├── UnusedTracker.groovy │ │ │ └── ZipCompressor.groovy │ │ │ ├── relocation │ │ │ ├── CacheableRelocator.groovy │ │ │ ├── RelocateClassContext.groovy │ │ │ ├── RelocatePathContext.groovy │ │ │ ├── Relocator.groovy │ │ │ └── SimpleRelocator.groovy │ │ │ ├── tasks │ │ │ ├── DefaultInheritManifest.groovy │ │ │ ├── InheritManifest.groovy │ │ │ ├── KnowsTask.groovy │ │ │ ├── ShadowCopyAction.groovy │ │ │ ├── ShadowJar.java │ │ │ └── ShadowSpec.java │ │ │ └── transformers │ │ │ ├── ApacheLicenseResourceTransformer.groovy │ │ │ ├── ApacheNoticeResourceTransformer.groovy │ │ │ ├── AppendingTransformer.groovy │ │ │ ├── CacheableTransformer.groovy │ │ │ ├── ComponentsXmlResourceTransformer.groovy │ │ │ ├── DontIncludeResourceTransformer.groovy │ │ │ ├── GroovyExtensionModuleTransformer.groovy │ │ │ ├── IncludeResourceTransformer.groovy │ │ │ ├── Log4j2PluginsCacheFileTransformer.groovy │ │ │ ├── ManifestAppenderTransformer.groovy │ │ │ ├── ManifestResourceTransformer.groovy │ │ │ ├── PropertiesFileTransformer.groovy │ │ │ ├── ServiceFileTransformer.groovy │ │ │ ├── Transformer.groovy │ │ │ ├── TransformerContext.groovy │ │ │ └── XmlAppendingTransformer.groovy │ └── resources │ │ ├── com │ │ └── github │ │ │ └── jengelman │ │ │ └── gradle │ │ │ └── plugins │ │ │ └── shadow │ │ │ └── internal │ │ │ ├── unixStartScript.txt │ │ │ └── windowsStartScript.txt │ │ ├── shadow-version.txt │ │ └── shadowBanner.txt └── test │ ├── groovy │ └── com │ │ └── github │ │ └── jengelman │ │ └── gradle │ │ └── plugins │ │ └── shadow │ │ ├── ApplicationSpec.groovy │ │ ├── ConfigurationCacheSpec.groovy │ │ ├── ConfigureShadowRelocationSpec.groovy │ │ ├── FilteringSpec.groovy │ │ ├── PublishingSpec.groovy │ │ ├── RelocationSpec.groovy │ │ ├── ShadowPluginSpec.groovy │ │ ├── TransformerSpec.groovy │ │ ├── caching │ │ ├── AbstractCachingSpec.groovy │ │ ├── MinimizationCachingSpec.groovy │ │ ├── RelocationCachingSpec.groovy │ │ ├── ShadowJarCachingSpec.groovy │ │ └── TransformCachingSpec.groovy │ │ ├── docs │ │ ├── ManualCodeSnippetTests.groovy │ │ ├── executer │ │ │ ├── GradleBuildExecuter.groovy │ │ │ └── NoopExecuter.groovy │ │ ├── extractor │ │ │ └── ManualSnippetExtractor.groovy │ │ ├── fixture │ │ │ └── GroovyDslFixture.groovy │ │ └── internal │ │ │ ├── Block.java │ │ │ └── snippets │ │ │ ├── CodeSnippetTestCase.java │ │ │ ├── CodeSnippetTests.java │ │ │ ├── DefaultCodeSnippetTests.groovy │ │ │ ├── TestCodeSnippet.java │ │ │ ├── executer │ │ │ ├── CompileException.java │ │ │ ├── ExceptionTransformer.groovy │ │ │ └── SnippetExecuter.java │ │ │ ├── fixture │ │ │ ├── GroovyScriptFixture.groovy │ │ │ └── SnippetFixture.java │ │ │ └── junit │ │ │ ├── DelegatingTestRunner.java │ │ │ ├── RunnerProvider.java │ │ │ └── SnippetRunner.java │ │ ├── relocation │ │ ├── SimpleRelocatorParameterTest.groovy │ │ └── SimpleRelocatorTest.groovy │ │ ├── transformers │ │ ├── ApacheLicenseResourceTransformerTest.groovy │ │ ├── ApacheNoticeResourceTransformerParameterTests.groovy │ │ ├── ApacheNoticeResourceTransformerTest.groovy │ │ ├── AppendingTransformerTest.groovy │ │ ├── ComponentsXmlResourceTransformerTest.groovy │ │ ├── Log4j2PluginsCacheFileTransformerSpec.groovy │ │ ├── ManifestAppenderTransformerTest.groovy │ │ ├── PropertiesFileTransformerSpec.groovy │ │ ├── PropertiesFileTransformerTest.groovy │ │ ├── ServiceFileTransformerSpec.groovy │ │ ├── TransformerSpecSupport.groovy │ │ ├── TransformerTestSupport.groovy │ │ └── XmlAppendingTransformerTest.groovy │ │ └── util │ │ ├── AppendableJar.groovy │ │ ├── AppendableMavenFileModule.groovy │ │ ├── AppendableMavenFileRepository.groovy │ │ ├── HashUtil.java │ │ ├── HashValue.java │ │ ├── JarBuilder.groovy │ │ ├── PluginSpecification.groovy │ │ ├── file │ │ ├── ExecOutput.groovy │ │ ├── Results.groovy │ │ ├── TestDirectoryProvider.java │ │ ├── TestFile.java │ │ ├── TestFileHelper.groovy │ │ ├── TestNameTestDirectoryProvider.java │ │ └── TestWorkspaceBuilder.groovy │ │ └── repo │ │ ├── AbstractModule.groovy │ │ └── maven │ │ ├── AbstractMavenModule.groovy │ │ ├── DefaultMavenMetaData.groovy │ │ ├── MavenDependency.groovy │ │ ├── MavenFileModule.groovy │ │ ├── MavenFileRepository.groovy │ │ ├── MavenMetaData.groovy │ │ ├── MavenModule.groovy │ │ ├── MavenPom.groovy │ │ ├── MavenRepository.groovy │ │ └── MavenScope.groovy │ ├── jars │ ├── plexus-utils-1.4.1.jar │ ├── test-artifact-1.0-SNAPSHOT.jar │ └── test-project-1.0-SNAPSHOT.jar │ └── resources │ ├── components-1.xml │ ├── components-2.xml │ ├── components-expected.xml │ ├── junit-3.8.2.jar │ ├── test-artifact-1.0-SNAPSHOT.jar │ └── test-project-1.0-SNAPSHOT.jar └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | gradlew binary 4 | gradlew.bat binary -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please check the [User Guide](http://imperceptiblethoughts.com/shadow) before submitting "how do I do 'x'?" questions! 2 | 3 | ### Shadow Version 4 | 5 | ### Gradle Version 6 | 7 | ### Expected Behavior 8 | 9 | ### Actual Behavior 10 | 11 | ### Gradle Build Script(s) 12 | 13 | ### Content of Shadow JAR (`jar tf ` - post link to GIST if too long) 14 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base", 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | matrix: 16 | os: [ ubuntu-latest, windows-latest ] 17 | # Always test on the latest version and all LTS. 18 | java: [ 11, 17, 21, 22 ] 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: actions/setup-java@v4 23 | with: 24 | distribution: 'zulu' 25 | java-version: ${{ matrix.java }} 26 | - uses: gradle/actions/setup-gradle@v3 27 | with: 28 | gradle-home-cache-cleanup: true 29 | validate-wrappers: true 30 | - run: ./gradlew build 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.un~ 2 | *.iml 3 | *.ipr 4 | *.iws 5 | .idea 6 | build 7 | .gradle 8 | *.orig 9 | out 10 | .gradletasknamecache 11 | .gradle-test-kit 12 | classes/ 13 | node_modules/ 14 | yarn-error.log 15 | src/docs/.vuepress/dist/ 16 | .DS_Store 17 | jd-gui.cfg 18 | bin/ 19 | .vscode/ 20 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Gradle-Shadow-Plugin 2 | Copyright (c) 2013 John Engelman All Rights Reserved. 3 | 4 | This product is licensed to you under the Apache License, Version 2.0 (the "License"). 5 | You may not use this product except in compliance with the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gradle Shadow 2 | 3 | Gradle plugin for creating fat/uber JARs with support for package relocation. 4 | 5 | **DEPRECATED** Changes in this repo have been ported to GradleUp. See [GradleUp/shadow](https://github.com/GradleUp/shadow) for info. 6 | 7 | ## Documentation 8 | 9 | Read the [User Guide](https://imperceptiblethoughts.com/shadow/)! 10 | 11 | ## Current Status 12 | 13 | [![Download](https://img.shields.io/gradle-plugin-portal/v/com.github.johnrengelman.shadow)](https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow) 14 | [![CI](https://github.com/GradleUp/shadow/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/GradleUp/shadow/actions/workflows/ci.yml?query=branch:main+event:push) 15 | [![License](https://img.shields.io/github/license/GradleUp/shadow.svg)](LICENSE) 16 | 17 | ## Latest Test Compatibility 18 | 19 | | Gradle Version | Shadow Version | 20 | |----------------|----------------| 21 | | 5.x | 5.2.0 - 6.0.0 | 22 | | 6.x | 5.2.0 - 6.1.0 | 23 | | 7.x | 7.0.0+ | 24 | | 8.x | 8.0.0+ | 25 | 26 | **NOTE**: Shadow v5.+ is compatible with Gradle 5.x - 6.x and Java 7 - 15 _only_, v6.1.0+ requires Java 8+. 27 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin 2 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 3 | 4 | plugins { 5 | id 'groovy' 6 | id 'project-report' 7 | id 'idea' 8 | id 'java-gradle-plugin' 9 | id 'signing' 10 | id 'com.gradle.plugin-publish' version '1.2.1' 11 | id 'org.ajoberstar.git-publish' version '4.2.2' 12 | id 'com.github.node-gradle.node' version '7.0.2' 13 | } 14 | 15 | apply plugin: ShadowPlugin 16 | 17 | apply from: file('gradle/docs.gradle') 18 | apply from: file('gradle/publish.gradle') 19 | apply from: file('gradle/vuepress.gradle') 20 | apply from: file('gradle/ghPages.gradle') 21 | apply from: file('gradle/dependencies.gradle') 22 | 23 | tasks.withType(Test).configureEach { 24 | useJUnitPlatform() 25 | 26 | // https://docs.gradle.org/8.8/userguide/performance.html#execute_tests_in_parallel 27 | maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 28 | 29 | if (System.env.CI == 'true') { 30 | testLogging.showStandardStreams = true 31 | minHeapSize "1g" 32 | maxHeapSize "1g" 33 | } 34 | 35 | systemProperty 'java.io.tmpdir', project.layout.buildDirectory.asFile.get().absolutePath 36 | 37 | // Required to test configuration cache in tests when using withDebug() 38 | // https://github.com/gradle/gradle/issues/22765#issuecomment-1339427241 39 | jvmArgs( 40 | "--add-opens", "java.base/java.util=ALL-UNNAMED", 41 | "--add-opens", "java.base/java.util.concurrent.atomic=ALL-UNNAMED", 42 | "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED", 43 | "--add-opens", "java.base/java.net=ALL-UNNAMED", 44 | ) 45 | } 46 | 47 | // Remove the gradleApi so it isn't merged into the jar file. 48 | // This is required because 'java-gradle-plugin' adds gradleApi() to the 'api' configuration. 49 | // See https://github.com/gradle/gradle/blob/972c3e5c6ef990dd2190769c1ce31998a9402a79/subprojects/plugin-development/src/main/java/org/gradle/plugin/devel/plugins/JavaGradlePluginPlugin.java#L161 50 | configurations.named(JavaPlugin.API_CONFIGURATION_NAME) { 51 | dependencies.remove(project.dependencies.gradleApi()) 52 | } 53 | 54 | tasks.named('shadowJar', ShadowJar) { 55 | from rootProject.file('LICENSE') 56 | from rootProject.file('NOTICE') 57 | enableRelocation true 58 | } 59 | 60 | idea { 61 | project { 62 | languageLevel = '1.8' 63 | } 64 | } 65 | 66 | tasks.named('ideaModule') { 67 | notCompatibleWithConfigurationCache("https://github.com/gradle/gradle/issues/13480") 68 | } 69 | 70 | sourceCompatibility = '1.8' 71 | targetCompatibility = '1.8' 72 | 73 | tasks.register('downloadDependencies', Exec) { 74 | dependsOn configurations.testRuntimeClasspath 75 | commandLine 'echo', 'Downloaded all dependencies' 76 | } 77 | -------------------------------------------------------------------------------- /buildSrc/build.gradle: -------------------------------------------------------------------------------- 1 | import org.codehaus.groovy.control.CompilerConfiguration 2 | 3 | plugins { 4 | id 'groovy' 5 | } 6 | 7 | repositories { 8 | gradlePluginPortal() 9 | mavenCentral() 10 | } 11 | 12 | ScriptHolder holder = new ScriptHolder() 13 | CompilerConfiguration cc = new CompilerConfiguration() 14 | cc.setScriptBaseClass(DelegatingScript.class.name) 15 | GroovyShell sh = new GroovyShell(Project.class.classLoader, new Binding(), cc) 16 | 17 | //The build file for the main project 18 | File projectBuildFile = file('../gradle/dependencies.gradle') 19 | 20 | //Use this parse command because Groovy wants to use the file name as the classname 21 | //which fails if your Gradle build file has been renamed to contain an invalid character (i.e. '-') 22 | DelegatingScript script = (DelegatingScript)sh.parse(projectBuildFile.text.replaceAll('shadow', 'implementation'), 'GradlePlugins') 23 | script.setDelegate(holder) 24 | 25 | //Resolve the project main Gradle file against our ScriptHolder 26 | script.run() 27 | 28 | //Class for holding the evaluation of a Gradle script 29 | //You may need to add some extra methods here depending on what you have all placed in build.gradle 30 | class ScriptHolder { 31 | Closure dependencies 32 | 33 | void dependencies(Closure c) { 34 | this.dependencies = c 35 | } 36 | 37 | void apply(Map map) { 38 | 39 | } 40 | } 41 | 42 | //Grab the dependencies closure and resolve it against 43 | //the buildSrc project dependencies 44 | //This effectively applies the same dependencies from build.gradle into buildSrc/build.gradle 45 | //This is required so that when buildSrc is compiled it has the dependencies to compile the source code 46 | def closure = holder.dependencies.clone() 47 | closure.delegate = project.dependencies 48 | closure() 49 | 50 | sourceSets { 51 | main { 52 | java.srcDirs = ['../src/main/java'] 53 | groovy.srcDirs = ['../src/main/groovy'] 54 | resources.srcDirs = ['../src/main/resources'] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx4g -XX:MaxMetaspaceSize=2g 2 | org.gradle.parallel=true 3 | org.gradle.caching=true 4 | org.gradle.configuration-cache=true -------------------------------------------------------------------------------- /gradle/dependencies.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compileOnly localGroovy() 3 | compileOnly gradleApi() 4 | shadow 'org.codehaus.groovy:groovy-backports-compat23:3.0.8' 5 | 6 | implementation 'org.jdom:jdom2:2.0.6.1' 7 | implementation 'org.ow2.asm:asm:9.7' 8 | implementation 'org.ow2.asm:asm-commons:9.7' 9 | implementation 'commons-io:commons-io:2.16.1' 10 | implementation 'org.apache.ant:ant:1.10.14' 11 | implementation 'org.codehaus.plexus:plexus-utils:4.0.1' 12 | implementation 'org.codehaus.plexus:plexus-xml:4.0.4' 13 | implementation "org.apache.logging.log4j:log4j-core:2.23.1" 14 | implementation('org.vafer:jdependency:2.10') { 15 | exclude group: 'org.ow2.asm' 16 | } 17 | 18 | testImplementation('org.spockframework:spock-core:2.3-groovy-3.0') { 19 | exclude group: 'org.codehaus.groovy' 20 | } 21 | testImplementation 'org.spockframework:spock-junit4:2.3-groovy-3.0' 22 | testImplementation 'xmlunit:xmlunit:1.6' 23 | testImplementation 'org.apache.commons:commons-lang3:3.15.0' 24 | testImplementation 'com.google.guava:guava:33.2.1-jre' 25 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.3' 26 | testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.10.3' 27 | testRuntimeOnly 'org.junit.platform:junit-platform-launcher' 28 | } 29 | -------------------------------------------------------------------------------- /gradle/docs.gradle: -------------------------------------------------------------------------------- 1 | def javaApiUrl = 'https://docs.oracle.com/javase/17/docs/api' 2 | def groovyApiUrl = "https://docs.groovy-lang.org/2.4.7/html/gapi/" 3 | 4 | tasks.withType(Javadoc).configureEach { 5 | classpath += project.configurations.shadow 6 | options.links(javaApiUrl, groovyApiUrl) 7 | if (JavaVersion.current().java8Compatible) { 8 | options.addStringOption('Xdoclint:none', '-quiet') 9 | } 10 | } 11 | 12 | java { 13 | withJavadocJar() 14 | withSourcesJar() 15 | } 16 | 17 | tasks.named('groovydoc') { 18 | classpath += project.configurations.shadow 19 | } 20 | 21 | tasks.named('build') { dependsOn javadocJar, sourcesJar } 22 | -------------------------------------------------------------------------------- /gradle/ghPages.gradle: -------------------------------------------------------------------------------- 1 | import org.apache.tools.ant.filters.ReplaceTokens 2 | 3 | apply plugin: 'org.ajoberstar.git-publish' 4 | 5 | if (project.hasProperty('githubToken')) { 6 | System.setProperty('org.ajoberstar.grgit.auth.username', project.property('githubToken') as String) 7 | } 8 | 9 | gitPublish { 10 | repoUri = 'https://github.com/johnrengelman/shadow.git' 11 | 12 | branch = 'gh-pages' 13 | 14 | contents { 15 | from 'build/site' 16 | into('.circleci') { 17 | from '.circleci' 18 | } 19 | into('api') { 20 | from project.tasks.groovydoc 21 | } 22 | filter(ReplaceTokens, tokens: [version: project.version]) 23 | } 24 | } 25 | 26 | tasks.named('gitPublishCopy') { dependsOn tasks.named('yarn_build') } 27 | tasks.named('gitPublishCopy') { dependsOn tasks.named('groovydoc') } 28 | -------------------------------------------------------------------------------- /gradle/publish.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.gradle.plugin-publish" 2 | 3 | group = 'com.github.johnrengelman' 4 | 5 | if (System.env.CIRCLE_TAG && System.env.CIRCLE_TAG =~ /^\d\.\d\.\d$/) { 6 | version = System.env.CIRCLE_TAG 7 | } else { 8 | version = file('src/main/resources/shadow-version.txt').text.trim() 9 | if (!version.endsWith("-SNAPSHOT")) { 10 | version = version + "-SNAPSHOT" 11 | } 12 | } 13 | 14 | gradlePlugin { 15 | website = 'https://github.com/johnrengelman/shadow' 16 | vcsUrl = 'https://github.com/johnrengelman/shadow' 17 | 18 | plugins { 19 | shadowPlugin { 20 | id = 'com.github.johnrengelman.shadow' 21 | implementationClass = 'com.github.jengelman.gradle.plugins.shadow.ShadowPlugin' 22 | displayName = 'Shadow Plugin' 23 | description = "Gradle plugin to create fat/uber JARs, apply file transforms, and relocate packages for applications and libraries. Gradle version of Maven's Shade plugin." 24 | tags.set(['onejar', 'shade', 'fatjar', 'uberjar']) 25 | } 26 | } 27 | } 28 | 29 | tasks.named('publishPlugins') { 30 | doFirst { 31 | if (version.endsWith("SNAPSHOT")) { 32 | throw new GradleException('Cannot publish SNAPSHOT versions to Plugin Portal!') 33 | } 34 | } 35 | notCompatibleWithConfigurationCache("https://github.com/gradle/gradle/issues/21283") 36 | } 37 | 38 | signing { 39 | if (System.env.CI == 'true') { 40 | def encodedSigningKey = findProperty("signingKey") 41 | def signingKey = encodedSigningKey ? new String(encodedSigningKey.decodeBase64()) : null 42 | def signingPassword = findProperty("signingPassword") as String 43 | useInMemoryPgpKeys(signingKey, signingPassword) 44 | } 45 | // See https://github.com/johnrengelman/shadow/pull/831#discussion_r1119012328 46 | required = false && gradle.taskGraph.hasTask("artifactoryPublish") 47 | sign(publishing.publications) 48 | } 49 | 50 | tasks.register('release') { 51 | dependsOn tasks.named('assemble'), tasks.named('publishPlugins'), tasks.named('gitPublishPush') 52 | } 53 | -------------------------------------------------------------------------------- /gradle/vuepress.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.github.node-gradle.node" 2 | 3 | node { 4 | yarnVersion = '1.5.1' 5 | } 6 | 7 | tasks.named('yarn_build') { 8 | inputs.files project.fileTree('src/docs') 9 | outputs.dir project.file('build/site') 10 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goooler/shadow/fee4caf3a1035b9d1182fb45cadadadcdb89185c/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shadow", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "git@github.com:johnrengelman/shadow.git", 6 | "author": "John Engelman ", 7 | "license": "MIT", 8 | "devDependencies": { 9 | "prismjs": "^1.20.0", 10 | "vuepress": "^1.5.0" 11 | }, 12 | "scripts": { 13 | "build": "vuepress build src/docs", 14 | "start": "vuepress dev src/docs" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | } 7 | 8 | plugins { 9 | id 'com.gradle.develocity' version '3.17.6' 10 | } 11 | 12 | dependencyResolutionManagement { 13 | repositories { 14 | mavenCentral() 15 | } 16 | } 17 | 18 | def isCI = providers.environmentVariable('CI').present 19 | 20 | develocity { 21 | buildScan { 22 | termsOfUseUrl = 'https://gradle.com/terms-of-service' 23 | termsOfUseAgree = 'yes' 24 | publishing.onlyIf { isCI } 25 | if (isCI) { 26 | tag 'CI' 27 | if (System.env.CIRCLE_TAG) { 28 | link 'VCS', "https://github.com/johnrengelman/shadow/tree/${System.env.CIRCLE_TAG}" 29 | } else { 30 | link 'VCS', "https://github.com/johnrengelman/shadow/tree/${System.env.CIRCLE_BRANCH}" 31 | } 32 | link 'VCS Commit', "https://github.com/johnrengelman/shadow/commit/${System.env.CIRCLE_SHA1}" 33 | if (System.env.CI_PULL_REQUEST) { 34 | link 'Pull Request', "${System.env.CI_PULL_REQUEST}" 35 | } 36 | } 37 | } 38 | } 39 | 40 | enableFeaturePreview("STABLE_CONFIGURATION_CACHE") 41 | 42 | rootProject.name = 'shadow' -------------------------------------------------------------------------------- /src/docs/.vuepress/components/ApiLink.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | base: "/shadow/", 3 | dest: "build/site", 4 | ga: "UA-321220-4", 5 | themeConfig: { 6 | repo: "johnrengelman/shadow", 7 | editLinks: true, 8 | editLinkText: 'Help improve these docs!', 9 | logo: '/logo+type.svg', 10 | docsDir: 'src/docs', 11 | title: 'Gradle Shadow Plugin', 12 | nav: [ 13 | { text: 'User Guide', link: '/introduction/' } 14 | ], 15 | sidebar: [ 16 | '/', 17 | '/introduction/', 18 | '/getting-started/', 19 | '/configuration/', 20 | '/configuration/filtering/', 21 | '/configuration/dependencies/', 22 | '/configuration/merging/', 23 | '/configuration/relocation/', 24 | '/configuration/minimizing/', 25 | '/configuration/reproducible-builds/', 26 | '/custom-tasks/', 27 | '/application-plugin/', 28 | '/publishing/', 29 | '/multi-project/', 30 | '/plugins/', 31 | '/changes/', 32 | '/about/' 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/docs/.vuepress/override.styl: -------------------------------------------------------------------------------- 1 | $accentColor = #B66E3C 2 | $textColor = #5F3416 3 | $borderColor = #9FB2CC 4 | -------------------------------------------------------------------------------- /src/docs/.vuepress/public/logo.orig.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 64 | 65 | -------------------------------------------------------------------------------- /src/docs/.vuepress/style.styl: -------------------------------------------------------------------------------- 1 | @import "~prismjs/themes/prism-tomorrow.css" 2 | 3 | div[class~="language-groovy"] 4 | &:before 5 | content "groovy" 6 | 7 | div[class~="language-kotlin"] 8 | &:before 9 | content "kotlin" 10 | -------------------------------------------------------------------------------- /src/docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroText: Gradle Shadow Plugin 4 | heroImage: /logo.svg 5 | tagline: The library author's dependency toolkit 6 | actionText: User Guide → 7 | actionLink: /introduction/ 8 | --- 9 | 10 | John Engelman - @johnrengelman 11 | 12 | [API Docs](http://imperceptiblethoughts.com/shadow/api/index.html) 13 | -------------------------------------------------------------------------------- /src/docs/about/README.md: -------------------------------------------------------------------------------- 1 | # About This Project 2 | 3 | I started this project in December of 2012. We were working on converting from a monolithic application into the 4 | new hot jazz of "microservices" using Dropwizard. 5 | I had also just started learning about Gradle and I knew that the incremental build system it provided would benefit 6 | our development team greatly. 7 | Unfortunately, the closest thing that Gradle had to Maven's Shade plugin was its ability to create application TARs and 8 | ZIPs. 9 | 10 | So, Charlie Knudsen and myself set out to port the existing Shade code into a Gradle plugin. 11 | This port is what existed up until the `0.9` milestone releases for Shadow. 12 | It functioned, but it wasn't idiomatic Gradle by any means. 13 | 14 | Starting with 0.9, Shadow was rewritten from the ground up as standard Gradle plugin and leveraged as much of Gradle's 15 | classes and concepts as possible. 16 | At the same time as the 0.9 release, Gradle was announcing the [Gradle Plugin Portal](https://plugins.gradle.org) and 17 | so Shadow was published there. 18 | 19 | ## Maintainers 20 | 21 | * [John Engelman](https://github.com/johnrengelman) 22 | 23 | ## Contributors 24 | 25 | * [Alan D. Cabrera](https://github.com/maguro) 26 | * [Andres Almiray](https://github.com/aalmiray) 27 | * [Artem Chubaryan](https://github.com/Armaxis) 28 | * [Attila Kelemen](https://github.com/kelemen) 29 | * [Ben Adazza](https://github.com/ben-adazza) 30 | * [Bernie Schelberg](https://github.com/bschelberg) 31 | * [Brandon Kearby](https://github.com/brandonkearby) 32 | * [Brane F. Gračnar](https://github.com/bfg) 33 | * [Caleb Larsen](https://github.com/MuffinTheMan) 34 | * [Charlie Knudsen](https://github.com/charliek) 35 | * [Chris Cowan](https://github.com/Macil) 36 | * [Chris Rankin](https://github.com/chrisr3) 37 | * [Christian Stein](https://github.com/sormuras) 38 | * [Daniel Oakey](https://github.com/danieloakey) 39 | * [debanne](https://github.com/debanne) 40 | * [Dennis Schumann](https://github.com/Hillkorn) 41 | * [Dmitry Vyazelenko](https://github.com/vyazelenko) 42 | * [ejjcase](https://github.com/ejjcase) 43 | * [Ethan Hall](https://github.com/ethankhall) 44 | * [Fedor Korotkov](https://github.com/fkorotkov) 45 | * [Felipe Lima](https://github.com/felipecsl) 46 | * [Gary Hale](https://github.com/ghale) 47 | * [Haw-Bin Chai](https://github.com/hbchai) 48 | * [Helder Pereira](https://github.com/helfper) 49 | * [Inez Korczyński](https://github.com/inez) 50 | * [James Nelson](https://github.com/JamesXNelson) 51 | * [Jeff Adler](https://github.com/jeffalder) 52 | * [John Szakmeister](https://github.com/jszakmeister) 53 | * [Konstantin Gribov](https://github.com/grossws) 54 | * [Lai Jiang](https://github.com/jianglai) 55 | * [Marc Philipp](https://github.com/marcphilipp) 56 | * [Mark Vieira](https://github.com/mark-vieira) 57 | * [Marke Vieira](https://github.com/mark-vieira) 58 | * [Martin Sadowski](https://github.com/ttsiebzehntt) 59 | * [Matt Hurne](https://github.com/mhurne) 60 | * [Matt King](https://github.com/kyrrigle) 61 | * [Matthew Haughton](https://github.com/3flex) 62 | * [Maximilian Müller](https://github.com/maxm123) 63 | * [Minecrell](https://github.com/Minecrell) 64 | * [Min-Ken Lai](https://github.com/minkenlai) 65 | * [Nicolas Humblot](https://github.com/nhumblot) 66 | * [Osip Fatkullin](https://github.com/osipxd) 67 | * [Paul N. Baker](https://github.com/paul-nelson-baker) 68 | * [Petar Petrov](https://github.com/petarov) 69 | * [Piotr Kubowicz](https://github.com/pkubowicz) 70 | * [Richard Marbach](https://github.com/RichardMarbach) 71 | * [Rob Spieldenner](https://github.com/rspieldenner) 72 | * [Roberto Perez Alcolea](https://github.com/rpalcolea) 73 | * [Schalk W. Cronjé](https://github.com/ysb33r) 74 | * [Scott Newson](https://github.com/sgnewson) 75 | * [Serban Iordache](https://github.com/siordache) 76 | * [Sergey Tselovalnikov](https://github.com/SerCeMan) 77 | * [Tim Yates](https://github.com/timyates) 78 | * [Trask Stalnaker](https://github.com/trask) 79 | * [Tyler Benson](https://github.com/tylerbenson) 80 | * [Victor Tso](https://github.com/roxchkplusony) 81 | * [Yahor Berdnikau](https://github.com/Tapchicoma) 82 | -------------------------------------------------------------------------------- /src/docs/application-plugin/README.md: -------------------------------------------------------------------------------- 1 | # Integrating with Application Plugin 2 | 3 | Shadow reacts to the presence of Gradle's 4 | [`application`](https://docs.gradle.org/current/userguide/application_plugin.html) plugin and will automatically 5 | configure additional tasks for running the shadowed JAR and creating distributions containing the shadowed JAR. 6 | 7 | Just like the normal `jar` task, when the `application` plugin is applied, the `shadowJar` manifest will be 8 | configured to contain the `Main-Class` attribute with the value specified in the project's `mainClassName` attribute. 9 | 10 | ```groovy 11 | // Using Shadow with Application Plugin 12 | apply plugin: 'java' 13 | apply plugin: 'application' 14 | apply plugin: 'com.github.johnrengelman.shadow' 15 | 16 | application { 17 | mainClass = 'myapp.Main' 18 | } 19 | ``` 20 | 21 | ## Running the Shadow JAR 22 | 23 | When applied along with the `application` plugin, the `runShadow` task will be created for starting 24 | the application from the shadowed JAR. 25 | The `runShadow` task is a [`JavaExec`](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html) 26 | task that is configured to execute `java -jar myproject-all.jar`. 27 | It can be configured the same as any other `JavaExec` task. 28 | 29 | ```groovy 30 | // Configuring the runShadow Task 31 | runShadow { 32 | args 'foo' 33 | } 34 | ``` 35 | 36 | ## Distributing the Shadow JAR 37 | 38 | The Shadow plugin will also configure distribution tasks when in the presence of the `application` plugin. 39 | The plugin will create `shadowDistZip` and `shadowDistTar` which creates Zip and Tar distributions 40 | respectively. 41 | Each distribution will contain the shadowed JAR file along with the necessary start scripts to launch 42 | the application. 43 | 44 | Additionally, the plugin will create the `installShadowDist` and `startShadowScripts` tasks which stages the necessary 45 | files for a distribution to `build/install/-shadow/`. 46 | -------------------------------------------------------------------------------- /src/docs/configuration/filtering/README.md: -------------------------------------------------------------------------------- 1 | # Filtering Shadow Jar Contents 2 | 3 | The final contents of a shadow JAR can be filtered using the `exclude` and `include` methods inherited from Gradle's 4 | `Jar` task type. 5 | 6 | Refer to the [Jar](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html) documentation for details 7 | on the various versions of the methods and their behavior. 8 | 9 | When using `exclude`/`include` with a `ShadowJar` task, the resulting copy specs are applied to the _final_ JAR 10 | contents. 11 | This means that, the configuration is applied to the individual files from both the project source set or _any_ 12 | of the dependencies to be merged. 13 | 14 | ```groovy 15 | // Exclude a file from Shadow Jar 16 | shadowJar { 17 | exclude 'a2.properties' 18 | } 19 | ``` 20 | 21 | 22 | Excludes and includes can be combined just like a normal `Jar` task, with `excludes` taking precedence over `includes`. 23 | Additionally, ANT style patterns can be used to match multiple files. 24 | 25 | ```groovy 26 | // Configuring output using ANT patterns 27 | shadowJar { 28 | include '*.jar' 29 | include '*.properties' 30 | exclude 'a2.properties' 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /src/docs/configuration/minimizing/README.md: -------------------------------------------------------------------------------- 1 | # Minimizing 2 | 3 | Shadow can automatically remove all classes of dependencies that are not used by the project, thereby minimizing the resulting shadowed JAR. 4 | 5 | ```groovy 6 | // Minimizing a shadow JAR 7 | shadowJar { 8 | minimize() 9 | } 10 | ``` 11 | 12 | A dependency can be excluded from the minimization process, thereby forcing it's inclusion the shadow JAR. 13 | This is useful when the dependency analyzer cannot find the usage of a class programmatically, for example if the class 14 | is loaded dynamically via `Class.forName(String)`. Each of the `group`, `name` and `version` fields separated by `:` of 15 | a `dependency` is interpreted as a regular expression. 16 | 17 | ```groovy 18 | // Force a class to be retained during minimization 19 | shadowJar { 20 | minimize { 21 | exclude(dependency('org.scala-lang:.*:.*')) 22 | } 23 | } 24 | ``` 25 | 26 | > Dependencies scoped as `api` will automatically excluded from minimization and used as "entry points" on minimization. 27 | 28 | Similar to dependencies, projects can also be excluded. 29 | 30 | ```groovy 31 | shadowJar { 32 | minimize { 33 | exclude(project(":api")) 34 | } 35 | } 36 | ``` 37 | 38 | > When excluding a `project`, all dependencies of the excluded `project` are automatically 39 | excluded as well. 40 | -------------------------------------------------------------------------------- /src/docs/configuration/relocation/README.md: -------------------------------------------------------------------------------- 1 | # Relocating Packages 2 | 3 | Shadow is capable of scanning a project's classes and relocating specific dependencies to a new location. 4 | This is often required when one of the dependencies is susceptible to breaking changes in versions or 5 | to classpath pollution in a downstream project. 6 | 7 | > Google's Guava and the ASM library are typical cases where package relocation can come in handy. 8 | 9 | Shadow uses the ASM library to modify class byte code to replace the package name and any import 10 | statements for a class. 11 | Any non-class files that are stored within a package structure are also relocated to the new location. 12 | 13 | ```groovy 14 | // Relocating a Package 15 | shadowJar { 16 | relocate 'junit.framework', 'shadow.junit' 17 | } 18 | ``` 19 | 20 | The code snippet will rewrite the location for any class in the `junit.framework` to be `shadow.junit`. 21 | For example, the class `junit.textui.TestRunner` becomes `shadow.junit.TestRunner`. 22 | In the resulting JAR, the class file is relocated from `junit/textui/TestRunner.class` to 23 | `shadow/junit/TestRunner.class`. 24 | 25 | > Relocation operates at a package level. 26 | It is not necessary to specify any patterns for matching, it will operate simply on the prefix 27 | provided. 28 | 29 | > Relocation will be applied globally to all instances of the matched prefix. 30 | That is, it does **not** scope to _only_ the dependencies being shadowed. 31 | Be specific as possible when configuring relocation as to avoid unintended relocations. 32 | 33 | ## Filtering Relocation 34 | 35 | Specific classes or files can be `included`/`excluded` from the relocation operation if necessary. Use 36 | [Ant Path Matcher](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/AntPathMatcher.html) 37 | syntax to specify matching path for your files and directories. 38 | 39 | ```groovy 40 | // Configuring Filtering for Relocation 41 | shadowJar { 42 | relocate('junit.textui', 'a') { 43 | exclude 'junit.textui.TestRunner' 44 | } 45 | relocate('junit.framework', 'b') { 46 | include 'junit.framework.Test*' 47 | } 48 | } 49 | ``` 50 | 51 | For a more advanced path matching you might want to use [Regular Expressions](https://regexr.com/) instead. Wrap the expresion in `%regex[]` before 52 | passing it to `include`/`exclude`. 53 | 54 | ```groovy 55 | // Configuring Filtering for Relocation with Regex 56 | shadowJar { 57 | relocate('org.foo', 'a') { 58 | include '%regex[org/foo/.*Factory[0-9].*]' 59 | } 60 | } 61 | ``` 62 | 63 | ## Automatically Relocating Dependencies 64 | 65 | Shadow is shipped with a task that can be used to automatically configure all packages from all dependencies to be relocated. 66 | This feature was formally shipped into a 2nd plugin (`com.github.johnrengelman.plugin-shadow`) but has been 67 | removed for clarity reasons in version 4.0.0. 68 | 69 | To configure automatic dependency relocation, set `enableRelocation true` and optionally specify a custom 70 | `relocationPrefix` to override the default value of `"shadow"`. 71 | 72 | ```groovy 73 | // Configure Auto Relocation 74 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 75 | 76 | tasks.named('shadowJar', ShadowJar) { 77 | enableRelocation true 78 | relocationPrefix "myapp" 79 | } 80 | ``` 81 | 82 | In versions before 8.1.0 it was necessary to configure a separate `ConfigureShadowRelocation` task for this. 83 | 84 | > Configuring package auto relocation can add significant time to the shadow process as it will process all dependencies 85 | in the configurations declared to be shadowed. By default, this is the `runtime` or `runtimeClasspath` configurations. 86 | Be mindful that some Gradle plugins will automatically add dependencies to your class path. You may need to remove these 87 | dependencies if you do not intend to shadow them into your library. The `java-gradle-plugin` would normally cause such 88 | problems if it were not for the special handling that Shadow provides as described in 89 | [Special Handling of the Java Gradle Plugin Development Plugin](/plugins/#special-handling-of-the-java-gradle-plugin-gevelopmeny-plugin). 90 | -------------------------------------------------------------------------------- /src/docs/configuration/reproducible-builds/README.md: -------------------------------------------------------------------------------- 1 | # Reproducible Builds 2 | 3 | By default, JAR files generated by Gradle (with or without Shadow) for a single project with the same source code may not be identical to each other. Sometimes it's desireable to configure a project to consistently output a byte-for-byte identical JAR on every build. Gradle supports this with the following configuration, and Shadow will correctly respect these settings too: 4 | 5 | ```groovy 6 | tasks.withType(AbstractArchiveTask) { 7 | preserveFileTimestamps = false 8 | reproducibleFileOrder = true 9 | } 10 | ``` 11 | 12 | One effect that this configuration will have is that the timestamps of all files in the JAR will be reset to a single consistent value. If your code or any files being included into the JAR depend on the timestamps being set accurately within the JAR, then this may not be the correct choice for you. 13 | 14 | See the [Reproducible archives section in Gradle's documentation](https://docs.gradle.org/4.9/userguide/working_with_files.html#sec:reproducible_archives) for more information. 15 | -------------------------------------------------------------------------------- /src/docs/custom-tasks/README.md: -------------------------------------------------------------------------------- 1 | # Creating a Custom ShadowJar Task 2 | 3 | The built in `shadowJar` task only provides an output for the `main` source set of the project. 4 | It is possible to add arbitrary [`ShadowJar`](http://imperceptiblethoughts.com/shadow/api/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.html) 5 | tasks to a project. When doing so, ensure that the `configurations` property is specified to inform Shadow which 6 | dependencies to merge into the output. 7 | 8 | ```groovy 9 | // Shadowing Test Sources and Dependencies 10 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 11 | 12 | task testJar(type: ShadowJar) { 13 | archiveClassifier.set("tests") 14 | from sourceSets.test.output 15 | configurations = [project.configurations.testImplementation] 16 | } 17 | ``` 18 | 19 | The code snippet above will generate a shadowed JAR containing both the `main` and `test` sources as well as all `runtime` 20 | and `testImplementation` dependencies. 21 | The file is output to `build/libs/--tests.jar`. 22 | -------------------------------------------------------------------------------- /src/docs/getting-started/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ```groovy no-plugins 4 | plugins { 5 | id 'com.github.johnrengelman.shadow' version '@version@' 6 | id 'java' 7 | } 8 | ``` 9 | 10 | Alternatively, the plugin can be added to the buildscript classpath and applied: 11 | 12 | ```groovy no-run 13 | buildscript { 14 | repositories { 15 | gradlePluginPortal() 16 | } 17 | dependencies { 18 | classpath 'com.github.johnrengelman:shadow:@version@' 19 | } 20 | } 21 | 22 | apply plugin: 'com.github.johnrengelman.shadow' 23 | apply plugin: 'java' 24 | ``` 25 | 26 | **NOTE:** The correct maven coordinates for each version of Shadow can be found by referencing the Gradle Plugin documentation [here](https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow). 27 | 28 | Shadow is a reactive plugin. 29 | This means that applying Shadow by itself will perform no configuration on your project. 30 | Instead, Shadow _reacts_ 31 | This means, that for most users, the `java` or `groovy` plugins must be _explicitly_ applied 32 | to have the desired effect. 33 | 34 | ## Default Java/Groovy Tasks 35 | 36 | In the presence of the `java` or `groovy` plugins, Shadow will automatically configure the 37 | following behavior: 38 | 39 | * Adds a `shadowJar` task to the project. 40 | * Adds a `shadow` configuration to the project. 41 | * Configures the `shadowJar` task to include all sources from the project's `main` sourceSet. 42 | * Configures the `shadowJar` task to bundle all dependencies from the `runtimeClasspath` configuration. 43 | * Configures the _classifier_ attribute of the `shadowJar` task to be `'all'` . 44 | * Configures the `shadowJar` task to generate a `Manifest` with: 45 | * Inheriting all configuration from the standard `jar` task. 46 | * Adds a `Class-Path` attribute to the `Manifest` that appends all dependencies from the `shadow` configuration 47 | * Configures the `shadowJar` task to _exclude_ any JAR index or cryptographic signature files matching the following patterns: 48 | * `META-INF/INDEX.LIST` 49 | * `META-INF/*.SF` 50 | * `META-INF/*.DSA` 51 | * `META-INF/*.RSA` 52 | * Creates and registers the `shadow` component in the project (used for integrating with `maven-publish`). 53 | 54 | ## Shadowing Gradle Plugins 55 | 56 | Shadow ships with a companion task that can be used to automatically discover dependency packages and configure 57 | them for relocation. This is useful in projects if you want to relocate all dependencies. 58 | 59 | For more details see the section [Using Shadow to Package Gradle Plugins](/plugins/) 60 | -------------------------------------------------------------------------------- /src/docs/introduction/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Shadow is a Gradle plugin for combining a project's dependency classes and resources into a single 4 | output Jar. 5 | The combined Jar is often referred to a _fat-jar_ or _uber-jar_. 6 | Shadow utilizes [`JarInputStream`](https://docs.oracle.com/javase/8/docs/api/java/util/jar/JarInputStream.html) and [`JarOutputStream`](https://docs.oracle.com/javase/8/docs/api/java/util/jar/JarOutputStream.html) to efficiently process dependent libraries 7 | into the output jar without incurring the I/O overhead of expanding the jars to disk. 8 | 9 | ## Benefits of Shadow 10 | 11 | Shadowing a project output has 2 major use cases: 12 | 13 | 1. Creating an _executable_ JAR distribution 14 | 1. Bundling and relocating common dependencies in libraries to avoid classpath conflicts 15 | 16 | ### Executable Distributions 17 | 18 | Executable distribution is the main use case for deploying an _application_ that can be executed/run in the runtime 19 | environment. 20 | In the case of Shadow, this is a single _uber_ or _fat_ JAR. 21 | The JAR file contains all the application code and dependent libraries to execute (not including the standard JVM 22 | libraries). 23 | The shadow JAR does **not** include the JRE itself. 24 | It must be available on the target system. 25 | 26 | Executable JARs contain a JAR MANIFEST that specifies the application Main Class. 27 | This allows the application to be started with a single command: 28 | 29 | ```bash 30 | $ java -jar application-shadow.jar 31 | ``` 32 | 33 | ### Library Bundling 34 | 35 | Dependency bundling and relocation is the main use case for _library_ authors. 36 | The goal of a bundled library is to create a pre-packaged dependency for other libraries or applications to utilize. 37 | Often in these scenarios, a library may contain a dependency that a downstream library or application also uses. 38 | In _some_ cases, different versions of this common dependency can cause an issue in either the upstream library or 39 | the downstream application. 40 | These issues often manifest themselves as binary incompatibilities in either the library or application code. 41 | 42 | By utilizing Shadow's ability to _relocate_ the package names for dependencies, a library author can ensure that the 43 | library's dependencies will not conflict with the same dependency being declared by the downstream application. 44 | -------------------------------------------------------------------------------- /src/docs/multi-project/README.md: -------------------------------------------------------------------------------- 1 | # Using Shadow in Multi-Project Builds 2 | 3 | When using Shadow in a multi-project build, project dependencies will be treated the same as 4 | external dependencies. 5 | That is a project dependency will be merged into the `shadowJar` output of the project that 6 | is applying the Shadow plugin. 7 | 8 | ## Depending on the Shadow Jar from Another Project 9 | 10 | In a multi-project build there may be one project that applies Shadow and another that 11 | requires the shadowed JAR as a dependency. 12 | In this case, use Gradle's normal dependency declaration mechanism to depend on the `shadow` 13 | configuration of the shadowed project. 14 | 15 | ```groovy 16 | // Depending On Shadow Output of Project 17 | dependencies { 18 | implementation project(path: ':api', configuration: 'shadow') 19 | } 20 | ``` -------------------------------------------------------------------------------- /src/docs/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Using Shadow to Package Gradle Plugins 2 | 3 | In some scenarios, writing a Gradle plugin can be problematic because your plugin may depend on a version that 4 | conflicts with the same dependency provided by the Gradle runtime. If this is the case, then you can utilize Shadow 5 | to relocate your dependencies to a different package name to avoid the collision. 6 | 7 | Configuring the relocation has always been possible, but the build author is required to know all the package names 8 | beforehand. As of Shadow v8.1.0, automatic package relocation can be enabled by setting the `enabledRelocation` 9 | and `relocationPrefix` settings on any `ShadowJar` task. 10 | 11 | A simple Gradle plugin can use this feature by applying the `shadow` plugin and configuring the `shadowJar` task for relocation. 12 | 13 | ```groovy no-plugins 14 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 15 | 16 | plugins { 17 | id 'com.github.johnrengelman.shadow' version '@version@' 18 | id 'java' 19 | } 20 | 21 | dependencies { 22 | shadow localGroovy() 23 | shadow gradleApi() 24 | 25 | implementation 'org.jdom:jdom2:2.0.6' 26 | implementation 'org.ow2.asm:asm:6.0' 27 | implementation 'org.ow2.asm:asm-commons:6.0' 28 | implementation 'commons-io:commons-io:2.4' 29 | implementation 'org.apache.ant:ant:1.9.4' 30 | implementation 'org.codehaus.plexus:plexus-utils:2.0.6' 31 | } 32 | 33 | tasks.named('shadowJar', ShadowJar) { 34 | enableRelocation true 35 | } 36 | ``` 37 | 38 | Note that the `localGroovy()` and `gradleApi()` dependencies are added to the `shadow` configuration instead of the 39 | normal `compile` configuration. These 2 dependencies are provided by Gradle to compile your project but are ultimately 40 | provided by the Gradle runtime when executing the plugin. Thus, it is **not** advisable to bundle these dependencies 41 | with your plugin. 42 | 43 | ## Publishing shadowed Gradle plugins 44 | The Gradle Publish Plugin introduced support for plugins packaged with Shadow in version 1.0.0. 45 | Starting with this version, plugin projects that apply both Shadow and the Gradle Plugin Publish plugin will be 46 | automatically configured to publish the output of the `shadowJar` tasks as the consumable artifact for the plugin. 47 | See the [Gradle Plugin Publish docs](https://docs.gradle.org/current/userguide/publishing_gradle_plugins.html#shadow_dependencies) for details. 48 | 49 | ## Special Handling of the Java Gradle Plugin Development Plugin 50 | 51 | The Java Gradle Plugin Development plugin, `java-gradle-plugin`, automatically adds the full Gradle API to the `compile` 52 | configuration; thus overriding a possible assignment of `gradleApi()` to the `shadow` configuration. Since it is never 53 | a good idea to include the Gradle API when creating a Gradle plugin, the dependency is removed so that it is not 54 | included in the resultant shadow jar. Virtually: 55 | 56 | // needed to prevent inclusion of gradle-api into shadow JAR 57 | configurations.compile.dependencies.remove dependencies.gradleApi() 58 | 59 | ## Automatic package relocation with Shadow prior to v8.1.0 60 | 61 | Prior to Shadow v8.1.0, Shadow handled this by introducing a new task type `ConfigureShadowRelocation`. 62 | Tasks of this type are configured to target an instance of a `ShadowJar` task and run immediately before it. 63 | 64 | The `ConfigureShadowRelocation` task, scans the dependencies from the configurations specified on the associated 65 | `ShadowJar` task and collects the package names contained within them. It then configures relocation for these 66 | packages using the specified `prefix` on the associated `ShadowJar` task. 67 | 68 | While this is useful for developing Gradle plugins, nothing about the `ConfigureShadowRelocation` task is tied to 69 | Gradle projects. It can be used for standard Java or Groovy projects. -------------------------------------------------------------------------------- /src/docs/publishing/README.md: -------------------------------------------------------------------------------- 1 | # Publishing Shadow JARs 2 | 3 | ## Publishing with Maven-Publish Plugin 4 | 5 | The Shadow plugin will automatically configure the necessary tasks in the presence of Gradle's 6 | `maven-publish` plugin. 7 | The plugin provides the `component` method from the `shadow` extension to configure the 8 | publication with the necessary artifact and dependencies in the POM file. 9 | 10 | ```groovy 11 | // Publishing a Shadow JAR with the Maven-Publish Plugin 12 | apply plugin: 'java' 13 | apply plugin: 'maven-publish' 14 | apply plugin: 'com.github.johnrengelman.shadow' 15 | 16 | publishing { 17 | publications { 18 | shadow(MavenPublication) { publication -> 19 | project.shadow.component(publication) 20 | } 21 | } 22 | repositories { 23 | maven { 24 | url "http://repo.myorg.com" 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | ## Shadow Configuration and Publishing 31 | 32 | The Shadow plugin provides a custom configuration (`configurations.shadow`) to specify 33 | runtime dependencies that are **not** merged into the final JAR file. 34 | When configuring publishing with the Shadow plugin, the dependencies in the `shadow` 35 | configuration, are translated to become `RUNTIME` scoped dependencies of the 36 | published artifact. 37 | 38 | No other dependencies are automatically configured for inclusion in the POM file. 39 | For example, excluded dependencies are **not** automatically added to the POM file or 40 | if the configuration for merging are modified by specifying 41 | `shadowJar.configurations = [configurations.myconfiguration]`, there is no automatic 42 | configuration of the POM file. 43 | 44 | This automatic configuration occurs _only_ when using the above methods for 45 | configuring publishing. If this behavior is not desirable, then publishing **must** 46 | be manually configured. 47 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowBasePlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.tasks.KnowsTask 4 | import org.gradle.api.GradleException 5 | import org.gradle.api.Plugin 6 | import org.gradle.api.Project 7 | import org.gradle.util.GradleVersion 8 | 9 | class ShadowBasePlugin implements Plugin { 10 | 11 | public static final String EXTENSION_NAME = 'shadow' 12 | public static final String CONFIGURATION_NAME = 'shadow' 13 | 14 | @Override 15 | void apply(Project project) { 16 | if (GradleVersion.current() < GradleVersion.version("8.3")) { 17 | throw new GradleException("This version of Shadow supports Gradle 8.3+ only. Please upgrade.") 18 | } 19 | project.extensions.create(EXTENSION_NAME, ShadowExtension, project) 20 | createShadowConfiguration(project) 21 | 22 | project.tasks.register(KnowsTask.NAME, KnowsTask) { knows -> 23 | knows.group = ShadowJavaPlugin.SHADOW_GROUP 24 | knows.description = DESC 25 | } 26 | } 27 | 28 | private static void createShadowConfiguration(Project project) { 29 | project.configurations.create(CONFIGURATION_NAME) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowExtension.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 4 | import org.gradle.api.Project 5 | import org.gradle.api.artifacts.ProjectDependency 6 | import org.gradle.api.artifacts.SelfResolvingDependency 7 | import org.gradle.api.file.RegularFile 8 | import org.gradle.api.provider.Provider 9 | import org.gradle.api.publish.maven.MavenPom 10 | import org.gradle.api.publish.maven.MavenPublication 11 | 12 | class ShadowExtension { 13 | private final Provider> archiveFile 14 | private final Provider> allDependencies 15 | 16 | ShadowExtension(Project project) { 17 | archiveFile = project.provider { project.tasks.withType(ShadowJar).getByName("shadowJar").archiveFile } 18 | allDependencies = project.provider { 19 | project.configurations.getByName("shadow").allDependencies.collect { 20 | if ((it instanceof ProjectDependency) || !(it instanceof SelfResolvingDependency)) { 21 | new Dep(it.group, it.name, it.version) 22 | } 23 | } 24 | } 25 | } 26 | 27 | void component(MavenPublication publication) { 28 | publication.artifact(archiveFile.get()) 29 | 30 | // Don't inline this variable, it seems Groovy closure capturing is confused by the field instead of a local variable. 31 | final def allDeps = allDependencies 32 | publication.pom { MavenPom pom -> 33 | pom.withXml { xml -> 34 | def dependenciesNode = xml.asNode().get('dependencies') ?: xml.asNode().appendNode('dependencies') 35 | allDeps.get().each { 36 | def dependencyNode = dependenciesNode.appendNode('dependency') 37 | dependencyNode.appendNode('groupId', it.group) 38 | dependencyNode.appendNode('artifactId', it.name) 39 | dependencyNode.appendNode('version', it.version) 40 | dependencyNode.appendNode('scope', 'runtime') 41 | } 42 | } 43 | } 44 | } 45 | 46 | private class Dep { 47 | String group 48 | String name 49 | String version 50 | 51 | Dep(String group, String name, String version) { 52 | this.group = group 53 | this.name = name 54 | this.version = version 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 4 | import org.gradle.api.Plugin 5 | import org.gradle.api.Project 6 | import org.gradle.api.attributes.Bundling 7 | import org.gradle.api.attributes.Category 8 | import org.gradle.api.attributes.LibraryElements 9 | import org.gradle.api.attributes.Usage 10 | import org.gradle.api.tasks.SourceSetContainer 11 | import org.gradle.configuration.project.ProjectConfigurationActionContainer 12 | 13 | import javax.inject.Inject 14 | 15 | class ShadowJavaPlugin implements Plugin { 16 | 17 | public static final String SHADOW_JAR_TASK_NAME = 'shadowJar' 18 | public static final String SHADOW_GROUP = 'Shadow' 19 | 20 | private final ProjectConfigurationActionContainer configurationActionContainer 21 | 22 | @Inject 23 | ShadowJavaPlugin(ProjectConfigurationActionContainer configurationActionContainer) { 24 | this.configurationActionContainer = configurationActionContainer 25 | } 26 | 27 | @Override 28 | void apply(Project project) { 29 | configureShadowTask(project) 30 | 31 | project.configurations.compileClasspath.extendsFrom project.configurations.shadow 32 | 33 | project.configurations { 34 | shadowRuntimeElements { 35 | canBeConsumed = true 36 | canBeResolved = false 37 | attributes { 38 | it.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage, Usage.JAVA_RUNTIME)) 39 | it.attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category, Category.LIBRARY)) 40 | it.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.objects.named(LibraryElements, LibraryElements.JAR)) 41 | it.attribute(Bundling.BUNDLING_ATTRIBUTE, project.objects.named(Bundling, Bundling.SHADOWED)) 42 | } 43 | outgoing.artifact(project.tasks.named(SHADOW_JAR_TASK_NAME)) 44 | } 45 | } 46 | 47 | project.configurations.shadowRuntimeElements.extendsFrom project.configurations.shadow 48 | 49 | project.components.java { 50 | addVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { 51 | mapToOptional() // make it a Maven optional dependency 52 | } 53 | } 54 | } 55 | 56 | protected static void configureShadowTask(Project project) { 57 | SourceSetContainer sourceSets = project.extensions.getByType(SourceSetContainer) 58 | project.tasks.register(SHADOW_JAR_TASK_NAME, ShadowJar) { shadow -> 59 | shadow.group = SHADOW_GROUP 60 | shadow.description = 'Create a combined JAR of project and runtime dependencies' 61 | shadow.archiveClassifier.set("all") 62 | shadow.manifest.inheritFrom(project.tasks.jar.manifest) 63 | def libsProvider = project.provider { -> [project.tasks.jar.manifest.attributes.get('Class-Path')] } 64 | def files = project.objects.fileCollection().from { -> 65 | project.configurations.findByName(ShadowBasePlugin.CONFIGURATION_NAME) 66 | } 67 | shadow.doFirst { 68 | if (!files.empty) { 69 | def libs = libsProvider.get() 70 | libs.addAll files.collect { "${it.name}" } 71 | manifest.attributes 'Class-Path': libs.findAll { it }.join(' ') 72 | } 73 | } 74 | shadow.from(sourceSets.main.output) 75 | shadow.configurations = [project.configurations.findByName('runtimeClasspath') ? 76 | project.configurations.runtimeClasspath : project.configurations.runtime] 77 | shadow.exclude('META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'module-info.class') 78 | shadow.dependencies { 79 | exclude(dependency(project.dependencies.gradleApi())) 80 | } 81 | } 82 | project.artifacts.add(ShadowBasePlugin.CONFIGURATION_NAME, project.tasks.named(SHADOW_JAR_TASK_NAME)) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 4 | import org.gradle.api.Plugin 5 | import org.gradle.api.Project 6 | import org.gradle.api.plugins.ApplicationPlugin 7 | import org.gradle.api.plugins.JavaPlugin 8 | 9 | class ShadowPlugin implements Plugin { 10 | 11 | @Override 12 | void apply(Project project) { 13 | project.with { 14 | plugins.apply(ShadowBasePlugin) 15 | plugins.withType(JavaPlugin) { 16 | plugins.apply(ShadowJavaPlugin) 17 | } 18 | plugins.withType(ApplicationPlugin) { 19 | plugins.apply(ShadowApplicationPlugin) 20 | } 21 | 22 | // Legacy build scan support for Gradle Enterprise, users should migrate to develocity plugin. 23 | rootProject.plugins.withId('com.gradle.enterprise') { 24 | configureBuildScan(rootProject) 25 | } 26 | rootProject.plugins.withId('com.gradle.develocity') { 27 | configureBuildScan(rootProject) 28 | } 29 | } 30 | } 31 | 32 | private void configureBuildScan(Project rootProject) { 33 | rootProject.buildScan.buildFinished { 34 | def shadowTasks = tasks.withType(ShadowJar) 35 | shadowTasks.each { task -> 36 | if (task.didWork) { 37 | task.stats.buildScanData.each { k, v -> 38 | rootProject.buildScan.value "shadow.${task.path}.${k}", v.toString() 39 | } 40 | rootProject.buildScan.value "shadow.${task.path}.configurations", task.configurations*.name.join(", ") 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowStats.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow 2 | 3 | import groovy.util.logging.Slf4j 4 | import org.gradle.api.GradleException 5 | 6 | @Slf4j 7 | class ShadowStats { 8 | 9 | long totalTime 10 | long jarStartTime 11 | long jarEndTime 12 | int jarCount = 1 13 | boolean processingJar 14 | Map relocations = [:] 15 | 16 | void relocate(String src, String dst) { 17 | relocations[src] = dst 18 | } 19 | 20 | String getRelocationString() { 21 | def maxLength = relocations.keySet().collect { it.length() }.max() 22 | relocations.collect { k, v -> "${k} ${separator(k, maxLength)} ${v}"}.sort().join("\n") 23 | } 24 | 25 | static String separator(String key, int max) { 26 | return "→" 27 | } 28 | 29 | void startJar() { 30 | if (processingJar) throw new GradleException("Can only time one entry at a time") 31 | processingJar = true 32 | jarStartTime = System.currentTimeMillis() 33 | } 34 | 35 | void finishJar() { 36 | if (processingJar) { 37 | jarEndTime = System.currentTimeMillis() 38 | jarCount++ 39 | totalTime += jarTiming 40 | processingJar = false 41 | } 42 | } 43 | 44 | void printStats() { 45 | println this 46 | } 47 | 48 | long getJarTiming() { 49 | jarEndTime - jarStartTime 50 | } 51 | 52 | double getTotalTimeSecs() { 53 | totalTime / 1000 54 | } 55 | 56 | double getAverageTimePerJar() { 57 | totalTime / jarCount 58 | } 59 | 60 | double getAverageTimeSecsPerJar() { 61 | averageTimePerJar / 1000 62 | } 63 | 64 | String toString() { 65 | StringBuilder sb = new StringBuilder() 66 | sb.append "*******************\n" 67 | sb.append "GRADLE SHADOW STATS\n" 68 | sb.append "\n" 69 | sb.append "Total Jars: $jarCount (includes project)\n" 70 | sb.append "Total Time: ${totalTimeSecs}s [${totalTime}ms]\n" 71 | sb.append "Average Time/Jar: ${averageTimeSecsPerJar}s [${averageTimePerJar}ms]\n" 72 | sb.append "*******************" 73 | } 74 | 75 | Map getBuildScanData() { 76 | [ 77 | dependencies: jarCount, 78 | relocations : relocationString, 79 | ] as Map 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/CleanProperties.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.internal 2 | 3 | class CleanProperties extends Properties { 4 | 5 | private static class StripCommentsWithTimestampBufferedWriter extends BufferedWriter { 6 | 7 | private final int lengthOfExpectedTimestamp 8 | 9 | StripCommentsWithTimestampBufferedWriter(final Writer out) { 10 | super(out) 11 | 12 | lengthOfExpectedTimestamp = ("#" + new Date().toString()).length() 13 | } 14 | 15 | @Override 16 | void write(final String str) throws IOException { 17 | if (couldBeCommentWithTimestamp(str)) { 18 | return 19 | } 20 | super.write(str) 21 | } 22 | 23 | private boolean couldBeCommentWithTimestamp(final String str) { 24 | return str != null && 25 | str.startsWith("#") && 26 | str.length() == lengthOfExpectedTimestamp 27 | } 28 | } 29 | 30 | @Override 31 | void store(final Writer writer, final String comments) throws IOException { 32 | super.store(new StripCommentsWithTimestampBufferedWriter(writer), comments) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/DefaultDependencyFilter.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.internal 2 | 3 | import groovy.util.logging.Slf4j 4 | import org.gradle.api.Project 5 | import org.gradle.api.artifacts.ResolvedDependency 6 | 7 | @Slf4j 8 | class DefaultDependencyFilter extends AbstractDependencyFilter { 9 | 10 | DefaultDependencyFilter(Project project) { 11 | super(project) 12 | } 13 | 14 | @Override 15 | protected void resolve(Set dependencies, 16 | Set includedDependencies, 17 | Set excludedDependencies) { 18 | dependencies.each { 19 | if (isIncluded(it) ? includedDependencies.add(it) : excludedDependencies.add(it)) { 20 | resolve(it.children, includedDependencies, excludedDependencies) 21 | } 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/DefaultZipCompressor.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.jengelman.gradle.plugins.shadow.internal 17 | 18 | import org.apache.tools.zip.Zip64Mode 19 | import org.apache.tools.zip.ZipOutputStream 20 | import org.gradle.api.UncheckedIOException 21 | 22 | class DefaultZipCompressor implements ZipCompressor { 23 | private final int entryCompressionMethod 24 | private final Zip64Mode zip64Mode 25 | 26 | DefaultZipCompressor(boolean allowZip64Mode, int entryCompressionMethod) { 27 | this.entryCompressionMethod = entryCompressionMethod 28 | zip64Mode = allowZip64Mode ? Zip64Mode.AsNeeded : Zip64Mode.Never 29 | } 30 | 31 | ZipOutputStream createArchiveOutputStream(File destination) { 32 | try { 33 | ZipOutputStream zipOutputStream = new ZipOutputStream(destination) 34 | zipOutputStream.setUseZip64(zip64Mode) 35 | zipOutputStream.setMethod(entryCompressionMethod) 36 | return zipOutputStream 37 | } catch (Exception e) { 38 | String message = String.format("Unable to create ZIP output stream for file %s.", destination) 39 | throw new UncheckedIOException(message, e) 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/DependencyFilter.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.internal 2 | 3 | import org.gradle.api.artifacts.Dependency 4 | import org.gradle.api.artifacts.ResolvedDependency 5 | import org.gradle.api.file.FileCollection 6 | import org.gradle.api.specs.Spec 7 | 8 | interface DependencyFilter { 9 | 10 | /** 11 | * Resolve a FileCollection against the include/exclude rules in the filter 12 | * @param configuration 13 | * @return 14 | */ 15 | FileCollection resolve(FileCollection configuration) 16 | 17 | /** 18 | * Resolve all FileCollections against the include/exclude ruels in the filter and combine the results 19 | * @param configurations 20 | * @return 21 | */ 22 | FileCollection resolve(Collection configurations) 23 | 24 | /** 25 | * Exclude dependencies that match the provided spec. 26 | * 27 | * @param spec 28 | * @return 29 | */ 30 | DependencyFilter exclude(Spec spec) 31 | 32 | /** 33 | * Include dependencies that match the provided spec. 34 | * 35 | * @param spec 36 | * @return 37 | */ 38 | DependencyFilter include(Spec spec) 39 | 40 | /** 41 | * Create a spec that matches the provided project notation on group, name, and version 42 | * @param notation 43 | * @return 44 | */ 45 | Spec project(Map notation) 46 | 47 | /** 48 | * Create a spec that matches the default configuration for the provided project path on group, name, and version 49 | * 50 | * @param notation 51 | * @return 52 | */ 53 | Spec project(String notation) 54 | 55 | /** 56 | * Create a spec that matches dependencies using the provided notation on group, name, and version 57 | * @param notation 58 | * @return 59 | */ 60 | Spec dependency(Object notation) 61 | 62 | /** 63 | * Create a spec that matches the provided dependency on group, name, and version 64 | * @param dependency 65 | * @return 66 | */ 67 | Spec dependency(Dependency dependency) 68 | 69 | /** 70 | * Create a spec that matches the provided closure 71 | * @param spec 72 | * @return 73 | */ 74 | Spec dependency(Closure spec) 75 | } 76 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/GradleVersionUtil.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.internal 2 | 3 | import org.apache.tools.zip.ZipOutputStream 4 | import org.gradle.api.internal.file.copy.CopySpecInternal 5 | import org.gradle.api.tasks.bundling.Jar 6 | import org.gradle.api.tasks.bundling.ZipEntryCompression 7 | import org.gradle.api.tasks.util.PatternSet 8 | 9 | class GradleVersionUtil { 10 | 11 | static PatternSet getRootPatternSet(CopySpecInternal mainSpec) { 12 | return mainSpec.buildRootResolver().getPatternSet() 13 | } 14 | 15 | static ZipCompressor getInternalCompressor(ZipEntryCompression entryCompression, Jar jar) { 16 | switch (entryCompression) { 17 | case ZipEntryCompression.DEFLATED: 18 | return new DefaultZipCompressor(jar.zip64, ZipOutputStream.DEFLATED) 19 | case ZipEntryCompression.STORED: 20 | return new DefaultZipCompressor(jar.zip64, ZipOutputStream.STORED) 21 | default: 22 | throw new IllegalArgumentException(String.format("Unknown Compression type %s", entryCompression)) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/JavaJarExec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.internal 2 | 3 | import org.gradle.api.tasks.InputFile 4 | import org.gradle.api.tasks.JavaExec 5 | import org.gradle.api.tasks.TaskAction 6 | 7 | class JavaJarExec extends JavaExec { 8 | 9 | @InputFile 10 | File jarFile 11 | 12 | @Override 13 | @TaskAction 14 | void exec() { 15 | List allArgs = [getJarFile().path] + getArgs() 16 | setArgs(allArgs) 17 | super.exec() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/MinimizeDependencyFilter.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.internal 2 | 3 | import groovy.util.logging.Slf4j 4 | import org.gradle.api.Project 5 | import org.gradle.api.artifacts.ResolvedDependency 6 | 7 | @Slf4j 8 | class MinimizeDependencyFilter extends AbstractDependencyFilter { 9 | 10 | MinimizeDependencyFilter(Project project) { 11 | super(project) 12 | } 13 | 14 | @Override 15 | protected void resolve(Set dependencies, 16 | Set includedDependencies, 17 | Set excludedDependencies) { 18 | 19 | dependencies.each { 20 | if (isIncluded(it) && !isParentExcluded(excludedDependencies, it) ? includedDependencies.add(it) : excludedDependencies.add(it)) { 21 | resolve(it.children, includedDependencies, excludedDependencies) 22 | } 23 | } 24 | } 25 | 26 | private static boolean isParentExcluded(Set excludedDependencies, ResolvedDependency dependency) { 27 | excludedDependencies.any { dependency.parents.contains(it) } 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/RelocationUtil.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.internal 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 4 | import java.util.jar.JarFile 5 | 6 | class RelocationUtil { 7 | 8 | static void configureRelocation(ShadowJar target, String prefix) { 9 | def packages = [] as Set 10 | target.configurations.each { configuration -> 11 | configuration.files.each { jar -> 12 | JarFile jf = new JarFile(jar) 13 | jf.entries().each { entry -> 14 | if (entry.name.endsWith(".class") && entry.name != "module-info.class") { 15 | packages << entry.name[0..entry.name.lastIndexOf('/') - 1].replaceAll('/', '.') 16 | } 17 | } 18 | jf.close() 19 | } 20 | } 21 | packages.each { 22 | target.relocate(it, "${prefix}.${it}") 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/UnusedTracker.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.internal 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.artifacts.Configuration 5 | import org.gradle.api.artifacts.Dependency 6 | import org.gradle.api.artifacts.ProjectDependency 7 | import org.gradle.api.artifacts.SelfResolvingDependency 8 | import org.gradle.api.file.FileCollection 9 | import org.gradle.api.tasks.InputFiles 10 | import org.vafer.jdependency.Clazz 11 | import org.vafer.jdependency.Clazzpath 12 | import org.vafer.jdependency.ClazzpathUnit 13 | 14 | /** Tracks unused classes in the project classpath. */ 15 | class UnusedTracker { 16 | private final FileCollection toMinimize 17 | private final List projectUnits 18 | private final Clazzpath cp = new Clazzpath() 19 | 20 | private UnusedTracker(Iterable classDirs, FileCollection classJars, FileCollection toMinimize) { 21 | this.toMinimize = toMinimize 22 | projectUnits = classDirs.collect { cp.addClazzpathUnit(it) } 23 | projectUnits.addAll(classJars.collect { cp.addClazzpathUnit(it) }) 24 | } 25 | 26 | Set findUnused() { 27 | Set unused = cp.clazzes 28 | for (cpu in projectUnits) { 29 | unused.removeAll(cpu.clazzes) 30 | unused.removeAll(cpu.transitiveDependencies) 31 | } 32 | return unused.collect { it.name }.toSet() 33 | } 34 | 35 | void addDependency(File jarOrDir) { 36 | if (toMinimize.contains(jarOrDir)) { 37 | cp.addClazzpathUnit(jarOrDir) 38 | } 39 | } 40 | 41 | static UnusedTracker forProject(FileCollection apiJars, Iterable sourceSetsClassesDirs, FileCollection toMinimize) { 42 | return new UnusedTracker(sourceSetsClassesDirs, apiJars, toMinimize) 43 | } 44 | 45 | @InputFiles 46 | FileCollection getToMinimize() { 47 | return toMinimize 48 | } 49 | 50 | private static boolean isProjectDependencyFile(File file, Dependency dep) { 51 | def fileName = file.name 52 | def dependencyName = dep.name 53 | 54 | return (fileName == "${dependencyName}.jar") || 55 | (fileName.startsWith("${dependencyName}-") && fileName.endsWith('.jar')) 56 | } 57 | 58 | private static void addJar(Configuration config, Dependency dep, List result) { 59 | def file = config.find { isProjectDependencyFile(it, dep) } as File 60 | if (file != null) { 61 | result.add(file) 62 | } 63 | } 64 | 65 | static FileCollection getApiJarsFromProject(Project project) { 66 | def apiDependencies = project.configurations.asMap['api']?.dependencies ?: null 67 | if (apiDependencies == null) return project.files() 68 | 69 | def runtimeConfiguration = project.configurations.asMap['runtimeClasspath'] ?: project.configurations.runtime 70 | def apiJars = new LinkedList() 71 | apiDependencies.each { dep -> 72 | if (dep instanceof ProjectDependency) { 73 | apiJars.addAll(getApiJarsFromProject(dep.dependencyProject)) 74 | addJar(runtimeConfiguration, dep, apiJars) 75 | } else if (dep instanceof SelfResolvingDependency) { 76 | apiJars.addAll(dep.resolve()) 77 | } else { 78 | addJar(runtimeConfiguration, dep, apiJars) 79 | apiJars.add(runtimeConfiguration.find { it.name.startsWith("${dep.name}-") } as File) 80 | } 81 | } 82 | 83 | return project.files(apiJars) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/ZipCompressor.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.jengelman.gradle.plugins.shadow.internal 17 | 18 | import org.apache.tools.zip.ZipOutputStream 19 | import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory 20 | 21 | interface ZipCompressor extends ArchiveOutputStreamFactory { 22 | 23 | ZipOutputStream createArchiveOutputStream(File destination) 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/relocation/CacheableRelocator.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.relocation 2 | 3 | import java.lang.annotation.ElementType 4 | import java.lang.annotation.Retention 5 | import java.lang.annotation.RetentionPolicy 6 | import java.lang.annotation.Target 7 | 8 | /** 9 | * Marks that a given instance of {@link Relocator} is is compatible with the Gradle build cache. 10 | * In other words, it has its appropriate inputs annotated so that Gradle can consider them when 11 | * determining the cache key. 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.TYPE) 15 | @interface CacheableRelocator { 16 | 17 | } -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/relocation/RelocateClassContext.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.relocation 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.ShadowStats 4 | import groovy.transform.Canonical 5 | import groovy.transform.builder.Builder 6 | 7 | @Canonical 8 | @Builder 9 | class RelocateClassContext { 10 | 11 | String className 12 | ShadowStats stats 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatePathContext.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.relocation 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.ShadowStats 4 | import groovy.transform.Canonical 5 | import groovy.transform.builder.Builder 6 | 7 | @Canonical 8 | @Builder 9 | class RelocatePathContext { 10 | 11 | String path 12 | ShadowStats stats 13 | } 14 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/relocation/Relocator.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.relocation 21 | 22 | /** 23 | * Modified from org.apache.maven.plugins.shade.relocation.Relocator.java 24 | * 25 | * @author Jason van Zyl 26 | * @author John Engelman 27 | */ 28 | interface Relocator { 29 | String ROLE = Relocator.class.getName() 30 | 31 | boolean canRelocatePath(String path) 32 | 33 | String relocatePath(RelocatePathContext context) 34 | 35 | boolean canRelocateClass(String className) 36 | 37 | String relocateClass(RelocateClassContext context) 38 | 39 | String applyToSourceContent(String sourceContent) 40 | } 41 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/DefaultInheritManifest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.tasks 2 | 3 | import org.gradle.api.Action 4 | import org.gradle.api.Project 5 | import org.gradle.api.internal.file.FileResolver 6 | import org.gradle.api.java.archives.Attributes 7 | import org.gradle.api.java.archives.Manifest 8 | import org.gradle.api.java.archives.ManifestException 9 | import org.gradle.api.java.archives.ManifestMergeSpec 10 | import org.gradle.api.java.archives.internal.DefaultManifest 11 | import org.gradle.api.java.archives.internal.DefaultManifestMergeSpec 12 | 13 | class DefaultInheritManifest implements InheritManifest { 14 | 15 | private List inheritMergeSpecs = [] 16 | 17 | private final transient Project project 18 | 19 | private final FileResolver fileResolver 20 | 21 | private final Manifest internalManifest 22 | 23 | DefaultInheritManifest(Project project, FileResolver fileResolver) { 24 | this.project = project 25 | this.internalManifest = new DefaultManifest(fileResolver) 26 | this.fileResolver = fileResolver 27 | } 28 | 29 | InheritManifest inheritFrom(Object... inheritPaths) { 30 | inheritFrom(inheritPaths, null) 31 | return this 32 | } 33 | 34 | InheritManifest inheritFrom(Object inheritPaths, Closure closure) { 35 | DefaultManifestMergeSpec mergeSpec = new DefaultManifestMergeSpec() 36 | mergeSpec.from(inheritPaths) 37 | inheritMergeSpecs.add(mergeSpec) 38 | project.configure(mergeSpec, closure) 39 | return this 40 | } 41 | 42 | @Override 43 | Attributes getAttributes() { 44 | return internalManifest.getAttributes() 45 | } 46 | 47 | @Override 48 | Map getSections() { 49 | return internalManifest.getSections() 50 | } 51 | 52 | @Override 53 | Manifest attributes(Map map) throws ManifestException { 54 | internalManifest.attributes(map) 55 | return this 56 | } 57 | 58 | @Override 59 | Manifest attributes(Map map, String s) throws ManifestException { 60 | internalManifest.attributes(map, s) 61 | return this 62 | } 63 | 64 | @Override 65 | DefaultManifest getEffectiveManifest() { 66 | DefaultManifest base = new DefaultManifest(fileResolver) 67 | inheritMergeSpecs.each { 68 | base = it.merge(base, fileResolver) 69 | } 70 | base.from internalManifest 71 | return base.getEffectiveManifest() 72 | } 73 | 74 | Manifest writeTo(Writer writer) { 75 | this.getEffectiveManifest().writeTo((Object) writer) 76 | return this 77 | } 78 | 79 | @Override 80 | Manifest writeTo(Object o) { 81 | this.getEffectiveManifest().writeTo(o) 82 | return this 83 | } 84 | 85 | @Override 86 | Manifest from(Object... objects) { 87 | internalManifest.from(objects) 88 | return this 89 | } 90 | 91 | @Override 92 | Manifest from(Object o, Closure closure) { 93 | internalManifest.from(o, closure) 94 | return this 95 | } 96 | 97 | @Override 98 | Manifest from(Object o, Action action) { 99 | internalManifest.from(o, action) 100 | return this 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/InheritManifest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.tasks 2 | 3 | import org.gradle.api.java.archives.Manifest 4 | 5 | interface InheritManifest extends Manifest { 6 | 7 | InheritManifest inheritFrom(Object... inheritPaths) 8 | 9 | InheritManifest inheritFrom(inheritPaths, Closure closure) 10 | } 11 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/KnowsTask.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.tasks 2 | 3 | import org.codehaus.groovy.reflection.ReflectionUtils 4 | import org.gradle.api.DefaultTask 5 | import org.gradle.api.tasks.TaskAction 6 | 7 | class KnowsTask extends DefaultTask { 8 | 9 | public static final String NAME = "knows" 10 | public static final String DESC = "Do you know who knows?" 11 | 12 | @TaskAction 13 | def knows() { 14 | println "\nNo, The Shadow Knows...." 15 | println ReflectionUtils.getCallingClass(0).getResourceAsStream("/shadowBanner.txt").text 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowSpec.java: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.tasks; 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.ShadowStats; 4 | import com.github.jengelman.gradle.plugins.shadow.internal.DependencyFilter; 5 | import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator; 6 | import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator; 7 | import com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer; 8 | import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer; 9 | import org.gradle.api.Action; 10 | import org.gradle.api.file.CopySpec; 11 | 12 | import java.lang.reflect.InvocationTargetException; 13 | 14 | interface ShadowSpec extends CopySpec { 15 | ShadowSpec minimize(); 16 | 17 | ShadowSpec minimize(Action configureClosure); 18 | 19 | ShadowSpec dependencies(Action configure); 20 | 21 | ShadowSpec transform(Class clazz) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException; 22 | 23 | ShadowSpec transform(Class clazz, Action configure) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException; 24 | 25 | ShadowSpec transform(Transformer transformer); 26 | 27 | ShadowSpec mergeServiceFiles(); 28 | 29 | ShadowSpec mergeServiceFiles(String rootPath); 30 | 31 | ShadowSpec mergeServiceFiles(Action configureClosure); 32 | 33 | ShadowSpec mergeGroovyExtensionModules(); 34 | 35 | ShadowSpec append(String resourcePath); 36 | 37 | ShadowSpec relocate(String pattern, String destination); 38 | 39 | ShadowSpec relocate(String pattern, String destination, Action configure); 40 | 41 | ShadowSpec relocate(Relocator relocator); 42 | 43 | ShadowSpec relocate(Class clazz) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException; 44 | 45 | ShadowSpec relocate(Class clazz, Action configure) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException; 46 | 47 | ShadowStats getStats(); 48 | } 49 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheLicenseResourceTransformer.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.transformers 21 | 22 | import org.apache.tools.zip.ZipOutputStream 23 | import org.gradle.api.file.FileTreeElement 24 | 25 | /** 26 | * Prevents duplicate copies of the license 27 | *

28 | * Modified from org.apache.maven.plugins.shade.resouce.ApacheLicenseResourceTransformer.java 29 | * 30 | * @author John Engelman 31 | */ 32 | class ApacheLicenseResourceTransformer implements Transformer { 33 | 34 | private static final String LICENSE_PATH = "META-INF/LICENSE" 35 | 36 | private static final String LICENSE_TXT_PATH = "META-INF/LICENSE.txt" 37 | 38 | boolean canTransformResource(FileTreeElement element) { 39 | def path = element.relativePath.pathString 40 | return LICENSE_PATH.equalsIgnoreCase(path) || 41 | LICENSE_TXT_PATH.regionMatches(true, 0, path, 0, LICENSE_TXT_PATH.length()) 42 | } 43 | 44 | void transform(TransformerContext context) { 45 | 46 | } 47 | 48 | boolean hasTransformedResource() { 49 | return false 50 | } 51 | 52 | void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformer.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.transformers 21 | 22 | import org.apache.tools.zip.ZipEntry 23 | import org.apache.tools.zip.ZipOutputStream 24 | import org.codehaus.plexus.util.IOUtil 25 | import org.gradle.api.file.FileTreeElement 26 | import org.gradle.api.tasks.Input 27 | import org.gradle.api.tasks.Optional 28 | 29 | /** 30 | * A resource processor that appends content for a resource, separated by a newline. 31 | * 32 | * Modified from org.apache.maven.plugins.shade.resource.AppendingTransformer.java 33 | * 34 | * Modifications 35 | * @author John Engelman 36 | */ 37 | @CacheableTransformer 38 | class AppendingTransformer implements Transformer { 39 | 40 | @Optional 41 | @Input 42 | String resource 43 | 44 | /** 45 | * Defer initialization, see https://github.com/johnrengelman/shadow/issues/763 46 | */ 47 | private ByteArrayOutputStream data 48 | 49 | boolean canTransformResource(FileTreeElement element) { 50 | def path = element.relativePath.pathString 51 | if (resource != null && resource.equalsIgnoreCase(path)) { 52 | return true 53 | } 54 | 55 | return false 56 | } 57 | 58 | void transform(TransformerContext context) { 59 | if (data == null) { 60 | data = new ByteArrayOutputStream() 61 | } 62 | 63 | IOUtil.copy(context.is, data) 64 | data.write('\n'.bytes) 65 | 66 | context.is.close() 67 | } 68 | 69 | boolean hasTransformedResource() { 70 | return data?.size() > 0 71 | } 72 | 73 | void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { 74 | if (data == null) { 75 | data = new ByteArrayOutputStream() 76 | } 77 | 78 | ZipEntry entry = new ZipEntry(resource) 79 | entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) 80 | os.putNextEntry(entry) 81 | 82 | IOUtil.copy(new ByteArrayInputStream(data.toByteArray()), os) 83 | data.reset() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/CacheableTransformer.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.transformers 2 | 3 | import java.lang.annotation.ElementType 4 | import java.lang.annotation.Retention 5 | import java.lang.annotation.RetentionPolicy 6 | import java.lang.annotation.Target 7 | 8 | /** 9 | * Marks that a given instance of {@link Transformer} is is compatible with the Gradle build cache. 10 | * In other words, it has its appropriate inputs annotated so that Gradle can consider them when 11 | * determining the cache key. 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.TYPE) 15 | @interface CacheableTransformer { 16 | 17 | } -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/DontIncludeResourceTransformer.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.transformers 21 | 22 | import org.apache.tools.zip.ZipOutputStream 23 | import org.codehaus.plexus.util.StringUtils 24 | import org.gradle.api.file.FileTreeElement 25 | import org.gradle.api.tasks.Input 26 | import org.gradle.api.tasks.Optional 27 | 28 | /** 29 | * A resource processor that prevents the inclusion of an arbitrary 30 | * resource into the shaded JAR. 31 | *

32 | * Modified from org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer.java 33 | * 34 | * @author John Engelman 35 | */ 36 | class DontIncludeResourceTransformer implements Transformer { 37 | 38 | @Optional 39 | @Input 40 | String resource 41 | 42 | boolean canTransformResource(FileTreeElement element) { 43 | def path = element.relativePath.pathString 44 | if (StringUtils.isNotEmpty(resource) && path.endsWith(resource)) { 45 | return true 46 | } 47 | 48 | return false 49 | } 50 | 51 | void transform(TransformerContext context) { 52 | // no op 53 | } 54 | 55 | boolean hasTransformedResource() { 56 | return false 57 | } 58 | 59 | void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { 60 | // no op 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/IncludeResourceTransformer.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.transformers 21 | 22 | import org.apache.tools.zip.ZipEntry 23 | import org.apache.tools.zip.ZipOutputStream 24 | import org.codehaus.plexus.util.IOUtil 25 | import org.gradle.api.file.FileTreeElement 26 | import org.gradle.api.tasks.Input 27 | import org.gradle.api.tasks.InputFile 28 | import org.gradle.api.tasks.PathSensitive 29 | import org.gradle.api.tasks.PathSensitivity 30 | 31 | /** 32 | * A resource processor that allows the addition of an arbitrary file 33 | * content into the shaded JAR. 34 | *

35 | * Modified from org.apache.maven.plugins.shade.resource.IncludeResourceTransformer.java 36 | * 37 | * @author John Engelman 38 | */ 39 | class IncludeResourceTransformer implements Transformer { 40 | 41 | @InputFile 42 | @PathSensitive(PathSensitivity.NONE) 43 | File file 44 | 45 | @Input 46 | String resource 47 | 48 | boolean canTransformResource(FileTreeElement element) { 49 | return false 50 | } 51 | 52 | void transform(TransformerContext context) { 53 | // no op 54 | } 55 | 56 | boolean hasTransformedResource() { 57 | return file != null ? file.exists() : false 58 | } 59 | 60 | void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { 61 | ZipEntry entry = new ZipEntry(resource) 62 | entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) 63 | os.putNextEntry(entry) 64 | 65 | InputStream is = new FileInputStream(file) 66 | IOUtil.copy(is, os) 67 | is.close() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ManifestAppenderTransformer.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.transformers 21 | 22 | import org.apache.tools.zip.ZipEntry 23 | import org.apache.tools.zip.ZipOutputStream 24 | import org.codehaus.plexus.util.IOUtil 25 | import org.gradle.api.file.FileTreeElement 26 | import org.gradle.api.tasks.Input 27 | 28 | import static java.nio.charset.StandardCharsets.UTF_8 29 | import static java.util.jar.JarFile.MANIFEST_NAME 30 | 31 | /** 32 | * A resource processor that can append arbitrary attributes to the first MANIFEST.MF 33 | * that is found in the set of JARs being processed. The attributes are appended in 34 | * the specified order, and duplicates are allowed. 35 | *

36 | * Modified from {@link ManifestResourceTransformer}. 37 | * @author Chris Rankin 38 | */ 39 | class ManifestAppenderTransformer implements Transformer { 40 | private static final byte[] EOL = "\r\n".getBytes(UTF_8) 41 | private static final byte[] SEPARATOR = ": ".getBytes(UTF_8) 42 | 43 | private byte[] manifestContents = [] 44 | private final List>> attributes = [] 45 | 46 | @Input 47 | List>> getAttributes() { attributes } 48 | 49 | ManifestAppenderTransformer append(String name, Comparable value) { 50 | attributes.add(new Tuple2>(name, value)) 51 | this 52 | } 53 | 54 | @Override 55 | boolean canTransformResource(FileTreeElement element) { 56 | MANIFEST_NAME.equalsIgnoreCase(element.relativePath.pathString) 57 | } 58 | 59 | @Override 60 | void transform(TransformerContext context) { 61 | if (manifestContents.length == 0) { 62 | manifestContents = IOUtil.toByteArray(context.is) 63 | try { 64 | context.is 65 | } catch (IOException ignored) { 66 | } 67 | } 68 | } 69 | 70 | @Override 71 | boolean hasTransformedResource() { 72 | !attributes.isEmpty() 73 | } 74 | 75 | @Override 76 | void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { 77 | ZipEntry entry = new ZipEntry(MANIFEST_NAME) 78 | entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) 79 | os.putNextEntry(entry) 80 | os.write(manifestContents) 81 | 82 | if (!attributes.isEmpty()) { 83 | for (attribute in attributes) { 84 | os.write(attribute.v1.getBytes(UTF_8)) 85 | os.write(SEPARATOR) 86 | os.write(attribute.v2.toString().getBytes(UTF_8)) 87 | os.write(EOL) 88 | } 89 | os.write(EOL) 90 | attributes.clear() 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ManifestResourceTransformer.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.transformers 21 | 22 | import org.apache.tools.zip.ZipEntry 23 | import org.apache.tools.zip.ZipOutputStream 24 | import org.gradle.api.file.FileTreeElement 25 | import org.gradle.api.tasks.Input 26 | import org.gradle.api.tasks.Optional 27 | 28 | import java.util.jar.Attributes 29 | import java.util.jar.Attributes.Name 30 | import java.util.jar.JarFile 31 | import java.util.jar.Manifest 32 | 33 | /** 34 | * A resource processor that allows the arbitrary addition of attributes to 35 | * the first MANIFEST.MF that is found in the set of JARs being processed, or 36 | * to a newly created manifest for the shaded JAR. 37 | *

38 | * Modified from org.apache.maven.plugins.shade.resource.ManifestResourceTransformer 39 | * @author Jason van Zyl 40 | * @author John Engelman 41 | */ 42 | class ManifestResourceTransformer implements Transformer { 43 | 44 | // Configuration 45 | @Optional 46 | @Input 47 | String mainClass 48 | 49 | @Optional 50 | @Input 51 | Map manifestEntries 52 | 53 | // Fields 54 | private boolean manifestDiscovered 55 | 56 | private Manifest manifest 57 | 58 | boolean canTransformResource(FileTreeElement element) { 59 | def path = element.relativePath.pathString 60 | if (JarFile.MANIFEST_NAME.equalsIgnoreCase(path)) { 61 | return true 62 | } 63 | 64 | return false 65 | } 66 | 67 | void transform(TransformerContext context) { 68 | // We just want to take the first manifest we come across as that's our project's manifest. This is the behavior 69 | // now which is situational at best. Right now there is no context passed in with the processing so we cannot 70 | // tell what artifact is being processed. 71 | if (!manifestDiscovered) { 72 | manifest = new Manifest(context.is) 73 | manifestDiscovered = true 74 | try { 75 | context.is 76 | } catch (IOException ignored) { 77 | } 78 | } 79 | } 80 | 81 | boolean hasTransformedResource() { 82 | return true 83 | } 84 | 85 | void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { 86 | // If we didn't find a manifest, then let's create one. 87 | if (manifest == null) { 88 | manifest = new Manifest() 89 | } 90 | 91 | Attributes attributes = manifest.getMainAttributes() 92 | 93 | if (mainClass != null) { 94 | attributes.put(Name.MAIN_CLASS, mainClass) 95 | } 96 | 97 | if (manifestEntries != null) { 98 | for (Map.Entry entry : manifestEntries.entrySet()) { 99 | attributes.put(new Name(entry.getKey()), entry.getValue()) 100 | } 101 | } 102 | 103 | ZipEntry entry = new ZipEntry(JarFile.MANIFEST_NAME) 104 | entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) 105 | os.putNextEntry(entry) 106 | manifest.write(os) 107 | } 108 | 109 | ManifestResourceTransformer attributes(Map attributes) { 110 | if (manifestEntries == null) { 111 | manifestEntries = [:] 112 | } 113 | manifestEntries.putAll(attributes) 114 | this 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/Transformer.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.transformers 21 | 22 | import org.apache.tools.zip.ZipOutputStream 23 | import org.gradle.api.Named 24 | import org.gradle.api.file.FileTreeElement 25 | import org.gradle.api.tasks.Internal 26 | 27 | /** 28 | * Modified from org.apache.maven.plugins.shade.resource.ResourceTransformer.java 29 | * 30 | * @author Jason van Zyl 31 | * @author Charlie Knudsen 32 | * @author John Engelman 33 | */ 34 | trait Transformer implements Named { 35 | 36 | abstract boolean canTransformResource(FileTreeElement element) 37 | 38 | abstract void transform(TransformerContext context) 39 | 40 | abstract boolean hasTransformedResource() 41 | 42 | abstract void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) 43 | 44 | @Internal 45 | String getName() { 46 | return getClass().simpleName 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/TransformerContext.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.transformers 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.ShadowStats 4 | import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator 5 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowCopyAction 6 | import groovy.transform.Canonical 7 | import groovy.transform.builder.Builder 8 | 9 | 10 | @Canonical 11 | @Builder 12 | class TransformerContext { 13 | 14 | String path 15 | InputStream is 16 | List relocators 17 | ShadowStats stats 18 | 19 | static long getEntryTimestamp(boolean preserveFileTimestamps, long entryTime) { 20 | preserveFileTimestamps ? entryTime : ShadowCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/com/github/jengelman/gradle/plugins/shadow/internal/windowsStartScript.txt: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem ${applicationName} startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=.\ 13 | 14 | set APP_BASE_NAME=%~n0 15 | set APP_HOME=%DIRNAME%${appHomeRelativePath} 16 | 17 | @rem Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script. 18 | set DEFAULT_JVM_OPTS=${defaultJvmOpts} 19 | 20 | @rem Find java.exe 21 | if defined JAVA_HOME goto findJavaFromJavaHome 22 | 23 | set JAVA_EXE=java.exe 24 | %JAVA_EXE% -version >NUL 2>&1 25 | if "%ERRORLEVEL%" == "0" goto init 26 | 27 | echo. 28 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 29 | echo. 30 | echo Please set the JAVA_HOME variable in your environment to match the 31 | echo location of your Java installation. 32 | 33 | goto fail 34 | 35 | :findJavaFromJavaHome 36 | set JAVA_HOME=%JAVA_HOME:"=% 37 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 38 | 39 | if exist "%JAVA_EXE%" goto init 40 | 41 | echo. 42 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 43 | echo. 44 | echo Please set the JAVA_HOME variable in your environment to match the 45 | echo location of your Java installation. 46 | 47 | goto fail 48 | 49 | :init 50 | @rem Get command-line arguments, handling Windows variants 51 | 52 | if not "%OS%" == "Windows_NT" goto win9xME_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | 64 | :execute 65 | @rem Setup the command line 66 | 67 | set CLASSPATH=$classpath 68 | 69 | @rem Execute ${applicationName} 70 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %${optsEnvironmentVar}% <% if ( appNameSystemProperty ) { %>"-D${appNameSystemProperty}=%APP_BASE_NAME%"<% } %> -jar "%CLASSPATH%" %CMD_LINE_ARGS% 71 | 72 | :end 73 | @rem End local scope for the variables with windows NT shell 74 | if "%ERRORLEVEL%"=="0" goto mainEnd 75 | 76 | :fail 77 | rem Set variable ${exitEnvironmentVar} if you need the _script_ return code instead of 78 | rem the _cmd.exe /c_ return code! 79 | if not "" == "%${exitEnvironmentVar}%" exit 1 80 | exit /b 1 81 | 82 | :mainEnd 83 | if "%OS%"=="Windows_NT" endlocal 84 | 85 | :omega -------------------------------------------------------------------------------- /src/main/resources/shadow-version.txt: -------------------------------------------------------------------------------- 1 | 8.1.1 2 | -------------------------------------------------------------------------------- /src/main/resources/shadowBanner.txt: -------------------------------------------------------------------------------- 1 | 2 | . 3 | .MMMMMO .M 4 | .MMMMMMMMM. MMMM. 5 | .MMMMMMMMMMMMMMM. 6 | .MMMMMMMMMMMMM 7 | .MMMMMMMMMMMM 8 | .+MMMMMMMMM,ZMMM. 9 | ...7MM8D8MM.ZMMMMM. 10 | .. MMZ..MZZNMMMMM 11 | .... MMMMMMMZZZ.MMMMMMOOOOOO.. 12 | ... 7MMMMMMMMMZZZMIMMMMOOOOOMMMM.. 13 | .. .~. .MMMMMOMZZZMZMMOOOOOMMMM MM. 14 | .MMMMM ..MMM.7DOMOMOOOOOOOMM MMMMM Z 15 | .. MMMMMMM.. . ...MMMMMMMMMOOOOOOMMMMMMMMMM 16 | . .MMMMMMM. .MMMMM MMMMMOMOMMMMMMMMMMM 17 | MMMMMMMMM .MMM.MMMMMMMMMMMOMMMMMMMMMMM 18 | .MMMMMMMM $MMMM MMMMMMMMMMMMMMMMMM MMM 19 | MMMMMMNMMMMMMMM M.MMMMMM.MMMMMMMM MMMMMM 20 | ..MMMMMMMMMMMMMMMMMMMMMMMMMMMM.MNMMMMMMM . 21 | ...MMMMMMMMMMM MMMMMMMMMMMMM.MMMMMMMM. 22 | MMMMMMMMMM.MMMMMMMMMMMMMDMMMMMMMM. 23 | ..MMMMMMMMMMMMMMMMMMM M,MMMMMMMMMMMMMMMZMMMMM +D , 24 | .:DMM.M. MMMMMMM.MMMMMMMMMMMMMMI:MMMMM :MMO 25 | . MMMMMMMMMMMMMMMMMMMM.MMMMM8 NMMMN 26 | ..MMMMMMMMMMMMMMMMMMMMM MMMMN. 27 | .MMMMMMMMMMMMMMMM. MMM7 , . =. 28 | MMMMMMMMMMMM.$MM M . MM7 29 | MMMMMMMMM=MI:M8 . MNOM M 30 | MMMMMMMMMM. . 31 | MMMMMM . 32 | +MM 33 | 34 | http://2.bp.blogspot.com/-urTvlwNjLeo/UGg5z9lxw5I/AAAAAAAAHRM/RCbSBi4I60s/s1600/The_Shadow_Knows_by_E_Mann.jpeg -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/ConfigureShadowRelocationSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.util.PluginSpecification 4 | 5 | 6 | class ConfigureShadowRelocationSpec extends PluginSpecification { 7 | 8 | def "auto relocate plugin dependencies"() { 9 | given: 10 | buildFile << """ 11 | tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { 12 | enableRelocation true 13 | } 14 | 15 | dependencies { 16 | implementation 'junit:junit:3.8.2' 17 | } 18 | """.stripIndent() 19 | 20 | when: 21 | run('shadowJar', '-s') 22 | 23 | then: 24 | contains(output, [ 25 | 'META-INF/MANIFEST.MF', 26 | 'shadow/junit/textui/ResultPrinter.class', 27 | 'shadow/junit/textui/TestRunner.class', 28 | 'shadow/junit/framework/Assert.class', 29 | 'shadow/junit/framework/AssertionFailedError.class', 30 | 'shadow/junit/framework/ComparisonCompactor.class', 31 | 'shadow/junit/framework/ComparisonFailure.class', 32 | 'shadow/junit/framework/Protectable.class', 33 | 'shadow/junit/framework/Test.class', 34 | 'shadow/junit/framework/TestCase.class', 35 | 'shadow/junit/framework/TestFailure.class', 36 | 'shadow/junit/framework/TestListener.class', 37 | 'shadow/junit/framework/TestResult$1.class', 38 | 'shadow/junit/framework/TestResult.class', 39 | 'shadow/junit/framework/TestSuite$1.class', 40 | 'shadow/junit/framework/TestSuite.class' 41 | ]) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/caching/AbstractCachingSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.caching 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.util.PluginSpecification 4 | import org.apache.commons.io.FileUtils 5 | import org.gradle.testkit.runner.BuildResult 6 | import org.gradle.testkit.runner.TaskOutcome 7 | import org.junit.Rule 8 | import org.junit.rules.TemporaryFolder 9 | 10 | import static org.gradle.testkit.runner.TaskOutcome.FROM_CACHE 11 | import static org.gradle.testkit.runner.TaskOutcome.SUCCESS 12 | 13 | abstract class AbstractCachingSpec extends PluginSpecification { 14 | @Rule TemporaryFolder alternateDir 15 | 16 | def setup() { 17 | // Use a test-specific build cache directory. This ensures that we'll only use cached outputs generated during this 18 | // test and we won't accidentally use cached outputs from a different test or a different build. 19 | settingsFile << """ 20 | buildCache { 21 | local { 22 | directory = new File(rootDir, 'build-cache') 23 | } 24 | } 25 | """ 26 | } 27 | 28 | void changeConfigurationTo(String content) { 29 | buildFile.text = defaultBuildScript 30 | buildFile << content 31 | } 32 | 33 | BuildResult runWithCacheEnabled(String... arguments) { 34 | List cacheArguments = [ '--build-cache' ] 35 | cacheArguments.addAll(arguments) 36 | return run(cacheArguments) 37 | } 38 | 39 | BuildResult runInAlternateDirWithCacheEnabled(String... arguments) { 40 | List cacheArguments = [ '--build-cache' ] 41 | cacheArguments.addAll(arguments) 42 | // TODO: Use PluginSpecification.run here to reuse flags, but cache tests failed for now, need to investigate. 43 | return runner.withProjectDir(alternateDir.root).withArguments(cacheArguments).build() 44 | } 45 | 46 | private String escapedPath(File file) { 47 | file.path.replaceAll('\\\\', '\\\\\\\\') 48 | } 49 | 50 | void assertShadowJarHasResult(TaskOutcome expectedOutcome) { 51 | def result = runWithCacheEnabled(shadowJarTask) 52 | assert result.task(shadowJarTask).outcome == expectedOutcome 53 | } 54 | 55 | void assertShadowJarHasResultInAlternateDir(TaskOutcome expectedOutcome) { 56 | def result = runInAlternateDirWithCacheEnabled(shadowJarTask) 57 | assert result.task(shadowJarTask).outcome == expectedOutcome 58 | } 59 | 60 | void copyToAlternateDir() { 61 | FileUtils.deleteDirectory(alternateDir.root) 62 | FileUtils.forceMkdir(alternateDir.root) 63 | FileUtils.copyDirectory(dir.root, alternateDir.root) 64 | } 65 | 66 | void assertShadowJarIsCachedAndRelocatable() { 67 | deleteOutputs() 68 | copyToAlternateDir() 69 | // check that shadowJar pulls from cache in the original directory 70 | assertShadowJarHasResult(FROM_CACHE) 71 | // check that shadowJar pulls from cache in a different directory 72 | assertShadowJarHasResultInAlternateDir(FROM_CACHE) 73 | } 74 | 75 | void assertShadowJarExecutes() { 76 | deleteOutputs() 77 | // task was executed and not pulled from cache 78 | assertShadowJarHasResult(SUCCESS) 79 | } 80 | 81 | void deleteOutputs() { 82 | if (output.exists()) { 83 | assert output.delete() 84 | } 85 | } 86 | 87 | String getShadowJarTask() { 88 | return ":shadowJar" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/caching/MinimizationCachingSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.caching 2 | 3 | import org.gradle.testkit.runner.BuildResult 4 | 5 | import static org.gradle.testkit.runner.TaskOutcome.* 6 | 7 | class MinimizationCachingSpec extends AbstractCachingSpec { 8 | File output 9 | String shadowJarTask = ":server:shadowJar" 10 | 11 | /** 12 | * Ensure that we get a cache miss when minimization is added and that caching works with minimization 13 | */ 14 | def 'shadowJar is cached correctly when minimization is added'() { 15 | given: 16 | file('settings.gradle') << """ 17 | include 'client', 'server' 18 | """.stripIndent() 19 | 20 | file('client/src/main/java/client/Client.java') << """ 21 | package client; 22 | public class Client {} 23 | """.stripIndent() 24 | 25 | file('client/build.gradle') << """ 26 | apply plugin: 'java' 27 | repositories { maven { url "${repo.uri}" } } 28 | dependencies { implementation 'junit:junit:3.8.2' } 29 | """.stripIndent() 30 | 31 | file('server/src/main/java/server/Server.java') << """ 32 | package server; 33 | public class Server {} 34 | """.stripIndent() 35 | 36 | file('server/build.gradle') << """ 37 | apply plugin: 'java' 38 | apply plugin: 'com.github.johnrengelman.shadow' 39 | 40 | repositories { maven { url "${repo.uri}" } } 41 | dependencies { implementation project(':client') } 42 | """.stripIndent() 43 | 44 | output = getFile('server/build/libs/server-all.jar') 45 | 46 | when: 47 | assertShadowJarExecutes() 48 | 49 | then: 50 | output.exists() 51 | contains(output, [ 52 | 'server/Server.class', 53 | 'junit/framework/Test.class', 54 | 'client/Client.class' 55 | ]) 56 | 57 | when: 58 | file('server/build.gradle').text = """ 59 | apply plugin: 'java' 60 | apply plugin: 'com.github.johnrengelman.shadow' 61 | 62 | shadowJar { 63 | minimize { 64 | exclude(dependency('junit:junit:.*')) 65 | } 66 | } 67 | 68 | repositories { maven { url "${repo.uri}" } } 69 | dependencies { implementation project(':client') } 70 | """.stripIndent() 71 | assertShadowJarExecutes() 72 | 73 | then: 74 | output.exists() 75 | contains(output, [ 76 | 'server/Server.class', 77 | 'junit/framework/Test.class' 78 | ]) 79 | doesNotContain(output, ['client/Client.class']) 80 | 81 | when: 82 | assertShadowJarIsCachedAndRelocatable() 83 | 84 | then: 85 | output.exists() 86 | contains(output, [ 87 | 'server/Server.class', 88 | 'junit/framework/Test.class' 89 | ]) 90 | doesNotContain(output, ['client/Client.class']) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/caching/RelocationCachingSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.caching 2 | 3 | class RelocationCachingSpec extends AbstractCachingSpec { 4 | /** 5 | * Ensure that we get a cache miss when relocation changes and that caching works with relocation 6 | */ 7 | def 'shadowJar is cached correctly when relocation is added'() { 8 | given: 9 | buildFile << """ 10 | dependencies { implementation 'junit:junit:3.8.2' } 11 | """.stripIndent() 12 | 13 | file('src/main/java/server/Server.java') << """ 14 | package server; 15 | 16 | import junit.framework.Test; 17 | 18 | public class Server {} 19 | """.stripIndent() 20 | 21 | when: 22 | assertShadowJarExecutes() 23 | 24 | then: 25 | output.exists() 26 | contains(output, [ 27 | 'server/Server.class', 28 | 'junit/framework/Test.class' 29 | ]) 30 | 31 | when: 32 | changeConfigurationTo """ 33 | dependencies { implementation 'junit:junit:3.8.2' } 34 | 35 | shadowJar { 36 | relocate 'junit.framework', 'foo.junit.framework' 37 | } 38 | """ 39 | assertShadowJarExecutes() 40 | 41 | then: 42 | output.exists() 43 | contains(output, [ 44 | 'server/Server.class', 45 | 'foo/junit/framework/Test.class' 46 | ]) 47 | 48 | and: 49 | doesNotContain(output, [ 50 | 'junit/framework/Test.class' 51 | ]) 52 | 53 | when: 54 | assertShadowJarIsCachedAndRelocatable() 55 | 56 | then: 57 | output.exists() 58 | contains(output, [ 59 | 'server/Server.class', 60 | 'foo/junit/framework/Test.class' 61 | ]) 62 | 63 | and: 64 | doesNotContain(output, [ 65 | 'junit/framework/Test.class' 66 | ]) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/ManualCodeSnippetTests.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.docs.executer.GradleBuildExecuter 4 | import com.github.jengelman.gradle.plugins.shadow.docs.executer.NoopExecuter 5 | import com.github.jengelman.gradle.plugins.shadow.docs.extractor.ManualSnippetExtractor 6 | import com.github.jengelman.gradle.plugins.shadow.docs.fixture.GroovyDslFixture 7 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.CodeSnippetTestCase 8 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.CodeSnippetTests 9 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.executer.SnippetExecuter 10 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.fixture.GroovyScriptFixture 11 | import com.google.common.base.StandardSystemProperty 12 | 13 | class ManualCodeSnippetTests extends CodeSnippetTestCase { 14 | 15 | public static final LinkedHashMap FIXTURES = [ 16 | "groovy": new GradleBuildExecuter("build.gradle", new GroovyDslFixture(), new GroovyDslFixture.ImportsExtractor()), 17 | "groovy no-plugins": new GradleBuildExecuter("build.gradle", new GroovyScriptFixture(), new GroovyDslFixture.ImportsExtractor()), 18 | "groovy no-run": new NoopExecuter() 19 | ] 20 | 21 | @Override 22 | protected void addTests(CodeSnippetTests tests) { 23 | File cwd = new File(StandardSystemProperty.USER_DIR.value()) 24 | 25 | def content = new File(cwd, "src/docs") 26 | 27 | FIXTURES.each { selector, executer -> 28 | ManualSnippetExtractor.extract(content, selector, executer).each { 29 | tests.add(it) 30 | } 31 | } 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/executer/GradleBuildExecuter.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.executer 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.TestCodeSnippet 4 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.executer.SnippetExecuter 5 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.fixture.SnippetFixture 6 | import com.github.jengelman.gradle.plugins.shadow.util.PluginSpecification 7 | import org.gradle.testkit.runner.GradleRunner 8 | import org.junit.rules.TemporaryFolder 9 | 10 | import java.util.function.Function 11 | 12 | class GradleBuildExecuter implements SnippetExecuter { 13 | 14 | private final SnippetFixture fixture 15 | private final String buildFile 16 | private final Function> importExtractor 17 | 18 | private List arguments = ["build", "-m"] 19 | 20 | GradleBuildExecuter(String buildFile, SnippetFixture fixture, Function> importExtractor) { 21 | this.buildFile = buildFile 22 | this.fixture = fixture 23 | this.importExtractor = importExtractor 24 | } 25 | 26 | GradleBuildExecuter(String buildFile, List arguments, SnippetFixture fixture, Function> importExtractor) { 27 | this(buildFile, fixture, importExtractor) 28 | this.arguments = arguments 29 | } 30 | 31 | @Override 32 | SnippetFixture getFixture() { 33 | return fixture 34 | } 35 | 36 | @Override 37 | void execute(TestCodeSnippet snippet) throws Exception { 38 | TemporaryFolder tempDir = new TemporaryFolder() 39 | tempDir.create() 40 | File dir = tempDir.newFolder() 41 | 42 | addSubProject(dir) 43 | File settings = new File(dir, "settings.gradle") 44 | settings.text = """ 45 | rootProject.name = 'shadowTest' 46 | include 'api', 'main' 47 | """ 48 | 49 | File mainDir = new File(dir, "main") 50 | mainDir.mkdirs() 51 | File buildFile = new File(mainDir, buildFile) 52 | 53 | 54 | List importsAndSnippet = importExtractor.apply(snippet.getSnippet()) 55 | 56 | String imports = importsAndSnippet.get(0) 57 | String snippetMinusImports = fixture.transform(importsAndSnippet.get(1)) 58 | String fullSnippet = imports + fixture.pre() + snippetMinusImports + fixture.post() 59 | 60 | buildFile.text = replaceTokens(fullSnippet) 61 | 62 | GradleRunner runner = GradleRunner.create().withProjectDir(dir).withPluginClasspath().forwardOutput() 63 | 64 | runner.withArguments(":main:build", "-m").build() 65 | 66 | } 67 | 68 | private static void addSubProject(File dir) { 69 | File api = new File(dir, "api") 70 | api.mkdirs() 71 | File build = new File(api, "build.gradle") 72 | build.text = """ 73 | plugins { 74 | id 'java' 75 | id 'com.github.johnrengelman.shadow' 76 | } 77 | 78 | repositories { 79 | mavenLocal() 80 | mavenCentral() 81 | } 82 | """ 83 | } 84 | 85 | private static String replaceTokens(String snippet) { 86 | return snippet.replaceAll("@version@", PluginSpecification.SHADOW_VERSION + '-SNAPSHOT') 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/executer/NoopExecuter.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.executer 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.TestCodeSnippet 4 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.executer.SnippetExecuter 5 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.fixture.SnippetFixture 6 | 7 | class NoopExecuter implements SnippetExecuter { 8 | 9 | @Override 10 | SnippetFixture getFixture() { 11 | return null 12 | } 13 | 14 | @Override 15 | void execute(TestCodeSnippet snippet) throws Exception { 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/extractor/ManualSnippetExtractor.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.extractor 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.TestCodeSnippet 4 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.executer.ExceptionTransformer 5 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.executer.SnippetExecuter 6 | import groovy.ant.FileNameFinder 7 | 8 | import java.util.regex.Pattern 9 | 10 | class ManualSnippetExtractor { 11 | 12 | static List extract(File root, String cssClass, SnippetExecuter executer) { 13 | List snippets = [] 14 | 15 | def snippetBlockPattern = Pattern.compile(/(?ims)```$cssClass\n(.*?)\n```/) 16 | def filenames = new FileNameFinder().getFileNames(root.absolutePath, "**/*.md") 17 | 18 | filenames.each { filename -> 19 | def file = new File(filename) 20 | addSnippets(snippets, file, snippetBlockPattern, executer) 21 | } 22 | 23 | snippets 24 | } 25 | 26 | private static void addSnippets(List snippets, File file, Pattern snippetBlockPattern, SnippetExecuter executer) { 27 | def source = file.text 28 | String testName = file.parentFile.name + "/" +file.name 29 | Map snippetsByLine = findSnippetsByLine(source, snippetBlockPattern) 30 | 31 | snippetsByLine.each { lineNumber, snippet -> 32 | snippets << createSnippet(testName, file, lineNumber, snippet, executer) 33 | } 34 | } 35 | 36 | private static List findSnippetBlocks(String code, Pattern snippetTagPattern) { 37 | List tags = [] 38 | code.eachMatch(snippetTagPattern) { matches -> 39 | tags.add(matches[0]) 40 | } 41 | tags 42 | } 43 | 44 | private static Map findSnippetsByLine(String source, Pattern snippetTagPattern) { 45 | List snippetBlocks = findSnippetBlocks(source, snippetTagPattern) 46 | Map snippetBlocksByLine = [:] 47 | 48 | int codeIndex = 0 49 | snippetBlocks.each { block -> 50 | codeIndex = source.indexOf(block, codeIndex) 51 | def lineNumber = source.substring(0, codeIndex).readLines().size() + 2 52 | snippetBlocksByLine.put(lineNumber, extractSnippetFromBlock(block)) 53 | codeIndex += block.size() 54 | } 55 | 56 | snippetBlocksByLine 57 | } 58 | 59 | private static String extractSnippetFromBlock(String tag) { 60 | tag.substring(tag.indexOf("\n") + 1, tag.lastIndexOf("\n")) 61 | } 62 | 63 | private static TestCodeSnippet createSnippet(String sourceClassName, File sourceFile, int lineNumber, String snippet, SnippetExecuter executer) { 64 | new TestCodeSnippet(snippet, sourceClassName, sourceClassName + ":$lineNumber", executer, new ExceptionTransformer(sourceClassName, sourceFile.name, lineNumber)) 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/fixture/GroovyDslFixture.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.fixture 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.fixture.GroovyScriptFixture 4 | 5 | import java.util.function.Function 6 | 7 | class GroovyDslFixture extends GroovyScriptFixture { 8 | 9 | @Override 10 | String pre() { 11 | """ 12 | plugins { 13 | id 'java' 14 | id 'com.github.johnrengelman.shadow' 15 | id 'application' 16 | } 17 | 18 | version = "1.0" 19 | group = 'shadow' 20 | 21 | repositories { 22 | mavenLocal() 23 | mavenCentral() 24 | } 25 | """ 26 | } 27 | 28 | static class ImportsExtractor implements Function> { 29 | 30 | @Override 31 | List apply(String snippet) { 32 | StringBuilder imports = new StringBuilder() 33 | StringBuilder scriptMinusImports = new StringBuilder() 34 | 35 | for (String line : snippet.split("\\n")) { 36 | StringBuilder target 37 | if (line.trim().startsWith("import ")) { 38 | target = imports 39 | } else { 40 | target = scriptMinusImports 41 | } 42 | 43 | target.append(line).append("\n") 44 | } 45 | 46 | return Arrays.asList(imports.toString(), scriptMinusImports.toString()) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/internal/Block.java: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.internal; 2 | 3 | public interface Block { 4 | 5 | /** 6 | * Execute the action. 7 | * 8 | * @throws Exception any 9 | */ 10 | void execute() throws Exception; 11 | } -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/internal/snippets/CodeSnippetTestCase.java: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets; 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.junit.DelegatingTestRunner; 4 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.junit.RunnerProvider; 5 | import org.junit.runner.RunWith; 6 | import org.junit.runner.Runner; 7 | 8 | import java.util.LinkedList; 9 | import java.util.List; 10 | 11 | @RunWith(DelegatingTestRunner.class) 12 | abstract public class CodeSnippetTestCase implements RunnerProvider { 13 | 14 | protected abstract void addTests(CodeSnippetTests tests); 15 | 16 | public final List getRunners() { 17 | List runners = new LinkedList<>(); 18 | CodeSnippetTests tests = new DefaultCodeSnippetTests(getClass(), runners); 19 | addTests(tests); 20 | return runners; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/internal/snippets/CodeSnippetTests.java: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets; 2 | 3 | public interface CodeSnippetTests { 4 | 5 | void add(TestCodeSnippet testCodeSnippet); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/internal/snippets/DefaultCodeSnippetTests.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.junit.SnippetRunner 4 | import org.junit.runner.Runner 5 | 6 | class DefaultCodeSnippetTests implements CodeSnippetTests { 7 | 8 | private final Class clazz 9 | private final List runners 10 | 11 | DefaultCodeSnippetTests(Class clazz, List runners) { 12 | this.clazz = clazz 13 | this.runners = runners 14 | } 15 | 16 | void add(TestCodeSnippet snippet) { 17 | runners.add(new SnippetRunner(clazz, snippet)) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/internal/snippets/TestCodeSnippet.java: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets; 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.executer.ExceptionTransformer; 4 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.executer.SnippetExecuter; 5 | 6 | public class TestCodeSnippet { 7 | 8 | private final String snippet; 9 | private final String className; 10 | private final String testName; 11 | private final SnippetExecuter executer; 12 | private final ExceptionTransformer exceptionTransformer; 13 | 14 | public TestCodeSnippet(String snippet, String className, String testName, SnippetExecuter executer, ExceptionTransformer exceptionTransformer) { 15 | this.snippet = snippet; 16 | this.className = className; 17 | this.testName = testName; 18 | this.executer = executer; 19 | this.exceptionTransformer = exceptionTransformer; 20 | } 21 | 22 | public String getSnippet() { 23 | return snippet; 24 | } 25 | 26 | public String getClassName() { 27 | return className; 28 | } 29 | 30 | public String getTestName() { 31 | return testName; 32 | } 33 | 34 | public ExceptionTransformer getExceptionTransformer() { 35 | return exceptionTransformer; 36 | } 37 | 38 | public SnippetExecuter getExecuter() { 39 | return executer; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/internal/snippets/executer/CompileException.java: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.executer; 2 | 3 | public class CompileException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 0; 6 | 7 | private final int lineNo; 8 | 9 | public CompileException(Throwable cause, int lineNo) { 10 | super(cause); 11 | this.lineNo = lineNo; 12 | } 13 | 14 | public int getLineNo() { 15 | return lineNo; 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/internal/snippets/executer/ExceptionTransformer.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.executer 2 | 3 | class ExceptionTransformer { 4 | 5 | final String sourceClassName 6 | final String sourceFileName 7 | final Integer lineNumber 8 | 9 | ExceptionTransformer(String sourceClassName, String sourceFileName, Integer lineNumber) { 10 | this.sourceClassName = sourceClassName 11 | this.sourceFileName = sourceFileName 12 | this.lineNumber = lineNumber 13 | } 14 | 15 | Throwable transform(Throwable throwable, Integer offset) throws Exception { 16 | def errorLine = 0 17 | 18 | if (throwable instanceof CompileException) { 19 | errorLine = throwable.lineNo 20 | } else { 21 | def frame = throwable.getStackTrace().find { it.fileName == sourceClassName } 22 | if (frame) { 23 | errorLine = frame.lineNumber 24 | } else { 25 | frame = throwable.getStackTrace().find { it.fileName == "Example.java" } 26 | if (frame) { 27 | errorLine = frame.lineNumber 28 | } 29 | } 30 | } 31 | errorLine = errorLine - offset 32 | StackTraceElement[] stack = throwable.getStackTrace() 33 | List newStack = new ArrayList(stack.length + 1) 34 | newStack.add(new StackTraceElement(sourceClassName, "javadoc", sourceFileName, lineNumber + errorLine)) 35 | newStack.addAll(stack) 36 | throwable.setStackTrace(newStack as StackTraceElement[]) 37 | throwable 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/internal/snippets/executer/SnippetExecuter.java: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.executer; 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.fixture.SnippetFixture; 4 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.TestCodeSnippet; 5 | 6 | public interface SnippetExecuter { 7 | 8 | SnippetFixture getFixture(); 9 | 10 | void execute(TestCodeSnippet snippet) throws Exception; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/internal/snippets/fixture/GroovyScriptFixture.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.fixture 2 | 3 | class GroovyScriptFixture extends SnippetFixture { 4 | 5 | @Override 6 | String post() { 7 | "\n;0;" 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/internal/snippets/fixture/SnippetFixture.java: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.fixture; 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.Block; 4 | 5 | public class SnippetFixture { 6 | 7 | public void around(Block action) throws Exception { 8 | action.execute(); 9 | } 10 | 11 | public String transform(String text) { 12 | return text; 13 | } 14 | 15 | public String pre() { 16 | return ""; 17 | } 18 | 19 | public String post() { 20 | return ""; 21 | } 22 | 23 | public Integer getOffset() { 24 | return pre().split("\n").length; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/internal/snippets/junit/DelegatingTestRunner.java: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.junit; 2 | 3 | import org.junit.runner.Runner; 4 | import org.junit.runners.Suite; 5 | import org.junit.runners.model.InitializationError; 6 | import org.junit.runners.model.RunnerScheduler; 7 | 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.util.List; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | public class DelegatingTestRunner extends Suite { 15 | 16 | public DelegatingTestRunner(Class clazz) throws InitializationError { 17 | super(clazz, extractRunners(clazz)); 18 | 19 | setScheduler(new RunnerScheduler() { 20 | 21 | private final ExecutorService service = Executors.newFixedThreadPool( 22 | System.getenv("CI") != null ? 1 : Runtime.getRuntime().availableProcessors() 23 | ); 24 | 25 | public void schedule(Runnable childStatement) { 26 | service.submit(childStatement); 27 | } 28 | 29 | public void finished() { 30 | try { 31 | service.shutdown(); 32 | service.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); 33 | } catch (InterruptedException e) { 34 | e.printStackTrace(System.err); 35 | } 36 | } 37 | }); 38 | } 39 | 40 | @SuppressWarnings("unchecked") 41 | private static List extractRunners(Class clazz) throws InitializationError { 42 | if (!RunnerProvider.class.isAssignableFrom(clazz)) { 43 | throw new InitializationError(clazz.getName() + " does not implement " + RunnerProvider.class.getName()); 44 | } 45 | 46 | Class asType = (Class) clazz; 47 | RunnerProvider instance; 48 | try { 49 | instance = asType.getConstructor().newInstance(); 50 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 51 | throw new InitializationError(e); 52 | } 53 | 54 | return instance.getRunners(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/internal/snippets/junit/RunnerProvider.java: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.junit; 2 | 3 | import org.junit.runner.Runner; 4 | 5 | import java.util.List; 6 | 7 | public interface RunnerProvider { 8 | 9 | List getRunners(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/docs/internal/snippets/junit/SnippetRunner.java: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.junit; 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.docs.internal.snippets.TestCodeSnippet; 4 | import org.junit.runner.Description; 5 | import org.junit.runner.Runner; 6 | import org.junit.runner.notification.Failure; 7 | import org.junit.runner.notification.RunNotifier; 8 | 9 | public class SnippetRunner extends Runner { 10 | 11 | private final Description description; 12 | private final TestCodeSnippet snippet; 13 | 14 | public SnippetRunner(Class testClass, TestCodeSnippet snippet) { 15 | this.description = Description.createTestDescription(testClass, snippet.getTestName()); 16 | this.snippet = snippet; 17 | } 18 | 19 | @Override 20 | public Description getDescription() { 21 | return description; 22 | } 23 | 24 | @Override 25 | public void run(RunNotifier notifier) { 26 | Description description = getDescription(); 27 | String filter = System.getProperty("filter"); 28 | if (filter != null && !filter.equals(description.getMethodName())) { 29 | notifier.fireTestIgnored(description); 30 | return; 31 | } 32 | 33 | try { 34 | notifier.fireTestStarted(description); 35 | snippet.getExecuter().execute(snippet); 36 | } catch (Throwable t) { 37 | Throwable transform; 38 | try { 39 | transform = snippet.getExceptionTransformer().transform(t, snippet.getExecuter().getFixture().getOffset()); 40 | } catch (Exception e) { 41 | throw new RuntimeException(e); 42 | } 43 | notifier.fireTestFailure(new Failure(description, transform)); 44 | } finally { 45 | notifier.fireTestFinished(description); 46 | } 47 | } 48 | 49 | @Override 50 | public int testCount() { 51 | return 1; 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocatorParameterTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.relocation 21 | 22 | import junit.framework.TestCase 23 | 24 | /** 25 | * Modified from org.apache.maven.plugins.shade.relocation.SimpleRelocatorParameterTest.java 26 | * 27 | * Modifications 28 | * @author John Engelman 29 | */ 30 | class SimpleRelocatorParameterTest extends TestCase { 31 | 32 | 33 | protected void setUp() { 34 | super.setUp() 35 | } 36 | 37 | void testThatNullPatternInConstructorShouldNotThrowNullPointerException() { 38 | constructThenFailOnNullPointerException(null, "") 39 | } 40 | 41 | void testThatNullShadedPatternInConstructorShouldNotThrowNullPointerException() { 42 | constructThenFailOnNullPointerException("", null) 43 | } 44 | 45 | private static void constructThenFailOnNullPointerException(String pattern, String shadedPattern) { 46 | try { 47 | new SimpleRelocator(pattern, shadedPattern, Collections. emptyList(), Collections. emptyList()) 48 | } 49 | catch (NullPointerException ignored) { 50 | fail("Constructor should not throw null pointer exceptions") 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheLicenseResourceTransformerTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.transformers 21 | 22 | import org.junit.Before 23 | import org.junit.Test 24 | 25 | import static org.junit.Assert.* 26 | 27 | /** 28 | * Test for {@link ApacheLicenseResourceTransformer}. 29 | * 30 | * @author Benjamin Bentmann 31 | * @version $Id: ApacheLicenseResourceTransformerTest.java 673906 2008-07-04 05:03:20Z brett $ 32 | * 33 | * Modified from org.apache.maven.plugins.shade.resources.ApacheLicenseResourceTransformerTest.java 34 | */ 35 | class ApacheLicenseResourceTransformerTest extends TransformerTestSupport { 36 | 37 | private ApacheLicenseResourceTransformer transformer 38 | 39 | static { 40 | /* 41 | * NOTE: The Turkish locale has an usual case transformation for the letters "I" and "i", making it a prime 42 | * choice to test for improper case-less string comparisions. 43 | */ 44 | Locale.setDefault(new Locale("tr")) 45 | } 46 | 47 | @Before 48 | void setUp() { 49 | this.transformer = new ApacheLicenseResourceTransformer() 50 | } 51 | 52 | @Test 53 | void testCanTransformResource() { 54 | assertTrue(this.transformer.canTransformResource(getFileElement("META-INF/LICENSE"))) 55 | assertTrue(this.transformer.canTransformResource(getFileElement("META-INF/LICENSE.TXT"))) 56 | assertTrue(this.transformer.canTransformResource(getFileElement("META-INF/License.txt"))) 57 | assertFalse(this.transformer.canTransformResource(getFileElement("META-INF/MANIFEST.MF"))) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformerParameterTests.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.transformers 21 | 22 | import com.github.jengelman.gradle.plugins.shadow.ShadowStats 23 | import junit.framework.TestCase 24 | import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator 25 | 26 | /** 27 | * Tests {@link ApacheLicenseResourceTransformer} parameters. 28 | * 29 | * Modified from org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformerParameterTests.java 30 | */ 31 | class ApacheNoticeResourceTransformerParameterTests extends TestCase { 32 | 33 | private static final String NOTICE_RESOURCE = "META-INF/NOTICE" 34 | private ApacheNoticeResourceTransformer subject 35 | private ShadowStats stats 36 | 37 | protected void setUp() { 38 | super.setUp() 39 | subject = new ApacheNoticeResourceTransformer() 40 | stats = new ShadowStats() 41 | } 42 | 43 | void testNoParametersShouldNotThrowNullPointerWhenNoInput() { 44 | processAndFailOnNullPointer("") 45 | } 46 | 47 | void testNoParametersShouldNotThrowNullPointerWhenNoLinesOfInput() { 48 | processAndFailOnNullPointer("Some notice text") 49 | } 50 | 51 | void testNoParametersShouldNotThrowNullPointerWhenOneLineOfInput() { 52 | processAndFailOnNullPointer("Some notice text\n") 53 | } 54 | 55 | void testNoParametersShouldNotThrowNullPointerWhenTwoLinesOfInput() { 56 | processAndFailOnNullPointer("Some notice text\nSome notice text\n") 57 | } 58 | 59 | void testNoParametersShouldNotThrowNullPointerWhenLineStartsWithSlashSlash() { 60 | processAndFailOnNullPointer("Some notice text\n//Some notice text\n") 61 | } 62 | 63 | void testNoParametersShouldNotThrowNullPointerWhenLineIsSlashSlash() { 64 | processAndFailOnNullPointer("//\n") 65 | } 66 | 67 | void testNoParametersShouldNotThrowNullPointerWhenLineIsEmpty() { 68 | processAndFailOnNullPointer("\n") 69 | } 70 | 71 | private void processAndFailOnNullPointer(final String noticeText) { 72 | try { 73 | final ByteArrayInputStream noticeInputStream = new ByteArrayInputStream(noticeText.getBytes()) 74 | final List emptyList = Collections.emptyList() 75 | subject.transform(TransformerContext.builder().path(NOTICE_RESOURCE).is(noticeInputStream).relocators(emptyList).stats(stats).build()) 76 | } 77 | catch (NullPointerException ignored) { 78 | fail("Null pointer should not be thrown when no parameters are set.") 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformerTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.transformers 21 | 22 | import org.junit.Before 23 | import org.junit.Test 24 | 25 | import static org.junit.Assert.* 26 | 27 | /** 28 | * Test for {@link ApacheNoticeResourceTransformer}. 29 | * 30 | * @author Benjamin Bentmann 31 | * @version $Id: ApacheNoticeResourceTransformerTest.java 673906 2008-07-04 05:03:20Z brett $ 32 | * 33 | * Modified from org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformerTest.java 34 | */ 35 | class ApacheNoticeResourceTransformerTest extends TransformerTestSupport { 36 | 37 | private ApacheNoticeResourceTransformer transformer 38 | 39 | static { 40 | /* 41 | * NOTE: The Turkish locale has an usual case transformation for the letters "I" and "i", making it a prime 42 | * choice to test for improper case-less string comparisions. 43 | */ 44 | Locale.setDefault(new Locale("tr")) 45 | } 46 | 47 | @Before 48 | void setUp() { 49 | this.transformer = new ApacheNoticeResourceTransformer() 50 | } 51 | 52 | @Test 53 | void testCanTransformResource() { 54 | assertTrue(this.transformer.canTransformResource(getFileElement("META-INF/NOTICE"))) 55 | assertTrue(this.transformer.canTransformResource(getFileElement("META-INF/NOTICE.TXT"))) 56 | assertTrue(this.transformer.canTransformResource(getFileElement("META-INF/Notice.txt"))) 57 | assertFalse(this.transformer.canTransformResource(getFileElement("META-INF/MANIFEST.MF"))) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformerTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.transformers 21 | 22 | import org.junit.Before 23 | import org.junit.Test 24 | 25 | import static org.junit.Assert.* 26 | 27 | /** 28 | * Test for {@link AppendingTransformer}. 29 | * 30 | * @author Benjamin Bentmann 31 | * @version $Id: AppendingTransformerTest.java 673906 2008-07-04 05:03:20Z brett $ 32 | */ 33 | class AppendingTransformerTest extends TransformerTestSupport { 34 | 35 | private AppendingTransformer transformer 36 | 37 | static 38 | { 39 | /* 40 | * NOTE: The Turkish locale has an usual case transformation for the letters "I" and "i", making it a prime 41 | * choice to test for improper case-less string comparisions. 42 | */ 43 | Locale.setDefault(new Locale("tr")) 44 | } 45 | 46 | @Before 47 | void setUp() { 48 | this.transformer = new AppendingTransformer() 49 | } 50 | 51 | @Test 52 | void testCanTransformResource() { 53 | this.transformer.resource = "abcdefghijklmnopqrstuvwxyz" 54 | 55 | assertTrue(this.transformer.canTransformResource(getFileElement("abcdefghijklmnopqrstuvwxyz"))) 56 | assertTrue(this.transformer.canTransformResource(getFileElement("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) 57 | assertFalse(this.transformer.canTransformResource(getFileElement("META-INF/MANIFEST.MF"))) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ComponentsXmlResourceTransformerTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.transformers 21 | 22 | import com.github.jengelman.gradle.plugins.shadow.ShadowStats 23 | import junit.framework.TestCase 24 | 25 | import org.custommonkey.xmlunit.Diff 26 | import org.custommonkey.xmlunit.XMLAssert 27 | import org.custommonkey.xmlunit.XMLUnit 28 | import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator 29 | import org.codehaus.plexus.util.IOUtil 30 | 31 | /** 32 | * Test for {@link ComponentsXmlResourceTransformer}. 33 | * 34 | * @author Brett Porter 35 | * @version $Id: ComponentsXmlResourceTransformerTest.java 1379994 2012-09-02 15:22:49Z hboutemy $ 36 | * 37 | * Modified from org.apache.maven.plugins.shade.resource.ComponentsXmlResourceTransformerTest.java 38 | */ 39 | class ComponentsXmlResourceTransformerTest extends TestCase { 40 | private ComponentsXmlResourceTransformer transformer 41 | private ShadowStats stats 42 | 43 | void setUp() { 44 | this.transformer = new ComponentsXmlResourceTransformer() 45 | stats = new ShadowStats() 46 | } 47 | 48 | void testConfigurationMerging() { 49 | 50 | XMLUnit.setNormalizeWhitespace(true) 51 | 52 | transformer.transform( 53 | TransformerContext.builder() 54 | .path("components-1.xml") 55 | .is(getClass().getResourceAsStream("/components-1.xml")) 56 | .relocators(Collections. emptyList()) 57 | .stats(stats) 58 | .build()) 59 | transformer.transform( 60 | TransformerContext.builder() 61 | .path("components-1.xml") 62 | .is(getClass().getResourceAsStream("/components-2.xml")) 63 | .relocators(Collections. emptyList()) 64 | .stats(stats) 65 | .build()) 66 | Diff diff = XMLUnit.compareXML( 67 | IOUtil.toString(getClass().getResourceAsStream("/components-expected.xml"), "UTF-8"), 68 | IOUtil.toString(transformer.getTransformedResource(), "UTF-8")) 69 | //assertEquals( IOUtil.toString( getClass().getResourceAsStream( "/components-expected.xml" ), "UTF-8" ), 70 | // IOUtil.toString( transformer.getTransformedResource(), "UTF-8" ).replaceAll("\r\n", "\n") ) 71 | XMLAssert.assertXMLIdentical(diff, true) 72 | } 73 | } -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformerSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.transformers 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.ShadowStats 4 | import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator 5 | import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator 6 | import org.apache.logging.log4j.core.config.plugins.processor.PluginCache 7 | import org.apache.tools.zip.ZipOutputStream 8 | import spock.lang.Specification 9 | 10 | 11 | import static java.util.Collections.singletonList 12 | import static org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor.PLUGIN_CACHE_FILE 13 | 14 | /** 15 | * @author Paul Nelson Baker 16 | * @since 2018-08 17 | * @see GitHub 18 | * @see LinkedIn 19 | */ 20 | class Log4j2PluginsCacheFileTransformerSpec extends Specification{ 21 | 22 | Log4j2PluginsCacheFileTransformer transformer 23 | 24 | void setup() { 25 | transformer = new Log4j2PluginsCacheFileTransformer() 26 | } 27 | 28 | void "should not transformer"() { 29 | when: 30 | transformer.transform(new TransformerContext(PLUGIN_CACHE_FILE, getResourceStream(PLUGIN_CACHE_FILE), null)) 31 | 32 | then: 33 | !transformer.hasTransformedResource() 34 | } 35 | 36 | void "should transform"() { 37 | given: 38 | List relocators = new ArrayList<>() 39 | relocators.add(new SimpleRelocator(null, null, null, null)) 40 | 41 | when: 42 | transformer.transform(new TransformerContext(PLUGIN_CACHE_FILE, getResourceStream(PLUGIN_CACHE_FILE), relocators)) 43 | 44 | then: 45 | transformer.hasTransformedResource() 46 | } 47 | 48 | void "relocate classes inside DAT file"() { 49 | given: 50 | String pattern = "org.apache.logging" 51 | String destination = "new.location.org.apache.logging" 52 | 53 | List relocators = singletonList((Relocator) new SimpleRelocator(pattern, destination, null, null)) 54 | 55 | when: 56 | transformer.transform(new TransformerContext(PLUGIN_CACHE_FILE, getResourceStream(PLUGIN_CACHE_FILE), relocators, new ShadowStats())) 57 | 58 | then: 59 | transformer.hasTransformedResource() 60 | 61 | when: 62 | // Write out to a fake jar file 63 | def testableZipFile = File.createTempFile("testable-zip-file-", ".jar") 64 | def fileOutputStream = new FileOutputStream(testableZipFile) 65 | def bufferedOutputStream = new BufferedOutputStream(fileOutputStream) 66 | def zipOutputStream = new ZipOutputStream(bufferedOutputStream) 67 | 68 | transformer.modifyOutputStream(zipOutputStream, true) 69 | 70 | zipOutputStream.close() 71 | bufferedOutputStream.close() 72 | fileOutputStream.close() 73 | 74 | then: 75 | // Pull the data back out and make sure it was transformed 76 | PluginCache cache = new PluginCache() 77 | def urlString = "jar:" + testableZipFile.toURI().toURL() + "!/" + PLUGIN_CACHE_FILE 78 | cache.loadCacheFiles(Collections.enumeration([new URL(urlString)])) 79 | 80 | cache.getCategory("lookup")["date"].className == "new.location.org.apache.logging.log4j.core.lookup.DateLookup" 81 | 82 | } 83 | 84 | InputStream getResourceStream(String resource) { 85 | return this.class.getClassLoader().getResourceAsStream(resource) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.transformers 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.ShadowStats 4 | import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator 5 | import org.apache.tools.zip.ZipOutputStream 6 | import org.junit.Before 7 | import org.junit.Test 8 | 9 | import java.util.zip.ZipFile 10 | 11 | import static java.util.Arrays.asList 12 | import static org.junit.Assert.* 13 | 14 | /** 15 | * Test for {@link PropertiesFileTransformer}. 16 | */ 17 | final class PropertiesFileTransformerTest extends TransformerTestSupport { 18 | static final String MANIFEST_NAME = "META-INF/MANIFEST.MF" 19 | 20 | private PropertiesFileTransformer transformer 21 | 22 | @Before 23 | void setUp() { 24 | transformer = new PropertiesFileTransformer() 25 | } 26 | 27 | @Test 28 | void testHasTransformedResource() { 29 | transformer.transform(new TransformerContext(MANIFEST_NAME)) 30 | 31 | assertTrue(transformer.hasTransformedResource()) 32 | } 33 | 34 | @Test 35 | void testHasNotTransformedResource() { 36 | assertFalse(transformer.hasTransformedResource()) 37 | } 38 | 39 | @Test 40 | void testTransformation() { 41 | transformer.transform(new TransformerContext(MANIFEST_NAME, getResourceStream(MANIFEST_NAME), Collections.emptyList(), new ShadowStats())) 42 | 43 | def testableZipFile = doTransformAndGetTransformedFile(transformer, false) 44 | def targetLines = readFrom(testableZipFile, MANIFEST_NAME) 45 | 46 | assertFalse(targetLines.isEmpty()) 47 | 48 | assertTrue(targetLines.contains("Manifest-Version=1.0")) 49 | } 50 | 51 | @Test 52 | void testTransformationPropertiesAreReproducible() { 53 | transformer.transform(new TransformerContext(MANIFEST_NAME, getResourceStream(MANIFEST_NAME), Collections.emptyList(), new ShadowStats())) 54 | 55 | def firstRunTransformedFile = doTransformAndGetTransformedFile(transformer, true) 56 | def firstRunTargetLines = readFrom(firstRunTransformedFile, MANIFEST_NAME) 57 | 58 | Thread.sleep(1000) // wait for 1sec to ensure timestamps in properties would change 59 | 60 | def secondRunTransformedFile = doTransformAndGetTransformedFile(transformer, true) 61 | def secondRunTargetLines = readFrom(secondRunTransformedFile, MANIFEST_NAME) 62 | 63 | assertEquals(firstRunTargetLines, secondRunTargetLines) 64 | } 65 | 66 | static File doTransformAndGetTransformedFile(final PropertiesFileTransformer transformer, final boolean preserveFileTimestamps) { 67 | def testableZipFile = File.createTempFile("testable-zip-file-", ".jar") 68 | def fileOutputStream = new FileOutputStream(testableZipFile) 69 | def bufferedOutputStream = new BufferedOutputStream(fileOutputStream) 70 | def zipOutputStream = new ZipOutputStream(bufferedOutputStream) 71 | 72 | try { 73 | transformer.modifyOutputStream(zipOutputStream, preserveFileTimestamps) 74 | } finally { 75 | zipOutputStream.close() 76 | } 77 | 78 | return testableZipFile 79 | } 80 | 81 | static List readFrom(File jarFile, String resourceName) { 82 | def zip = new ZipFile(jarFile) 83 | try { 84 | def entry = zip.getEntry(resourceName) 85 | if (!entry) { 86 | return Collections.emptyList() 87 | } 88 | return zip.getInputStream(entry).readLines() 89 | } finally { 90 | zip.close() 91 | } 92 | } 93 | 94 | InputStream getResourceStream(String resource) { 95 | this.class.classLoader.getResourceAsStream(resource) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.transformers 2 | 3 | import spock.lang.Unroll 4 | 5 | @Unroll 6 | class ServiceFileTransformerSpec extends TransformerSpecSupport { 7 | 8 | def "#status path #path #transform transformed"() { 9 | given: 10 | def transformer = new ServiceFileTransformer() 11 | if (exclude) { 12 | transformer.exclude(path) 13 | } 14 | 15 | when: 16 | def actual = transformer.canTransformResource(getFileElement(path)) 17 | 18 | then: 19 | actual == expected 20 | 21 | where: 22 | path | exclude | expected 23 | 'META-INF/services/java.sql.Driver' | false | true 24 | 'META-INF/services/io.dropwizard.logging.AppenderFactory' | false | true 25 | 'META-INF/services/org.apache.maven.Shade' | true | false 26 | 'META-INF/services/foo/bar/moo.goo.Zoo' | false | true 27 | 'foo/bar.properties' | false | false 28 | 'foo.props' | false | false 29 | 30 | transform = expected ? 'can be' : 'can not be' 31 | status = exclude ? 'excluded' : 'non-excluded' 32 | } 33 | 34 | def "transforms service file"() { 35 | given: 36 | def element = getFileElement(path) 37 | def transformer = new ServiceFileTransformer() 38 | 39 | when: 40 | if (transformer.canTransformResource(element)) { 41 | transformer.transform(context(path, input1)) 42 | transformer.transform(context(path, input2)) 43 | } 44 | 45 | then: 46 | transformer.hasTransformedResource() 47 | output == transformer.serviceEntries[path].toInputStream().text 48 | 49 | where: 50 | path | input1 | input2 || output 51 | 'META-INF/services/com.acme.Foo' | 'foo' | 'bar' || 'foo\nbar' 52 | 'META-INF/services/com.acme.Bar' | 'foo\nbar' | 'zoo' || 'foo\nbar\nzoo' 53 | } 54 | 55 | def "excludes Groovy extension module descriptor files by default"() { 56 | given: 57 | def transformer = new ServiceFileTransformer() 58 | def element = getFileElement('META-INF/services/org.codehaus.groovy.runtime.ExtensionModule') 59 | 60 | expect: 61 | !transformer.canTransformResource(element) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/TransformerSpecSupport.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.transformers 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.ShadowStats 4 | import org.gradle.api.file.FileTreeElement 5 | import org.gradle.api.file.RelativePath 6 | import org.gradle.api.internal.file.DefaultFileTreeElement 7 | import spock.lang.Shared 8 | import spock.lang.Specification 9 | 10 | class TransformerSpecSupport extends Specification { 11 | 12 | @Shared 13 | ShadowStats stats 14 | 15 | def setup() { 16 | stats = new ShadowStats() 17 | } 18 | 19 | protected static FileTreeElement getFileElement(String path) { 20 | return new DefaultFileTreeElement(null, RelativePath.parse(true, path), null, null) 21 | } 22 | 23 | protected static InputStream toInputStream(String str) { 24 | return new ByteArrayInputStream(str.bytes) 25 | } 26 | 27 | protected static InputStream toInputStream(Properties props, String charset) { 28 | ByteArrayOutputStream baos = new ByteArrayOutputStream() 29 | baos.withWriter(charset) { w -> 30 | props.store(w, '') 31 | } 32 | new ByteArrayInputStream(baos.toByteArray()) 33 | } 34 | 35 | protected static Properties toProperties(Map map) { 36 | map.inject(new Properties()) { Properties props, entry -> 37 | props.put(entry.key, entry.value) 38 | props 39 | } 40 | } 41 | 42 | protected static Map toMap(Properties props) { 43 | props.inject([:]) { Map map, entry -> 44 | map.put(entry.key, entry.value) 45 | map 46 | } 47 | } 48 | 49 | protected TransformerContext context(String path, Map input, String charset = 'ISO_8859_1') { 50 | TransformerContext.builder().path(path).is(toInputStream(toProperties(input), charset)).relocators([]).stats(stats).build() 51 | } 52 | 53 | protected TransformerContext context(String path, String input) { 54 | TransformerContext.builder().path(path).is(toInputStream(input)).relocators([]).stats(stats).build() 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/TransformerTestSupport.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.transformers 2 | 3 | import org.gradle.api.file.FileTreeElement 4 | import org.gradle.api.file.RelativePath 5 | import org.gradle.api.internal.file.DefaultFileTreeElement 6 | 7 | class TransformerTestSupport { 8 | 9 | protected static FileTreeElement getFileElement(String path) { 10 | return new DefaultFileTreeElement(null, RelativePath.parse(true, path), null, null) 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/XmlAppendingTransformerTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License") you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package com.github.jengelman.gradle.plugins.shadow.transformers 21 | 22 | import org.junit.Before 23 | import org.junit.Test 24 | 25 | import static org.junit.Assert.* 26 | 27 | /** 28 | * Test for {@link XmlAppendingTransformer}. 29 | * 30 | * @author Benjamin Bentmann 31 | * @version $Id: XmlAppendingTransformerTest.java 673906 2008-07-04 05:03:20Z brett $ 32 | * 33 | * Modified from org.apache.maven.plugins.shade.resource.XmlAppendingTransformerTest.java 34 | */ 35 | class XmlAppendingTransformerTest extends TransformerTestSupport { 36 | 37 | XmlAppendingTransformer transformer 38 | 39 | static { 40 | /* 41 | * NOTE: The Turkish locale has an usual case transformation for the letters "I" and "i", making it a prime 42 | * choice to test for improper case-less string comparisons. 43 | */ 44 | Locale.setDefault(new Locale("tr")) 45 | } 46 | 47 | @Before 48 | void setUp() { 49 | transformer = new XmlAppendingTransformer() 50 | } 51 | 52 | @Test 53 | void testCanTransformResource() { 54 | transformer.resource = "abcdefghijklmnopqrstuvwxyz" 55 | 56 | assertTrue(this.transformer.canTransformResource(getFileElement("abcdefghijklmnopqrstuvwxyz"))) 57 | assertTrue(this.transformer.canTransformResource(getFileElement("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) 58 | assertFalse(this.transformer.canTransformResource(getFileElement("META-INF/MANIFEST.MF"))) 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/AppendableJar.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util 2 | 3 | class AppendableJar { 4 | 5 | Map contents = [:] 6 | File file 7 | 8 | AppendableJar(File file) { 9 | this.file = file 10 | } 11 | 12 | AppendableJar insertFile(String path, String content) { 13 | contents[path] = content 14 | return this 15 | } 16 | 17 | File write() { 18 | JarBuilder builder = new JarBuilder(file.newOutputStream()) 19 | contents.each { path, contents -> 20 | builder.withFile(path, contents) 21 | } 22 | builder.build() 23 | return file 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/AppendableMavenFileModule.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.util.repo.maven.MavenFileModule 4 | import groovy.transform.InheritConstructors 5 | import org.apache.commons.io.IOUtils 6 | 7 | @InheritConstructors 8 | class AppendableMavenFileModule extends MavenFileModule { 9 | 10 | Map> contents = [:].withDefault { [:] } 11 | Map files = [:] 12 | 13 | AppendableMavenFileModule use(File file) { 14 | return use('', file) 15 | } 16 | 17 | AppendableMavenFileModule use(String classifier, File file) { 18 | files[classifier] = file 19 | return this 20 | } 21 | 22 | AppendableMavenFileModule insertFile(String path, String content) { 23 | insertFile('', path, content) 24 | return this 25 | } 26 | 27 | AppendableMavenFileModule insertFile(String classifier, String path, String content) { 28 | contents[classifier][path] = content 29 | return this 30 | } 31 | 32 | @Override 33 | File publishArtifact(Map artifact) { 34 | def artifactFile = artifactFile(artifact) 35 | if (type == 'pom') { 36 | return artifactFile 37 | } 38 | String classifier = (String) artifact['classifier'] ?: '' 39 | if (files.containsKey(classifier)) { 40 | publishWithStream(artifactFile) { OutputStream os -> 41 | IOUtils.copy(files[classifier].newInputStream(), os) 42 | } 43 | } else { 44 | publishWithStream(artifactFile) { OutputStream os -> 45 | writeJar(os, contents[classifier]) 46 | } 47 | } 48 | return artifactFile 49 | } 50 | 51 | static void writeJar(OutputStream os, Map contents) { 52 | if (contents) { 53 | JarBuilder builder = new JarBuilder(os) 54 | contents.each { path, content -> 55 | builder.withFile(path, content) 56 | } 57 | builder.build() 58 | } 59 | } 60 | 61 | /** 62 | * Adds an additional artifact to this module. 63 | * @param options Can specify any of: type or classifier 64 | */ 65 | AppendableMavenFileModule artifact(Map options) { 66 | artifacts << options 67 | return this 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/AppendableMavenFileRepository.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.util.repo.maven.MavenFileRepository 4 | import groovy.transform.InheritConstructors 5 | 6 | @InheritConstructors 7 | class AppendableMavenFileRepository extends MavenFileRepository { 8 | 9 | @Override 10 | AppendableMavenFileModule module(String groupId, String artifactId, Object version = '1.0') { 11 | def artifactDir = rootDir.file("${groupId.replace('.', '/')}/$artifactId/$version") 12 | return new AppendableMavenFileModule(artifactDir, groupId, artifactId, version as String) 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/HashUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util; 2 | 3 | import org.gradle.api.UncheckedIOException; 4 | import org.gradle.internal.UncheckedException; 5 | import java.io.ByteArrayInputStream; 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.FileNotFoundException; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.security.MessageDigest; 12 | import java.security.NoSuchAlgorithmException; 13 | 14 | public class HashUtil { 15 | public static HashValue createHash(File file, String algorithm) { 16 | try { 17 | return createHash(new FileInputStream(file), algorithm); 18 | } catch (UncheckedIOException e) { 19 | // Catch any unchecked io exceptions and add the file path for troubleshooting 20 | throw new UncheckedIOException(String.format("Failed to create %s hash for file %s.", algorithm, file.getAbsolutePath()), e.getCause()); 21 | } catch (FileNotFoundException e) { 22 | throw new UncheckedIOException(e); 23 | } 24 | } 25 | 26 | private static HashValue createHash(InputStream instr, String algorithm) { 27 | MessageDigest messageDigest; 28 | try { 29 | messageDigest = createMessageDigest(algorithm); 30 | byte[] buffer = new byte[4096]; 31 | try { 32 | while (true) { 33 | int nread = instr.read(buffer); 34 | if (nread < 0) { 35 | break; 36 | } 37 | messageDigest.update(buffer, 0, nread); 38 | } 39 | } finally { 40 | instr.close(); 41 | } 42 | } catch (IOException e) { 43 | throw new UncheckedIOException(e); 44 | } 45 | return new HashValue(messageDigest.digest()); 46 | } 47 | 48 | private static MessageDigest createMessageDigest(String algorithm) { 49 | try { 50 | return MessageDigest.getInstance(algorithm); 51 | } catch (NoSuchAlgorithmException e) { 52 | throw UncheckedException.throwAsUncheckedException(e); 53 | } 54 | } 55 | 56 | public static HashValue sha1(byte[] bytes) { 57 | return createHash(new ByteArrayInputStream(bytes), "SHA1"); 58 | } 59 | 60 | public static HashValue sha1(InputStream inputStream) { 61 | return createHash(inputStream, "SHA1"); 62 | } 63 | 64 | public static HashValue md5(File file) { 65 | return createHash(file, "MD5"); 66 | } 67 | 68 | public static HashValue sha1(File file) { 69 | return createHash(file, "SHA1"); 70 | } 71 | 72 | public static HashValue sha256(byte[] bytes) { 73 | return createHash(new ByteArrayInputStream(bytes), "SHA-256"); 74 | } 75 | 76 | public static HashValue sha256(InputStream inputStream) { 77 | return createHash(inputStream, "SHA-256"); 78 | } 79 | 80 | public static HashValue sha256(File file) { 81 | return createHash(file, "SHA-256"); 82 | } 83 | 84 | public static HashValue sha512(InputStream inputStream) { 85 | return createHash(inputStream, "SHA-512"); 86 | } 87 | 88 | public static HashValue sha512(File file) { 89 | return createHash(file, "SHA-512"); 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/HashValue.java: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util; 2 | 3 | import java.math.BigInteger; 4 | 5 | public class HashValue { 6 | private final BigInteger digest; 7 | 8 | public HashValue(byte[] digest) { 9 | this.digest = new BigInteger(1, digest); 10 | } 11 | 12 | public HashValue(String hexString) { 13 | this.digest = new BigInteger(hexString, 16); 14 | } 15 | 16 | public static HashValue parse(String inputString) { 17 | if (inputString == null || inputString.isEmpty()) { 18 | return null; 19 | } 20 | return new HashValue(parseInput(inputString)); 21 | } 22 | 23 | private static String parseInput(String inputString) { 24 | if (inputString == null) { 25 | return null; 26 | } 27 | String cleaned = inputString.trim().toLowerCase(); 28 | int spaceIndex = cleaned.indexOf(' '); 29 | if (spaceIndex != -1) { 30 | String firstPart = cleaned.substring(0, spaceIndex); 31 | if (firstPart.startsWith("md") || firstPart.startsWith("sha")) { 32 | cleaned = cleaned.substring(cleaned.lastIndexOf(' ') + 1); 33 | } else if (firstPart.endsWith(":")) { 34 | cleaned = cleaned.substring(spaceIndex + 1).replace(" ", ""); 35 | } else { 36 | cleaned = cleaned.substring(0, spaceIndex); 37 | } 38 | } 39 | return cleaned; 40 | } 41 | 42 | public String asCompactString() { 43 | return digest.toString(36); 44 | } 45 | 46 | public String asHexString() { 47 | return digest.toString(16); 48 | } 49 | 50 | public byte[] asByteArray() { 51 | return digest.toByteArray(); 52 | } 53 | 54 | public BigInteger asBigInteger() { 55 | return digest; 56 | } 57 | 58 | @Override 59 | public boolean equals(Object other) { 60 | if (this == other) { 61 | return true; 62 | } 63 | if (!(other instanceof HashValue)) { 64 | return false; 65 | } 66 | 67 | HashValue otherHashValue = (HashValue) other; 68 | return digest.equals(otherHashValue.digest); 69 | } 70 | 71 | @Override 72 | public int hashCode() { 73 | return digest.hashCode(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util 2 | 3 | import org.codehaus.plexus.util.IOUtil 4 | 5 | import java.util.jar.JarEntry 6 | import java.util.jar.JarOutputStream 7 | 8 | class JarBuilder { 9 | 10 | List entries = [] 11 | JarOutputStream jos 12 | 13 | JarBuilder(OutputStream os) { 14 | jos = new JarOutputStream(os) 15 | } 16 | 17 | private void addDirectory(String name) { 18 | if (!entries.contains(name)) { 19 | if (name.lastIndexOf('/') > 0) { 20 | String parent = name.substring(0, name.lastIndexOf('/')) 21 | if (!entries.contains(parent)) { 22 | addDirectory(parent) 23 | } 24 | } 25 | 26 | // directory entries must end in "/" 27 | JarEntry entry = new JarEntry(name + "/") 28 | jos.putNextEntry(entry) 29 | 30 | entries.add(name) 31 | } 32 | } 33 | 34 | JarBuilder withFile(String path, String data) { 35 | def idx = path.lastIndexOf('/') 36 | if (idx != -1) { 37 | addDirectory(path.substring(0, idx)) 38 | } 39 | if (!entries.contains(path)) { 40 | JarEntry entry = new JarEntry(path) 41 | jos.putNextEntry(entry) 42 | entries << path 43 | IOUtil.copy(data.bytes, jos) 44 | } 45 | return this 46 | } 47 | 48 | void build() { 49 | jos.close() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/file/ExecOutput.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util.file 2 | 3 | class ExecOutput { 4 | ExecOutput(String rawOutput, String error) { 5 | this.rawOutput = rawOutput 6 | this.out = rawOutput.replaceAll("\r\n|\r", "\n") 7 | this.error = error 8 | } 9 | 10 | String rawOutput 11 | String out 12 | String error 13 | } 14 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/file/Results.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util.file 2 | 3 | import org.gradle.tooling.GradleConnectionException 4 | import org.gradle.tooling.ResultHandler 5 | 6 | class Results implements ResultHandler { 7 | 8 | private final Object lock = new Object() 9 | 10 | private boolean success = false 11 | private GradleConnectionException exception 12 | 13 | void waitForCompletion() { 14 | synchronized(lock) { 15 | while(!successful && !failed) { 16 | lock.wait() 17 | } 18 | } 19 | } 20 | 21 | boolean getSuccessful() { 22 | return success && !exception 23 | } 24 | 25 | boolean getFailed() { 26 | return exception as boolean 27 | } 28 | 29 | GradleConnectionException getException() { 30 | return exception 31 | } 32 | 33 | void markComplete() { 34 | synchronized(lock) { 35 | success = true 36 | exception = null 37 | lock.notifyAll() 38 | } 39 | } 40 | 41 | void markFailed(GradleConnectionException e) { 42 | synchronized(lock) { 43 | success = false 44 | exception = e 45 | lock.notifyAll() 46 | } 47 | } 48 | 49 | @Override 50 | void onComplete(Void aVoid) { 51 | markComplete() 52 | } 53 | 54 | @Override 55 | void onFailure(GradleConnectionException e) { 56 | markFailed(e) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/file/TestDirectoryProvider.java: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util.file; 2 | 3 | /** 4 | * Implementations provide a working space to be used in tests. 5 | *

6 | * The client is not responsible for removing any files. 7 | */ 8 | public interface TestDirectoryProvider { 9 | 10 | /** 11 | * The directory to use, guaranteed to exist. 12 | * 13 | * @return The directory to use, guaranteed to exist. 14 | */ 15 | TestFile getTestDirectory(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/file/TestWorkspaceBuilder.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util.file 2 | 3 | /** 4 | * Used in TestFile.create(). 5 | * 6 | * Should be inner class of TestFile, but can't because Groovy has issues with inner classes as delegates. 7 | */ 8 | class TestWorkspaceBuilder { 9 | TestFile baseDir 10 | 11 | TestWorkspaceBuilder(TestFile baseDir) { 12 | this.baseDir = baseDir 13 | } 14 | 15 | def apply(Closure cl) { 16 | cl.delegate = this 17 | cl.resolveStrategy = Closure.DELEGATE_FIRST 18 | cl() 19 | } 20 | 21 | def file(String name) { 22 | TestFile file = baseDir.file(name) 23 | file.write('some content') 24 | file 25 | } 26 | 27 | def setMode(int mode) { 28 | baseDir.mode = mode 29 | } 30 | 31 | def methodMissing(String name, Object args) { 32 | if (args.length == 1 && args[0] instanceof Closure) { 33 | baseDir.file(name).create(args[0]) 34 | } 35 | else { 36 | throw new MissingMethodException(name, getClass(), args) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/AbstractModule.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util.repo 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.util.HashUtil 4 | import com.github.jengelman.gradle.plugins.shadow.util.file.TestFile 5 | 6 | abstract class AbstractModule { 7 | /** 8 | * @param cl A closure that is passed a writer to use to generate the content. 9 | */ 10 | protected void publish(TestFile file, Closure cl) { 11 | def hashBefore = file.exists() ? getHash(file, "sha1") : null 12 | def tmpFile = file.parentFile.file("${file.name}.tmp") 13 | 14 | tmpFile.withWriter("utf-8") { 15 | cl.call(it) 16 | } 17 | 18 | def hashAfter = getHash(tmpFile, "sha1") 19 | if (hashAfter == hashBefore) { 20 | // Already published 21 | return 22 | } 23 | 24 | assert !file.exists() || file.delete() 25 | assert tmpFile.renameTo(file) 26 | onPublish(file) 27 | } 28 | 29 | protected void publishWithStream(TestFile file, Closure cl) { 30 | def hashBefore = file.exists() ? getHash(file, "sha1") : null 31 | def tmpFile = file.parentFile.file("${file.name}.tmp") 32 | 33 | tmpFile.withOutputStream { 34 | cl.call(it) 35 | } 36 | 37 | def hashAfter = getHash(tmpFile, "sha1") 38 | if (hashAfter == hashBefore) { 39 | // Already published 40 | return 41 | } 42 | 43 | assert !file.exists() || file.delete() 44 | assert tmpFile.renameTo(file) 45 | onPublish(file) 46 | } 47 | 48 | protected abstract onPublish(TestFile file) 49 | 50 | static TestFile getSha1File(TestFile file) { 51 | getHashFile(file, "sha1") 52 | } 53 | 54 | static TestFile sha1File(TestFile file) { 55 | hashFile(file, "sha1", 40) 56 | } 57 | 58 | static TestFile getMd5File(TestFile file) { 59 | getHashFile(file, "md5") 60 | } 61 | 62 | static TestFile md5File(TestFile file) { 63 | hashFile(file, "md5", 32) 64 | } 65 | 66 | private static TestFile hashFile(TestFile file, String algorithm, int len) { 67 | def hashFile = getHashFile(file, algorithm) 68 | def hash = getHash(file, algorithm) 69 | hashFile.text = String.format("%0${len}x", hash) 70 | return hashFile 71 | } 72 | 73 | private static TestFile getHashFile(TestFile file, String algorithm) { 74 | file.parentFile.file("${file.name}.${algorithm}") 75 | } 76 | 77 | private static BigInteger getHash(TestFile file, String algorithm) { 78 | HashUtil.createHash(file, algorithm.toUpperCase()).asBigInteger() 79 | } 80 | } -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/DefaultMavenMetaData.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util.repo.maven 2 | 3 | import groovy.xml.XmlParser 4 | 5 | /** 6 | * http://maven.apache.org/ref/3.0.1/maven-repository-metadata/repository-metadata.html 7 | */ 8 | class DefaultMavenMetaData implements MavenMetaData{ 9 | 10 | String text 11 | 12 | String groupId 13 | String artifactId 14 | String version 15 | 16 | List versions = [] 17 | String lastUpdated 18 | 19 | DefaultMavenMetaData(File file) { 20 | text = file.text 21 | def xml = new XmlParser().parseText(text) 22 | 23 | groupId = xml.groupId[0]?.text() 24 | artifactId = xml.artifactId[0]?.text() 25 | version = xml.version[0]?.text() 26 | 27 | def versioning = xml.versioning[0] 28 | 29 | lastUpdated = versioning.lastUpdated[0]?.text() 30 | 31 | versioning.versions[0].version.each { 32 | versions << it.text() 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenDependency.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util.repo.maven 2 | 3 | class MavenDependency { 4 | String groupId 5 | String artifactId 6 | String version 7 | String classifier 8 | String type 9 | 10 | MavenDependency hasType(def type) { 11 | assert this.type == type 12 | return this 13 | } 14 | 15 | @Override 16 | String toString() { 17 | return String.format("MavenDependency %s:%s:%s:%s@%s", groupId, artifactId, version, classifier, type) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenFileModule.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util.repo.maven 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.util.file.TestFile 4 | 5 | class MavenFileModule extends AbstractMavenModule { 6 | private boolean uniqueSnapshots = true 7 | 8 | MavenFileModule(TestFile moduleDir, String groupId, String artifactId, String version) { 9 | super(moduleDir, groupId, artifactId, version) 10 | } 11 | 12 | boolean getUniqueSnapshots() { 13 | return uniqueSnapshots 14 | } 15 | 16 | MavenModule withNonUniqueSnapshots() { 17 | uniqueSnapshots = false 18 | return this 19 | } 20 | 21 | @Override 22 | String getMetaDataFileContent() { 23 | """ 24 | 25 | 26 | $groupId 27 | $artifactId 28 | $version 29 | 30 | 31 | ${timestampFormat.format(publishTimestamp)} 32 | $publishCount 33 | 34 | ${updateFormat.format(publishTimestamp)} 35 | 36 | 37 | """ 38 | } 39 | 40 | @Override 41 | protected onPublish(TestFile file) { 42 | sha1File(file) 43 | md5File(file) 44 | } 45 | 46 | @Override 47 | protected boolean publishesMetaDataFile() { 48 | uniqueSnapshots && version.endsWith("-SNAPSHOT") 49 | } 50 | 51 | @Override 52 | protected boolean publishesHashFiles() { 53 | true 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenFileRepository.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util.repo.maven 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.util.file.TestFile 4 | 5 | /** 6 | * A fixture for dealing with file Maven repositories. 7 | */ 8 | class MavenFileRepository implements MavenRepository { 9 | final TestFile rootDir 10 | 11 | MavenFileRepository(TestFile rootDir) { 12 | this.rootDir = rootDir 13 | } 14 | 15 | URI getUri() { 16 | return rootDir.toURI() 17 | } 18 | 19 | MavenFileModule module(String groupId, String artifactId, Object version = '1.0') { 20 | def artifactDir = rootDir.file("${groupId.replace('.', '/')}/$artifactId/$version") 21 | return new MavenFileModule(artifactDir, groupId, artifactId, version as String) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenMetaData.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util.repo.maven 2 | 3 | interface MavenMetaData { 4 | List getVersions(); 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenModule.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util.repo.maven 2 | 3 | import com.github.jengelman.gradle.plugins.shadow.util.file.TestFile 4 | 5 | interface MavenModule { 6 | /** 7 | * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module. Publishes only those artifacts whose content has 8 | * changed since the last call to {@code #publish()}. 9 | */ 10 | MavenModule publish() 11 | 12 | /** 13 | * Publishes the pom.xml only 14 | */ 15 | MavenModule publishPom() 16 | 17 | /** 18 | * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module, with different content (and size) to any 19 | * previous publication. 20 | */ 21 | MavenModule publishWithChangedContent() 22 | 23 | MavenModule withNonUniqueSnapshots() 24 | 25 | MavenModule parent(String group, String artifactId, String version) 26 | 27 | MavenModule dependsOn(String group, String artifactId, String version) 28 | 29 | MavenModule hasPackaging(String packaging) 30 | 31 | /** 32 | * Sets the type of the main artifact for this module. 33 | */ 34 | MavenModule hasType(String type) 35 | 36 | TestFile getPomFile() 37 | 38 | TestFile getArtifactFile() 39 | 40 | TestFile getMetaDataFile() 41 | 42 | MavenPom getParsedPom() 43 | 44 | MavenMetaData getRootMetaData() 45 | } 46 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenPom.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util.repo.maven 2 | 3 | import groovy.xml.XmlParser 4 | 5 | class MavenPom { 6 | String groupId 7 | String artifactId 8 | String version 9 | String packaging 10 | String description 11 | final Map scopes = [:] 12 | 13 | MavenPom(File pomFile) { 14 | if (pomFile.exists()){ 15 | def pom = new XmlParser().parse(pomFile) 16 | 17 | groupId = pom.groupId[0]?.text() 18 | artifactId = pom.artifactId[0]?.text() 19 | version = pom.version[0]?.text() 20 | packaging = pom.packaging[0]?.text() 21 | description = pom.description[0]?.text() 22 | 23 | pom.dependencies.dependency.each { dep -> 24 | def scopeElement = dep.scope 25 | def scopeName = scopeElement ? scopeElement.text() : "runtime" 26 | def scope = scopes[scopeName] 27 | if (!scope) { 28 | scope = new MavenScope() 29 | scopes[scopeName] = scope 30 | } 31 | MavenDependency mavenDependency = new MavenDependency( 32 | groupId: dep.groupId.text(), 33 | artifactId: dep.artifactId.text(), 34 | version: dep.version.text(), 35 | classifier: dep.classifier ? dep.classifier.text() : null, 36 | type: dep.type ? dep.type.text() : null 37 | ) 38 | def key = "${mavenDependency.groupId}:${mavenDependency.artifactId}:${mavenDependency.version}" 39 | key += mavenDependency.classifier ? ":${mavenDependency.classifier}" : "" 40 | scope.dependencies[key] = mavenDependency 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenRepository.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util.repo.maven 2 | 3 | /** 4 | * A fixture for dealing with Maven repositories. 5 | */ 6 | interface MavenRepository { 7 | URI getUri() 8 | 9 | MavenModule module(String groupId, String artifactId) 10 | 11 | MavenModule module(String groupId, String artifactId, Object version) 12 | } 13 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/repo/maven/MavenScope.groovy: -------------------------------------------------------------------------------- 1 | package com.github.jengelman.gradle.plugins.shadow.util.repo.maven 2 | 3 | import org.apache.commons.lang3.StringUtils 4 | 5 | class MavenScope { 6 | Map dependencies = [:] 7 | 8 | void assertDependsOn(String[] expected) { 9 | assert dependencies.size() == expected.length 10 | expected.each { 11 | String key = StringUtils.substringBefore(it, "@") 12 | def dependency = expectDependency(key) 13 | 14 | String type = null 15 | if (it != key) { 16 | type = StringUtils.substringAfter(it, "@") 17 | } 18 | assert dependency.hasType(type) 19 | } 20 | } 21 | 22 | MavenDependency expectDependency(String key) { 23 | final dependency = dependencies[key] 24 | if (dependency == null) { 25 | throw new AssertionError("Could not find expected dependency $key. Actual: ${dependencies.values()}") 26 | } 27 | return dependency 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/jars/plexus-utils-1.4.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goooler/shadow/fee4caf3a1035b9d1182fb45cadadadcdb89185c/src/test/jars/plexus-utils-1.4.1.jar -------------------------------------------------------------------------------- /src/test/jars/test-artifact-1.0-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goooler/shadow/fee4caf3a1035b9d1182fb45cadadadcdb89185c/src/test/jars/test-artifact-1.0-SNAPSHOT.jar -------------------------------------------------------------------------------- /src/test/jars/test-project-1.0-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goooler/shadow/fee4caf3a1035b9d1182fb45cadadadcdb89185c/src/test/jars/test-project-1.0-SNAPSHOT.jar -------------------------------------------------------------------------------- /src/test/resources/components-1.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | org.apache.maven.wagon.Wagon 24 | http 25 | org.apache.maven.wagon.providers.http.LightweightHttpWagon 26 | per-lookup 27 | LightweightHttpWagon 28 | false 29 | 30 | 31 | org.apache.maven.wagon.Wagon 32 | https 33 | org.apache.maven.wagon.providers.http.LightweightHttpsWagon 34 | per-lookup 35 | LIghtweightHttpsWagon 36 | false 37 | 38 | 39 | 40 | User-Agent 41 | Apache Maven/${project.version} 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/test/resources/components-2.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | org.apache.maven.wagon.Wagon 24 | http 25 | org.apache.maven.wagon.providers.http.LightweightHttpWagon 26 | per-lookup 27 | LightweightHttpWagon 28 | false 29 | 30 | 31 | 32 | User-Agent 33 | Apache Maven/${project.version} 34 | 35 | 36 | 37 | 38 | 39 | org.apache.maven.wagon.Wagon 40 | https 41 | org.apache.maven.wagon.providers.http.LightweightHttpsWagon 42 | per-lookup 43 | LIghtweightHttpsWagon 44 | false 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/test/resources/components-expected.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | org.apache.maven.wagon.Wagon 24 | http 25 | org.apache.maven.wagon.providers.http.LightweightHttpWagon 26 | per-lookup 27 | LightweightHttpWagon 28 | false 29 | 30 | 31 | 32 | User-Agent 33 | Apache Maven/${project.version} 34 | 35 | 36 | 37 | 38 | 39 | org.apache.maven.wagon.Wagon 40 | https 41 | org.apache.maven.wagon.providers.http.LightweightHttpsWagon 42 | per-lookup 43 | LIghtweightHttpsWagon 44 | false 45 | 46 | 47 | 48 | User-Agent 49 | Apache Maven/${project.version} 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/test/resources/junit-3.8.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goooler/shadow/fee4caf3a1035b9d1182fb45cadadadcdb89185c/src/test/resources/junit-3.8.2.jar -------------------------------------------------------------------------------- /src/test/resources/test-artifact-1.0-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goooler/shadow/fee4caf3a1035b9d1182fb45cadadadcdb89185c/src/test/resources/test-artifact-1.0-SNAPSHOT.jar -------------------------------------------------------------------------------- /src/test/resources/test-project-1.0-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goooler/shadow/fee4caf3a1035b9d1182fb45cadadadcdb89185c/src/test/resources/test-project-1.0-SNAPSHOT.jar --------------------------------------------------------------------------------