Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Take into account api scope on minimization #405

Merged
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@ task relocateShadowJar(type: ConfigureShadowRelocation) {
target = tasks.shadowJar
}

tasks.shadowJar.dependsOn tasks.relocateShadowJar
tasks.shadowJar.dependsOn tasks.relocateShadowJar
5 changes: 5 additions & 0 deletions src/docs/asciidoc/15-minimizing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,8 @@ shadowJar {
}
}
----

[NOTE]
====
Dependencies scoped as `api` will automatically excluded from minimization and used as "entry points" on minimization.
====
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.github.jengelman.gradle.plugins.shadow.internal

import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.SourceSet
import org.vafer.jdependency.Clazz
Expand All @@ -13,9 +15,10 @@ class UnusedTracker {
private final List<ClazzpathUnit> projectUnits
private final Clazzpath cp = new Clazzpath()

private UnusedTracker(List<File> classDirs, FileCollection toMinimize) {
private UnusedTracker(List<File> classDirs, FileCollection classJars, FileCollection toMinimize) {
this.toMinimize = toMinimize
projectUnits = classDirs.collect { cp.addClazzpathUnit(it) }
projectUnits.addAll(classJars.collect { cp.addClazzpathUnit(it) })
}

Set<String> findUnused() {
Expand All @@ -33,12 +36,33 @@ class UnusedTracker {
}
}

static UnusedTracker forProject(Project project, FileCollection toMinimize) {
static UnusedTracker forProject(Project project, List<Configuration> configurations, DependencyFilter dependencyFilter) {
def apiJars = getApiJarsFromProject(project)
FileCollection toMinimize = dependencyFilter.resolve(configurations) - apiJars

final List<File> classDirs = new ArrayList<>()
for (SourceSet sourceSet in project.sourceSets) {
Iterable<File> classesDirs = sourceSet.output.hasProperty('classesDirs') ? sourceSet.output.classesDirs : [sourceSet.output.classesDir]
Iterable<File> classesDirs = sourceSet.output.classesDirs
classDirs.addAll(classesDirs.findAll { it.isDirectory() })
}
return new UnusedTracker(classDirs, toMinimize)
return new UnusedTracker(classDirs, apiJars, toMinimize)
}

private static FileCollection getApiJarsFromProject(Project project) {
def apiDependencies = project.configurations.asMap['api']?.dependencies ?: null
if (apiDependencies == null) return project.files()

def runtimeConfiguration = project.configurations.asMap['runtimeClasspath'] ?: project.configurations.runtime
def apiJars = new LinkedList<File>()
apiDependencies.each { dep ->
if (dep instanceof ProjectDependency) {
apiJars.addAll(getApiJarsFromProject(dep.dependencyProject))
apiJars.add(runtimeConfiguration.find { it.name.endsWith("${dep.name}.jar") } as File)
} else {
apiJars.add(runtimeConfiguration.find { it.name.startsWith("${dep.name}-") } as File)
}
}

return project.files(apiJars)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ public InheritManifest getManifest() {
@Override
protected CopyAction createCopyAction() {
DocumentationRegistry documentationRegistry = getServices().get(DocumentationRegistry.class);
FileCollection toMinimize = dependencyFilterForMinimize.resolve(configurations);
final UnusedTracker unusedTracker = UnusedTracker.forProject(getProject(), toMinimize);
final UnusedTracker unusedTracker = UnusedTracker.forProject(getProject(), configurations, dependencyFilterForMinimize);
return new ShadowCopyAction(getArchivePath(), getInternalCompressor(), documentationRegistry,
this.getMetadataCharset(), transformers, relocators, getRootPatternSet(), shadowStats,
versionUtil, isPreserveFileTimestamps(), minimizeJar, unusedTracker);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,160 @@ class ShadowPluginSpec extends PluginSpecification {
doesNotContain(serverOutput, ['client/Client.class'])
}

/**
* 'api' used as api for 'impl', and depended on 'lib'. 'junit' is independent.
* The minimize shall remove 'junit', but not 'api'.
* Unused classes of 'api' and theirs dependencies also shouldn't be removed.
*/
def 'use minimize with dependencies with api scope'() {
given:
file('settings.gradle') << """
include 'api', 'lib', 'impl'
""".stripIndent()

file('lib/src/main/java/lib/LibEntity.java') << """
package lib;
public interface LibEntity {}
""".stripIndent()

file('lib/src/main/java/lib/UnusedLibEntity.java') << """
package lib;
public class UnusedLibEntity implements LibEntity {}
""".stripIndent()

file('lib/build.gradle') << """
apply plugin: 'java'
repositories { maven { url "${repo.uri}" } }
""".stripIndent()

file('api/src/main/java/api/Entity.java') << """
package api;
public interface Entity {}
""".stripIndent()

file('api/src/main/java/api/UnusedEntity.java') << """
package api;
import lib.LibEntity;
public class UnusedEntity implements LibEntity {}
""".stripIndent()

file('api/build.gradle') << """
apply plugin: 'java'
repositories { maven { url "${repo.uri}" } }
dependencies {
compile 'junit:junit:3.8.2'
compile project(':lib')
}
""".stripIndent()

file('impl/src/main/java/impl/SimpleEntity.java') << """
package impl;
import api.Entity;
public class SimpleEntity implements Entity {}
""".stripIndent()

file('impl/build.gradle') << """
apply plugin: 'java-library'
apply plugin: 'com.github.johnrengelman.shadow'

shadowJar {
minimize()
}

repositories { maven { url "${repo.uri}" } }
dependencies { api project(':api') }
""".stripIndent()

File serverOutput = file('impl/build/libs/impl-all.jar')

when:
runner.withArguments(':impl:shadowJar', '--stacktrace').withDebug(true).build()

then:
contains(serverOutput, [
'impl/SimpleEntity.class',
'api/Entity.class',
'api/UnusedEntity.class',
'lib/LibEntity.class',
])
doesNotContain(serverOutput, ['junit/framework/Test.class', 'lib/UnusedLibEntity.class'])
}

/**
* 'api' used as api for 'impl', and 'lib' used as api for 'api'.
* Unused classes of 'api' and 'lib' shouldn't be removed.
*/
def 'use minimize with transitive dependencies with api scope'() {
given:
file('settings.gradle') << """
include 'api', 'lib', 'impl'
""".stripIndent()

file('lib/src/main/java/lib/LibEntity.java') << """
package lib;
public interface LibEntity {}
""".stripIndent()

file('lib/src/main/java/lib/UnusedLibEntity.java') << """
package lib;
public class UnusedLibEntity implements LibEntity {}
""".stripIndent()

file('lib/build.gradle') << """
apply plugin: 'java'
repositories { maven { url "${repo.uri}" } }
""".stripIndent()

file('api/src/main/java/api/Entity.java') << """
package api;
public interface Entity {}
""".stripIndent()

file('api/src/main/java/api/UnusedEntity.java') << """
package api;
import lib.LibEntity;
public class UnusedEntity implements LibEntity {}
""".stripIndent()

file('api/build.gradle') << """
apply plugin: 'java-library'
repositories { maven { url "${repo.uri}" } }
dependencies { api project(':lib') }
""".stripIndent()

file('impl/src/main/java/impl/SimpleEntity.java') << """
package impl;
import api.Entity;
public class SimpleEntity implements Entity {}
""".stripIndent()

file('impl/build.gradle') << """
apply plugin: 'java-library'
apply plugin: 'com.github.johnrengelman.shadow'

shadowJar {
minimize()
}

repositories { maven { url "${repo.uri}" } }
dependencies { api project(':api') }
""".stripIndent()

File serverOutput = file('impl/build/libs/impl-all.jar')

when:
runner.withArguments(':impl:shadowJar', '--stacktrace').withDebug(true).build()

then:
contains(serverOutput, [
'impl/SimpleEntity.class',
'api/Entity.class',
'api/UnusedEntity.class',
'lib/LibEntity.class',
'lib/UnusedLibEntity.class'
])
}

def 'depend on project shadow jar'() {
given:
file('settings.gradle') << """
Expand Down