Skip to content

Commit

Permalink
Lazy exact dependencies (#2639)
Browse files Browse the repository at this point in the history
`baseline-exact-dependencies` is now far more lazy around `Configuration` creation in order to support Gradle 8.
  • Loading branch information
CRogers authored Oct 12, 2023
1 parent 642e1e3 commit f6e4309
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 92 deletions.
6 changes: 6 additions & 0 deletions changelog/@unreleased/pr-2639.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type: fix
fix:
description: '`baseline-exact-dependencies` is now far more lazy around `Configuration`
creation in order to support Gradle 8.'
links:
- https://github.com/palantir/gradle-baseline/pull/2639
1 change: 1 addition & 0 deletions gradle-baseline-java/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ tasks.test.dependsOn tasks.publishToMavenLocal
test {
environment 'CIRCLE_ARTIFACTS', "${buildDir}/artifacts"
environment 'CIRCLE_TEST_REPORTS', "${buildDir}/circle-reports"
systemProperty 'ignoreDeprecations', 'true'
}

gradlePlugin {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.apache.maven.shared.dependency.analyzer.DefaultClassAnalyzer;
import org.apache.maven.shared.dependency.analyzer.DependencyAnalyzer;
import org.apache.maven.shared.dependency.analyzer.asm.ASMDependencyAnalyzer;
import org.gradle.api.NamedDomainObjectProvider;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
Expand Down Expand Up @@ -79,7 +80,7 @@ public void apply(Project project) {
project.getConvention()
.getPlugin(JavaPluginConvention.class)
.getSourceSets()
.all(sourceSet ->
.configureEach(sourceSet ->
configureSourceSet(project, sourceSet, checkUnusedDependencies, checkImplicitDependencies));
});
}
Expand All @@ -89,19 +90,15 @@ private static void configureSourceSet(
SourceSet sourceSet,
TaskProvider<CheckUnusedDependenciesParentTask> checkUnusedDependencies,
TaskProvider<CheckImplicitDependenciesParentTask> checkImplicitDependencies) {
Configuration implementation =
project.getConfigurations().getByName(sourceSet.getImplementationConfigurationName());
Optional<Configuration> maybeCompile =
Optional.ofNullable(project.getConfigurations().findByName(getCompileConfigurationName(sourceSet)));
Configuration compileClasspath =
project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName());

Configuration explicitCompile = project.getConfigurations()
.create("baseline-exact-dependencies-" + sourceSet.getName(), conf -> {
conf.setDescription(String.format(
"Tracks the explicit (not inherited) dependencies added to either %s "
+ "or compile (deprecated)",
implementation));

NamedDomainObjectProvider<Configuration> compileClasspath =
project.getConfigurations().named(sourceSet.getCompileClasspathConfigurationName());

NamedDomainObjectProvider<Configuration> explicitCompile = project.getConfigurations()
.register("baseline-exact-dependencies-" + sourceSet.getName(), conf -> {
conf.setDescription(
"Tracks the explicit (not inherited) dependencies added to either implementation "
+ "or compile (deprecated)");
conf.setVisible(false);
conf.setCanBeConsumed(false);

Expand All @@ -119,80 +116,93 @@ private static void configureSourceSet(
}
});

// Without this, the 'checkUnusedDependencies correctly picks up project dependency on java-library'
// test fails, by not causing gradle run the jar task, but resolving the path to the jar (rather
// than to the classes directory), which then doesn't exist.

// Specifically, we need to pick up the LIBRARY_ELEMENTS_ATTRIBUTE, which is being configured on
// compileClasspath in JavaBasePlugin.defineConfigurationsForSourceSet, but we can't reference it
// directly because that would require us to depend on Gradle 5.6.
// Instead, we just copy the attributes from compileClasspath.
compileClasspath.getAttributes().keySet().forEach(attribute -> {
Object value = compileClasspath.getAttributes().getAttribute(attribute);
conf.getAttributes().attribute((Attribute<Object>) attribute, value);
});
});
project.afterEvaluate(_ignored -> {
// Without this, the 'checkUnusedDependencies correctly picks up project dependency on
// java-library' test fails, by not causing gradle run the jar task, but resolving the path to
// the jar (rather than to the classes directory), which then doesn't exist.
// Specifically, we need to pick up the LIBRARY_ELEMENTS_ATTRIBUTE, which is being configured on
// compileClasspath in JavaBasePlugin.defineConfigurationsForSourceSet, but we can't reference
// it directly because that would require us to depend on Gradle 5.6.
// Instead, we just copy the attributes from compileClasspath.
compileClasspath.get().getAttributes().keySet().forEach(attribute -> {
Object value =
compileClasspath.get().getAttributes().getAttribute(attribute);
conf.getAttributes().attribute((Attribute<Object>) attribute, value);
});

// Figure out what our compile dependencies are while ignoring dependencies we've inherited from other source
// sets. For example, if we are `test`, some of our configurations extend from the `main` source set:
// testImplementation extendsFrom(implementation)
// \-- testCompile extendsFrom(compile)
// We therefore want to look at only the dependencies _directly_ declared in the implementation and compile
// configurations (belonging to our source set)
project.afterEvaluate(p -> {
Configuration implCopy = implementation.copy();
// Without these, explicitCompile will successfully resolve 0 files and you'll waste 1 hour trying
// to figure out why.
project.getConfigurations().add(implCopy);

explicitCompile.extendsFrom(implCopy);

// For Gradle 6 and below, the compile configuration might still be used.
maybeCompile.ifPresent(compile -> {
Configuration compileCopy = compile.copy();
// Ensure it's not resolvable, otherwise plugins that resolve all configurations might have
// a bad time resolving this with GCV, if you have direct dependencies without corresponding entries in
// versions.props, but instead rely on getting a version for them from the lock file.
compileCopy.setCanBeResolved(false);
compileCopy.setCanBeConsumed(false);

project.getConfigurations().add(compileCopy);

explicitCompile.extendsFrom(compileCopy);
});
});
// Figure out what our compile dependencies are while ignoring dependencies we've inherited from
// other source sets. For example, if we are `test`, some of our configurations extend from the
// `main` source set:
// testImplementation extendsFrom(implementation)
// \-- testCompile extendsFrom(compile)
// We therefore want to look at only the dependencies _directly_ declared in the implementation
// and compile configurations (belonging to our source set)
Configuration implCopy = project.getConfigurations()
.getByName(sourceSet.getImplementationConfigurationName())
.copy();

// Without these, explicitCompile will successfully resolve 0 files and you'll waste 1 hour
// trying to figure out why.
project.getConfigurations().add(implCopy);

conf.extendsFrom(implCopy);

Optional<Configuration> maybeCompile = Optional.ofNullable(
project.getConfigurations().findByName(getCompileConfigurationName(sourceSet)));

// For Gradle 6 and below, the compile configuration might still be used.
maybeCompile.ifPresent(compile -> {
Configuration compileCopy = compile.copy();
// Ensure it's not resolvable, otherwise plugins that resolve all configurations might have
// a bad time resolving this with GCV, if you have direct dependencies without corresponding
// entries in versions.props, but instead rely on getting a version for them from the lock
// file.
compileCopy.setCanBeResolved(false);
compileCopy.setCanBeConsumed(false);

project.getConfigurations().add(compileCopy);

conf.extendsFrom(compileCopy);
});
});

explicitCompile.withDependencies(deps -> {
// Pick up GCV locks. We're making an internal assumption that this configuration exists,
// but we can rely on this since we control GCV.
// Alternatively, we could tell GCV to lock this configuration, at the cost of a slightly more
// expensive 'unifiedClasspath' resolution during lock computation.
if (project.getRootProject().getPluginManager().hasPlugin("com.palantir.versions-lock")) {
explicitCompile.extendsFrom(project.getConfigurations().getByName("lockConstraints"));
}
// Inherit the excludes from compileClasspath too (that get aggregated from all its super-configurations).
compileClasspath.getExcludeRules().forEach(rule -> explicitCompile.exclude(excludeRuleAsMap(rule)));
});
conf.withDependencies(deps -> {
// Pick up GCV locks. We're making an internal assumption that this configuration exists,
// but we can rely on this since we control GCV.
// Alternatively, we could tell GCV to lock this configuration, at the cost of a slightly more
// expensive 'unifiedClasspath' resolution during lock computation.
if (project.getRootProject().getPluginManager().hasPlugin("com.palantir.versions-lock")) {
conf.extendsFrom(project.getConfigurations().getByName("lockConstraints"));
}
// Inherit the excludes from compileClasspath too (that get aggregated from all its
// super-configurations).
compileClasspath.get().getExcludeRules().forEach(rule -> conf.exclude(excludeRuleAsMap(rule)));
});

// Since we are copying configurations before resolving 'explicitCompile', make double sure that it's not
// being resolved (or dependencies realized via `.getIncoming().getDependencies()`) too early.
AtomicBoolean projectsEvaluated = new AtomicBoolean();
project.getGradle().projectsEvaluated(g -> projectsEvaluated.set(true));
explicitCompile
.getIncoming()
.beforeResolve(ir -> Preconditions.checkState(
projectsEvaluated.get()
|| (project.getGradle().getStartParameter().isConfigureOnDemand()
&& project.getState().getExecuted()),
"Tried to resolve %s too early.",
explicitCompile));
// Since we are copying configurations before resolving 'explicitCompile', make double sure that
// it's not being resolved (or dependencies realized via `.getIncoming().getDependencies()`)
// too early.
AtomicBoolean projectsEvaluated = new AtomicBoolean();
project.getGradle().projectsEvaluated(g -> projectsEvaluated.set(true));

conf.getIncoming().beforeResolve(_ignored -> {
Preconditions.checkState(
projectsEvaluated.get()
|| (project.getGradle()
.getStartParameter()
.isConfigureOnDemand()
&& project.getState().getExecuted()),
"Tried to resolve %s too early.",
conf);
});
});

