diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/AbstractDependencyFilter.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/AbstractDependencyFilter.groovy new file mode 100644 index 000000000..ca497e559 --- /dev/null +++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/AbstractDependencyFilter.groovy @@ -0,0 +1,120 @@ +package com.github.jengelman.gradle.plugins.shadow.internal + +import groovy.util.logging.Slf4j +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ResolvedDependency +import org.gradle.api.file.FileCollection +import org.gradle.api.specs.Spec +import org.gradle.api.specs.Specs + +@Slf4j +abstract class AbstractDependencyFilter implements DependencyFilter { + private final Project project + + protected final List> includeSpecs = [] + protected final List> excludeSpecs = [] + + AbstractDependencyFilter(Project project) { + assert project + this.project = project + } + + abstract protected void resolve(Set dependencies, + Set includedDependencies, + Set excludedDependencies) + + FileCollection resolve(Configuration configuration) { + Set includedDeps = [] + Set excludedDeps = [] + resolve(configuration.resolvedConfiguration.firstLevelModuleDependencies, includedDeps, excludedDeps) + return project.files(configuration.files) - project.files(excludedDeps.collect { + it.moduleArtifacts*.file + }.flatten()) + } + + FileCollection resolve(Collection configurations) { + configurations.collect { + resolve(it) + }.sum() as FileCollection ?: project.files() + } + + /** + * Exclude dependencies that match the provided spec. + * + * @param spec + * @return + */ + DependencyFilter exclude(Spec spec) { + excludeSpecs << spec + return this + } + + /** + * Include dependencies that match the provided spec. + * + * @param spec + * @return + */ + DependencyFilter include(Spec spec) { + includeSpecs << spec + return this + } + + /** + * Create a spec that matches the provided project notation on group, name, and version + * @param notation + * @return + */ + Spec project(Map notation) { + dependency(project.dependencies.project(notation)) + } + + /** + * Create a spec that matches the default configuration for the provided project path on group, name, and version + * + * @param notation + * @return + */ + Spec project(String notation) { + dependency(project.dependencies.project(path: notation, configuration: 'default')) + } + + /** + * Create a spec that matches dependencies using the provided notation on group, name, and version + * @param notation + * @return + */ + Spec dependency(Object notation) { + dependency(project.dependencies.create(notation)) + } + + /** + * Create a spec that matches the provided dependency on group, name, and version + * @param dependency + * @return + */ + Spec dependency(Dependency dependency) { + this.dependency({ ResolvedDependency it -> + (!dependency.group || it.moduleGroup.matches(dependency.group)) && + (!dependency.name || it.moduleName.matches(dependency.name)) && + (!dependency.version || it.moduleVersion.matches(dependency.version)) + }) + } + + /** + * Create a spec that matches the provided closure + * @param spec + * @return + */ + Spec dependency(Closure spec) { + return Specs.convertClosureToSpec(spec) + } + + protected boolean isIncluded(ResolvedDependency dependency) { + boolean include = includeSpecs.empty || includeSpecs.any { it.isSatisfiedBy(dependency) } + boolean exclude = !excludeSpecs.empty && excludeSpecs.any { it.isSatisfiedBy(dependency) } + return include && !exclude + } +} diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/DefaultDependencyFilter.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/DefaultDependencyFilter.groovy index 3cb7dee7b..4734fedd1 100644 --- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/DefaultDependencyFilter.groovy +++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/DefaultDependencyFilter.groovy @@ -2,114 +2,16 @@ package com.github.jengelman.gradle.plugins.shadow.internal import groovy.util.logging.Slf4j import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.ResolvedDependency -import org.gradle.api.file.FileCollection -import org.gradle.api.specs.Spec -import org.gradle.api.specs.Specs @Slf4j -class DefaultDependencyFilter implements DependencyFilter { - - private final Project project - - private final List> includeSpecs = [] - private final List> excludeSpecs = [] +class DefaultDependencyFilter extends AbstractDependencyFilter { DefaultDependencyFilter(Project project) { - assert project - this.project = project - } - - FileCollection resolve(Configuration configuration) { - Set includedDeps = [] - Set excludedDeps = [] - resolve(configuration.resolvedConfiguration.firstLevelModuleDependencies, includedDeps, excludedDeps) - return project.files(configuration.files) - project.files(excludedDeps.collect { - it.moduleArtifacts*.file - }.flatten()) - } - - FileCollection resolve(Collection configurations) { - configurations.collect { - resolve(it) - }.sum() as FileCollection ?: project.files() - } - - /** - * Exclude dependencies that match the provided spec. - * - * @param spec - * @return - */ - DependencyFilter exclude(Spec spec) { - excludeSpecs << spec - return this - } - - /** - * Include dependencies that match the provided spec. - * - * @param spec - * @return - */ - DependencyFilter include(Spec spec) { - includeSpecs << spec - return this - } - - /** - * Create a spec that matches the provided project notation on group, name, and version - * @param notation - * @return - */ - Spec project(Map notation) { - dependency(project.dependencies.project(notation)) - } - - /** - * Create a spec that matches the default configuration for the provided project path on group, name, and version - * - * @param notation - * @return - */ - Spec project(String notation) { - dependency(project.dependencies.project(path: notation, configuration: 'default')) + super(project) } - /** - * Create a spec that matches dependencies using the provided notation on group, name, and version - * @param notation - * @return - */ - Spec dependency(Object notation) { - dependency(project.dependencies.create(notation)) - } - - /** - * Create a spec that matches the provided dependency on group, name, and version - * @param dependency - * @return - */ - Spec dependency(Dependency dependency) { - this.dependency({ ResolvedDependency it -> - (!dependency.group || it.moduleGroup.matches(dependency.group)) && - (!dependency.name || it.moduleName.matches(dependency.name)) && - (!dependency.version || it.moduleVersion.matches(dependency.version)) - }) - } - - /** - * Create a spec that matches the provided closure - * @param spec - * @return - */ - Spec dependency(Closure spec) { - return Specs.convertClosureToSpec(spec) - } - - + @Override protected void resolve(Set dependencies, Set includedDependencies, Set excludedDependencies) { @@ -120,9 +22,4 @@ class DefaultDependencyFilter implements DependencyFilter { } } - protected boolean isIncluded(ResolvedDependency dependency) { - boolean include = includeSpecs.empty || includeSpecs.any { it.isSatisfiedBy(dependency) } - boolean exclude = !excludeSpecs.empty && excludeSpecs.any { it.isSatisfiedBy(dependency) } - return include && !exclude - } } diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/MinimizeDependencyFilter.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/MinimizeDependencyFilter.groovy new file mode 100644 index 000000000..762005ffc --- /dev/null +++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/MinimizeDependencyFilter.groovy @@ -0,0 +1,29 @@ +package com.github.jengelman.gradle.plugins.shadow.internal + +import groovy.util.logging.Slf4j +import org.gradle.api.Project +import org.gradle.api.artifacts.ResolvedDependency + +@Slf4j +class MinimizeDependencyFilter extends AbstractDependencyFilter { + + MinimizeDependencyFilter(Project project) { + super(project) + } + + @Override + protected void resolve(Set dependencies, + Set includedDependencies, + Set excludedDependencies) { + + dependencies.each { + if (isIncluded(it) && !isParentExcluded(excludedDependencies, it) ? includedDependencies.add(it) : excludedDependencies.add(it)) { + resolve(it.children, includedDependencies, excludedDependencies) + } + } + } + + private boolean isParentExcluded(Set excludedDependencies, ResolvedDependency dependency) { + excludedDependencies.any { dependency.parents.contains(it) } + } +} \ No newline at end of file diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.java b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.java index 159d90f6e..6130a3cdc 100644 --- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.java +++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.java @@ -9,8 +9,6 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer; import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer; import groovy.lang.MetaClass; -import java.io.File; -import java.util.Set; import org.codehaus.groovy.runtime.InvokerHelper; import org.gradle.api.Action; import org.gradle.api.artifacts.Configuration; @@ -45,7 +43,7 @@ public ShadowJar() { super(); versionUtil = new GradleVersionUtil(getProject().getGradle().getGradleVersion()); dependencyFilter = new DefaultDependencyFilter(getProject()); - dependencyFilterForMinimize = new DefaultDependencyFilter(getProject()); + dependencyFilterForMinimize = new MinimizeDependencyFilter(getProject()); setManifest(new DefaultInheritManifest(getServices().get(FileResolver.class))); transformers = new ArrayList<>(); relocators = new ArrayList<>(); diff --git a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowPluginSpec.groovy b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowPluginSpec.groovy index 1ced02a7b..e354f26e3 100644 --- a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowPluginSpec.groovy +++ b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowPluginSpec.groovy @@ -321,6 +321,167 @@ class ShadowPluginSpec extends PluginSpecification { doesNotContain(serverOutput, ['client/Client.class']) } + /** + * 'Client', 'Server' and 'junit' are independent. + * Unused classes of 'client' and theirs dependencies shouldn't be removed. + */ + def 'exclude a project from minimize '() { + given: + file('settings.gradle') << """ + include 'client', 'server' + """.stripIndent() + + file('client/src/main/java/client/Client.java') << """ + package client; + public class Client {} + """.stripIndent() + + file('client/build.gradle') << """ + apply plugin: 'java' + repositories { maven { url "${repo.uri}" } } + """.stripIndent() + + file('server/src/main/java/server/Server.java') << """ + package server; + public class Server {} + """.stripIndent() + + file('server/build.gradle') << """ + apply plugin: 'java' + apply plugin: 'com.github.johnrengelman.shadow' + + shadowJar { + minimize { + exclude(project(':client')) + } + } + + repositories { maven { url "${repo.uri}" } } + dependencies { compile project(':client') } + """.stripIndent() + + File serverOutput = file('server/build/libs/server-all.jar') + + when: + runner.withArguments(':server:shadowJar', '--stacktrace').withDebug(true).build() + + then: + contains(serverOutput, [ + 'client/Client.class', + 'server/Server.class' + ]) + } + + /** + * 'Client', 'Server' and 'junit' are independent. + * Unused classes of 'client' and theirs dependencies shouldn't be removed. + */ + def 'exclude a project from minimize - shall not exclude transitive dependencies that are used in subproject'() { + given: + file('settings.gradle') << """ + include 'client', 'server' + """.stripIndent() + + file('client/src/main/java/client/Client.java') << """ + package client; + import junit.framework.TestCase; + public class Client extends TestCase { + public static void main(String[] args) {} + } + """.stripIndent() + + file('client/build.gradle') << """ + apply plugin: 'java' + repositories { maven { url "${repo.uri}" } } + dependencies { compile 'junit:junit:3.8.2' } + """.stripIndent() + + file('server/src/main/java/server/Server.java') << """ + package server; + public class Server {} + """.stripIndent() + + file('server/build.gradle') << """ + apply plugin: 'java' + apply plugin: 'com.github.johnrengelman.shadow' + + shadowJar { + minimize { + exclude(project(':client')) + } + } + + repositories { maven { url "${repo.uri}" } } + dependencies { compile project(':client') } + """.stripIndent() + + File serverOutput = file('server/build/libs/server-all.jar') + + when: + runner.withArguments(':server:shadowJar', '--stacktrace').withDebug(true).build() + + then: + contains(serverOutput, [ + 'client/Client.class', + 'server/Server.class', + 'junit/framework/TestCase.class' + ]) + } + + /** + * 'Client', 'Server' and 'junit' are independent. + * Unused classes of 'client' and theirs dependencies shouldn't be removed. + */ + def 'exclude a project from minimize - shall not exclude transitive dependencies from subproject that are not used'() { + given: + file('settings.gradle') << """ + include 'client', 'server' + """.stripIndent() + + file('client/src/main/java/client/Client.java') << """ + package client; + public class Client { } + """.stripIndent() + + file('client/build.gradle') << """ + apply plugin: 'java' + repositories { maven { url "${repo.uri}" } } + dependencies { compile 'junit:junit:3.8.2' } + """.stripIndent() + + file('server/src/main/java/server/Server.java') << """ + package server; + public class Server {} + """.stripIndent() + + file('server/build.gradle') << """ + apply plugin: 'java' + apply plugin: 'com.github.johnrengelman.shadow' + + shadowJar { + minimize { + exclude(project(':client')) + } + } + + repositories { maven { url "${repo.uri}" } } + dependencies { compile project(':client') } + """.stripIndent() + + File serverOutput = file('server/build/libs/server-all.jar') + + when: + runner.withArguments(':server:shadowJar', '--stacktrace').withDebug(true).build() + + then: + contains(serverOutput, [ + 'client/Client.class', + 'server/Server.class', + 'junit/framework/TestCase.class' + ]) + } + + /** * 'api' used as api for 'impl', and depended on 'lib'. 'junit' is independent. * The minimize shall remove 'junit', but not 'api'.