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

Fixes to allow Configuration Caching in Gradle 6.6+ #591

Merged
merged 6 commits into from
Oct 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,10 @@ class ShadowApplicationPlugin implements Plugin<Project> {
}

protected void configureJarMainClass(Project project) {
ApplicationPluginConvention pluginConvention = (
ApplicationPluginConvention) project.convention.plugins.application

jar.inputs.property('mainClassName', { pluginConvention.mainClassName })
def classNameProvider = project.provider { project.convention.plugins.application.mainClassName }
jar.inputs.property('mainClassName', classNameProvider)
jar.doFirst {
manifest.attributes 'Main-Class': pluginConvention.mainClassName
manifest.attributes 'Main-Class': classNameProvider.get()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}

Expand Down Expand Up @@ -93,7 +91,7 @@ class ShadowApplicationPlugin implements Plugin<Project> {
startScripts.conventionMapping.applicationName = { pluginConvention.applicationName }
startScripts.conventionMapping.outputDir = { new File(project.buildDir, 'scriptsShadow') }
startScripts.conventionMapping.defaultJvmOpts = { pluginConvention.applicationDefaultJvmArgs }
startScripts.inputs.files jar
startScripts.inputs.files project.objects.fileCollection().from { -> jar }

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import org.gradle.api.Project
import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer
import org.gradle.api.artifacts.maven.MavenPom
import org.gradle.api.attributes.Bundling
import org.gradle.api.attributes.LibraryElements
import org.gradle.api.attributes.Category
import org.gradle.api.attributes.LibraryElements
import org.gradle.api.attributes.Usage
import org.gradle.api.attributes.java.TargetJvmVersion
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.plugins.MavenPlugin
import org.gradle.api.tasks.Upload
Expand Down Expand Up @@ -77,10 +76,13 @@ class ShadowJavaPlugin implements Plugin<Project> {
}
}
shadow.manifest.inheritFrom project.tasks.jar.manifest
def libsProvider = project.provider { -> [project.tasks.jar.manifest.attributes.get('Class-Path')] }
def files = project.objects.fileCollection().from { ->
project.configurations.findByName(ShadowBasePlugin.CONFIGURATION_NAME)
}
shadow.doFirst {
def files = project.configurations.findByName(ShadowBasePlugin.CONFIGURATION_NAME).files
if (files) {
def libs = [project.tasks.jar.manifest.attributes.get('Class-Path')]
if (!files.empty) {
def libs = libsProvider.get()
libs.addAll files.collect { "${it.name}" }
manifest.attributes 'Class-Path': libs.findAll { it }.join(' ')
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.SelfResolvingDependency
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.SourceSet
import org.vafer.jdependency.Clazz
import org.vafer.jdependency.Clazzpath
import org.vafer.jdependency.ClazzpathUnit
Expand All @@ -18,7 +17,7 @@ class UnusedTracker {
private final List<ClazzpathUnit> projectUnits
private final Clazzpath cp = new Clazzpath()

private UnusedTracker(List<File> classDirs, FileCollection classJars, FileCollection toMinimize) {
private UnusedTracker(Iterable<File> classDirs, FileCollection classJars, FileCollection toMinimize) {
this.toMinimize = toMinimize
projectUnits = classDirs.collect { cp.addClazzpathUnit(it) }
projectUnits.addAll(classJars.collect { cp.addClazzpathUnit(it) })
Expand All @@ -39,16 +38,8 @@ class UnusedTracker {
}
}

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.classesDirs
classDirs.addAll(classesDirs.findAll { it.isDirectory() })
}
return new UnusedTracker(classDirs, apiJars, toMinimize)
static UnusedTracker forProject(FileCollection apiJars, Iterable<File> sourceSetsClassesDirs, FileCollection toMinimize) {
return new UnusedTracker(sourceSetsClassesDirs, apiJars, toMinimize)
}

@InputFiles
Expand All @@ -71,7 +62,7 @@ class UnusedTracker {
}
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.gradle.api.Action;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DuplicatesStrategy;
import org.gradle.api.file.FileCollection;
import org.gradle.api.internal.DocumentationRegistry;
Expand All @@ -19,6 +20,7 @@
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.util.PatternSet;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -29,14 +31,26 @@ public class ShadowJar extends Jar implements ShadowSpec {

private List<Transformer> transformers;
private List<Relocator> relocators;
private List<Configuration> configurations;
private DependencyFilter dependencyFilter;
private transient List<Configuration> configurations;
private transient DependencyFilter dependencyFilter;

private boolean minimizeJar;
private DependencyFilter dependencyFilterForMinimize;
private transient DependencyFilter dependencyFilterForMinimize;
private FileCollection toMinimize;
private FileCollection apiJars;
private FileCollection sourceSetsClassesDirs;

private final ShadowStats shadowStats = new ShadowStats();
private final GradleVersionUtil versionUtil;

private final ConfigurableFileCollection includedDependencies = getProject().files(new Callable<FileCollection>() {

@Override
public FileCollection call() throws Exception {
return dependencyFilter.resolve(configurations);
}
});

public ShadowJar() {
super();
setDuplicatesStrategy(DuplicatesStrategy.INCLUDE); //shadow filters out files later. This was the default behavior in Gradle < 6.x
Expand Down Expand Up @@ -99,12 +113,55 @@ public InheritManifest getManifest() {
@Override
protected CopyAction createCopyAction() {
DocumentationRegistry documentationRegistry = getServices().get(DocumentationRegistry.class);
final UnusedTracker unusedTracker = minimizeJar ? UnusedTracker.forProject(getProject(), configurations, dependencyFilterForMinimize) : null;
final UnusedTracker unusedTracker = minimizeJar ? UnusedTracker.forProject(getApiJars(), getSourceSetsClassesDirs().getFiles(), getToMinimize()) : null;
return new ShadowCopyAction(getArchiveFile().get().getAsFile(), getInternalCompressor(), documentationRegistry,
this.getMetadataCharset(), transformers, relocators, getRootPatternSet(), shadowStats,
versionUtil, isPreserveFileTimestamps(), minimizeJar, unusedTracker);
}

@Classpath
FileCollection getToMinimize() {
if (toMinimize == null) {
toMinimize = minimizeJar
? dependencyFilterForMinimize.resolve(configurations).minus(getApiJars())
: getProject().getObjects().fileCollection();
}
return toMinimize;
}


@Classpath
FileCollection getApiJars() {
if (apiJars == null) {
apiJars = minimizeJar
? UnusedTracker.getApiJarsFromProject(getProject())
: getProject().getObjects().fileCollection();
}
return apiJars;
}


@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
FileCollection getSourceSetsClassesDirs() {
if (sourceSetsClassesDirs == null) {
ConfigurableFileCollection allClassesDirs = getProject().getObjects().fileCollection();
if (minimizeJar) {
for (SourceSet sourceSet : getProject().getExtensions().getByType(SourceSetContainer.class)) {
FileCollection classesDirs = sourceSet.getOutput().getClassesDirs();
allClassesDirs.from(classesDirs);
}
}
sourceSetsClassesDirs = allClassesDirs.filter(new Spec<File>() {
@Override
public boolean isSatisfiedBy(File file) {
return file.isDirectory();
}
});
}
return sourceSetsClassesDirs;
}

@Internal
protected ZipCompressor getInternalCompressor() {
return versionUtil.getInternalCompressor(getEntryCompression(), this);
Expand All @@ -119,13 +176,7 @@ protected void copy() {

@Classpath
public FileCollection getIncludedDependencies() {
return getProject().files(new Callable<FileCollection>() {

@Override
public FileCollection call() throws Exception {
return dependencyFilter.resolve(configurations);
}
});
return includedDependencies;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.github.jengelman.gradle.plugins.shadow

import com.github.jengelman.gradle.plugins.shadow.util.PluginSpecification

class ConfigurationCacheSpec extends PluginSpecification {

def setup() {
repo.module('shadow', 'a', '1.0')
.insertFile('a.properties', 'a')
.insertFile('a2.properties', 'a2')
.publish()
repo.module('shadow', 'b', '1.0')
.insertFile('b.properties', 'b')
.publish()

buildFile << """
dependencies {
compile 'shadow:a:1.0'
compile 'shadow:b:1.0'
}
""".stripIndent()
}

def "supports configuration cache"() {
given:
file('src/main/java/myapp/Main.java') << """
package myapp;
public class Main {
public static void main(String[] args) {
System.out.println("TestApp: Hello World! (" + args[0] + ")");
}
}
""".stripIndent()

buildFile << """
apply plugin: 'application'

mainClassName = 'myapp.Main'

dependencies {
compile 'shadow:a:1.0'
}

runShadow {
args 'foo'
}
""".stripIndent()

settingsFile << "rootProject.name = 'myapp'"

when:
runner.withArguments('--configuration-cache', 'shadowJar').build()
def result = runner.withArguments('--configuration-cache', 'shadowJar').build()

then:
result.output.contains("Reusing configuration cache.")
}

def "configuration caching supports includes"() {
given:
buildFile << """
shadowJar {
exclude 'a2.properties'
}
""".stripIndent()

when:
runner.withArguments('--configuration-cache', 'shadowJar').build()
output.delete()
def result = runner.withArguments('--configuration-cache', 'shadowJar').build()

then:
contains(output, ['a.properties', 'b.properties'])

and:
doesNotContain(output, ['a2.properties'])
result.output.contains("Reusing configuration cache.")
}

def "configuration caching supports minimize"() {
given:
file('settings.gradle') << """
include 'client', 'server'
""".stripIndent()

and:
file('client/src/main/java/client/Client.java') << """
package client;
public class Client {}
""".stripIndent()
file('client/build.gradle') << """
apply plugin: 'java'
repositories { maven { url "${repo.uri}" } }
dependencies { compile 'junit:junit:3.8.2' }
""".stripIndent()

and:
file('server/src/main/java/server/Server.java') << """
package server;
public class Server {}
""".stripIndent()
file('server/build.gradle') << """
apply plugin: 'java'
apply plugin: 'com.github.johnrengelman.shadow'

shadowJar {
minimize {
exclude(dependency('junit:junit:.*'))
}
}

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

and:
def output = getFile('server/build/libs/server-all.jar')

when:
runner.withArguments('--configuration-cache', 'shadowJar', '-s').build()
output.delete()
def result = runner.withArguments('--configuration-cache', 'shadowJar', '-s').build()

then:
output.exists()
contains(output, [
'server/Server.class',
'junit/framework/Test.class'
])
doesNotContain(output, ['client/Client.class'])

and:
result.output.contains("Reusing configuration cache.")
}
}