Consider transitives when identifying project dependencies

Previously, when building a layered jar, the Gradle plugin only
considered a configuration's direct dependencies when identifying
project dependencies. This resulted in transitive project dependencies
being missed when deciding which dependencies belong in the
application layer.

This commit updates ResolvedDependencies to consider all projects
from the root project when collecting the IDs of local projects. This
ensures that any project dependency, no matter where it appears in the
dependency graph, is successfully identified.

Fixes gh-25163
pull/25507/head
Andy Wilkinson 4 years ago
parent 7cb1605c11
commit 45e6c12cb6

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -74,15 +74,16 @@ public class BootJar extends Jar implements BootArchive {
*/ */
public BootJar() { public BootJar() {
this.support = new BootArchiveSupport(LAUNCHER, new LibrarySpec(), new ZipCompressionResolver()); this.support = new BootArchiveSupport(LAUNCHER, new LibrarySpec(), new ZipCompressionResolver());
this.bootInfSpec = getProject().copySpec().into("BOOT-INF"); Project project = getProject();
this.mainClass = getProject().getObjects().property(String.class); this.bootInfSpec = project.copySpec().into("BOOT-INF");
this.mainClass = project.getObjects().property(String.class);
configureBootInfSpec(this.bootInfSpec); configureBootInfSpec(this.bootInfSpec);
getMainSpec().with(this.bootInfSpec); getMainSpec().with(this.bootInfSpec);
getProject().getConfigurations().all((configuration) -> { project.getConfigurations().all((configuration) -> {
ResolvableDependencies incoming = configuration.getIncoming(); ResolvableDependencies incoming = configuration.getIncoming();
incoming.afterResolve((resolvableDependencies) -> { incoming.afterResolve((resolvableDependencies) -> {
if (resolvableDependencies == incoming) { if (resolvableDependencies == incoming) {
this.resolvedDependencies.processConfiguration(configuration); this.resolvedDependencies.processConfiguration(project, configuration);
} }
}); });
}); });

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,9 +22,9 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.artifacts.ResolvedConfiguration; import org.gradle.api.artifacts.ResolvedConfiguration;
@ -44,13 +44,15 @@ class ResolvedDependencies {
private final Map<Configuration, ResolvedConfigurationDependencies> configurationDependencies = new LinkedHashMap<>(); private final Map<Configuration, ResolvedConfigurationDependencies> configurationDependencies = new LinkedHashMap<>();
void processConfiguration(Configuration configuration) { private String projectId(Project project) {
Set<String> projectDependencyIds = configuration.getAllDependencies().withType(ProjectDependency.class).stream() return project.getGroup() + ":" + project.getName() + ":" + project.getVersion();
.map((projectDependency) -> projectDependency.getGroup() + ":" + projectDependency.getName() + ":" }
+ projectDependency.getVersion())
void processConfiguration(Project project, Configuration configuration) {
Set<String> localProjectIds = project.getRootProject().getAllprojects().stream().map(this::projectId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
this.configurationDependencies.put(configuration, this.configurationDependencies.put(configuration,
new ResolvedConfigurationDependencies(projectDependencyIds, configuration.getResolvedConfiguration())); new ResolvedConfigurationDependencies(localProjectIds, configuration.getResolvedConfiguration()));
} }
DependencyDescriptor find(File file) { DependencyDescriptor find(File file) {

@ -149,6 +149,7 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull(); assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/alpha-1.2.3.jar")).isNotNull(); assertThat(jarFile.getEntry("BOOT-INF/lib/alpha-1.2.3.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/bravo-1.2.3.jar")).isNotNull(); assertThat(jarFile.getEntry("BOOT-INF/lib/bravo-1.2.3.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/charlie-1.2.3.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/commons-lang3-3.9.jar")).isNotNull(); assertThat(jarFile.getEntry("BOOT-INF/lib/commons-lang3-3.9.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar")).isNotNull(); assertThat(jarFile.getEntry("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar")).isNotNull(); assertThat(jarFile.getEntry("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar")).isNotNull();
@ -171,7 +172,8 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
assertThat(indexedLayers.get("spring-boot-loader")).containsExactly("org/"); assertThat(indexedLayers.get("spring-boot-loader")).containsExactly("org/");
assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies); assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies);
assertThat(indexedLayers.get("application")).containsExactly("BOOT-INF/classes/", "BOOT-INF/classpath.idx", assertThat(indexedLayers.get("application")).containsExactly("BOOT-INF/classes/", "BOOT-INF/classpath.idx",
"BOOT-INF/layers.idx", "BOOT-INF/lib/alpha-1.2.3.jar", "BOOT-INF/lib/bravo-1.2.3.jar", "META-INF/"); "BOOT-INF/layers.idx", "BOOT-INF/lib/alpha-1.2.3.jar", "BOOT-INF/lib/bravo-1.2.3.jar",
"BOOT-INF/lib/charlie-1.2.3.jar", "META-INF/");
BuildResult listLayers = this.gradleBuild.build("listLayers"); BuildResult listLayers = this.gradleBuild.build("listLayers");
assertThat(listLayers.task(":listLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(listLayers.task(":listLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
String listLayersOutput = listLayers.getOutput(); String listLayersOutput = listLayers.getOutput();
@ -244,6 +246,7 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull(); assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/alpha-1.2.3.jar")).isNotNull(); assertThat(jarFile.getEntry("BOOT-INF/lib/alpha-1.2.3.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/bravo-1.2.3.jar")).isNotNull(); assertThat(jarFile.getEntry("BOOT-INF/lib/bravo-1.2.3.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/charlie-1.2.3.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/commons-lang3-3.9.jar")).isNotNull(); assertThat(jarFile.getEntry("BOOT-INF/lib/commons-lang3-3.9.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar")).isNotNull(); assertThat(jarFile.getEntry("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar")).isNotNull(); assertThat(jarFile.getEntry("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar")).isNotNull();
@ -259,6 +262,7 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
Set<String> expectedSubprojectDependencies = new TreeSet<>(); Set<String> expectedSubprojectDependencies = new TreeSet<>();
expectedSubprojectDependencies.add("BOOT-INF/lib/alpha-1.2.3.jar"); expectedSubprojectDependencies.add("BOOT-INF/lib/alpha-1.2.3.jar");
expectedSubprojectDependencies.add("BOOT-INF/lib/bravo-1.2.3.jar"); expectedSubprojectDependencies.add("BOOT-INF/lib/bravo-1.2.3.jar");
expectedSubprojectDependencies.add("BOOT-INF/lib/charlie-1.2.3.jar");
Set<String> expectedDependencies = new TreeSet<>(); Set<String> expectedDependencies = new TreeSet<>();
expectedDependencies.add("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar"); expectedDependencies.add("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar");
expectedDependencies.add("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar"); expectedDependencies.add("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar");
@ -348,7 +352,7 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
private void writeSettingsGradle() { private void writeSettingsGradle() {
try (PrintWriter writer = new PrintWriter( try (PrintWriter writer = new PrintWriter(
new FileWriter(new File(this.gradleBuild.getProjectDir(), "settings.gradle")))) { new FileWriter(new File(this.gradleBuild.getProjectDir(), "settings.gradle")))) {
writer.println("include 'alpha', 'bravo'"); writer.println("include 'alpha', 'bravo', 'charlie'");
} }
catch (IOException ex) { catch (IOException ex) {
throw new RuntimeException(ex); throw new RuntimeException(ex);

@ -323,7 +323,7 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
return null; return null;
}).given(resolvableDependencies).afterResolve(any(Action.class)); }).given(resolvableDependencies).afterResolve(any(Action.class));
given(configuration.getIncoming()).willReturn(resolvableDependencies); given(configuration.getIncoming()).willReturn(resolvableDependencies);
bootJar.getResolvedDependencies().processConfiguration(configuration); bootJar.getResolvedDependencies().processConfiguration(bootJar.getProject(), configuration);
} }
private ResolvedArtifact mockLibraryArtifact(String fileName, String group, String module, String version) { private ResolvedArtifact mockLibraryArtifact(String fileName, String group, String module, String version) {

@ -7,6 +7,11 @@ subprojects {
apply plugin: 'java' apply plugin: 'java'
group = 'org.example.projects' group = 'org.example.projects'
version = '1.2.3' version = '1.2.3'
if (it.name == 'bravo') {
dependencies {
implementation(project(':charlie'))
}
}
} }
bootJar { bootJar {

@ -7,6 +7,11 @@ subprojects {
apply plugin: 'java' apply plugin: 'java'
group = 'org.example.projects' group = 'org.example.projects'
version = '1.2.3' version = '1.2.3'
if (it.name == 'bravo') {
dependencies {
implementation(project(':charlie'))
}
}
} }
bootJar { bootJar {

Loading…
Cancel
Save