TaskProvider<CheckUnusedDependenciesTask> sourceSetUnusedDependencies = project.getTasks()
.register(
checkUnusedDependenciesNameForSourceSet(sourceSet), CheckUnusedDependenciesTask.class, task -> {
task.dependsOn(sourceSet.getClassesTaskName());
task.setSourceClasses(sourceSet.getOutput().getClassesDirs());
task.dependenciesConfiguration(explicitCompile);
task.getDependenciesConfigurations().add(explicitCompile);

// this is liberally applied to ease the Java8 -> 11 transition
task.ignore("javax.annotation", "javax.annotation-api");
Expand All @@ -211,7 +221,7 @@ private static void configureSourceSet(
task -> {
task.dependsOn(sourceSet.getClassesTaskName());
task.setSourceClasses(sourceSet.getOutput().getClassesDirs());
task.dependenciesConfiguration(compileClasspath);
task.getDependenciesConfigurations().add(compileClasspath);
task.suggestionConfigurationName(sourceSet.getImplementationConfigurationName());

task.ignore("org.slf4j", "slf4j-api");
Expand All @@ -227,7 +237,7 @@ static String checkUnusedDependenciesNameForSourceSet(SourceSet sourceSet) {
}

/**
* The {@link SourceSet#getCompileConfigurationName()} method got removed in Gradle 7. Because we want to stay
* The {@code SourceSet#getCompileConfigurationName()} method got removed in Gradle 7. Because we want to stay
* compatible with Gradle 6 but can't compile this method, we reimplement it temporarily.
* TODO(fwindheuser): Remove after dropping support for Gradle 6.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,10 @@ private boolean shouldIgnore(ResolvedArtifact artifact) {
}

@Classpath
public final Provider<List<Configuration>> getDependenciesConfigurations() {
public final ListProperty<Configuration> getDependenciesConfigurations() {
return dependenciesConfigurations;
}

public final void dependenciesConfiguration(Configuration dependenciesConfiguration) {
this.dependenciesConfigurations.add(Objects.requireNonNull(dependenciesConfiguration));
}

@Classpath
public final Provider<FileCollection> getSourceClasses() {
return sourceClasses;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,10 @@ private boolean shouldIgnore(ResolvedArtifact artifact) {
}

@Classpath
public final Provider<List<Configuration>> getDependenciesConfigurations() {
public final ListProperty<Configuration> getDependenciesConfigurations() {
return dependenciesConfigurations;
}

public final void dependenciesConfiguration(Configuration dependenciesConfiguration) {
this.dependenciesConfigurations.add(Objects.requireNonNull(dependenciesConfiguration));
}

@Input
public final Provider<List<Configuration>> getSourceOnlyConfigurations() {
return sourceOnlyConfigurations;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

public final class GradleTestVersions {
public static final ImmutableList<String> VERSIONS =
ImmutableList.of(Baseline.MIN_GRADLE_VERSION.getVersion(), "7.1.1", "7.3");
ImmutableList.of(Baseline.MIN_GRADLE_VERSION.getVersion(), "7.6.2", "8.4");

private GradleTestVersions() {}
}

0 comments on commit f6e4309

Please sign in to comment.