Skip to content

Commit

Permalink
Consider transitives when identifying project dependencies
Browse files Browse the repository at this point in the history
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
  • Loading branch information
wilkinsona committed Feb 10, 2021
1 parent 7cb1605 commit 45e6c12
Show file tree
Hide file tree
Showing 6 changed files with 32 additions and 15 deletions.
@@ -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");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -74,15 +74,16 @@ public class BootJar extends Jar implements BootArchive {
*/
public BootJar() {
this.support = new BootArchiveSupport(LAUNCHER, new LibrarySpec(), new ZipCompressionResolver());
this.bootInfSpec = getProject().copySpec().into("BOOT-INF");
this.mainClass = getProject().getObjects().property(String.class);
Project project = getProject();
this.bootInfSpec = project.copySpec().into("BOOT-INF");
this.mainClass = project.getObjects().property(String.class);
configureBootInfSpec(this.bootInfSpec);
getMainSpec().with(this.bootInfSpec);
getProject().getConfigurations().all((configuration) -> {
project.getConfigurations().all((configuration) -> {
ResolvableDependencies incoming = configuration.getIncoming();
incoming.afterResolve((resolvableDependencies) -> {
if (resolvableDependencies == incoming) {
this.resolvedDependencies.processConfiguration(configuration);
this.resolvedDependencies.processConfiguration(project, configuration);
}
});
});
Expand Down
@@ -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");
* you may not use this file except in compliance with the License.
Expand All @@ -22,9 +22,9 @@
import java.util.Set;
import java.util.stream.Collectors;

import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.artifacts.ResolvedConfiguration;

Expand All @@ -44,13 +44,15 @@ class ResolvedDependencies {

private final Map<Configuration, ResolvedConfigurationDependencies> configurationDependencies = new LinkedHashMap<>();

void processConfiguration(Configuration configuration) {
Set<String> projectDependencyIds = configuration.getAllDependencies().withType(ProjectDependency.class).stream()
.map((projectDependency) -> projectDependency.getGroup() + ":" + projectDependency.getName() + ":"
+ projectDependency.getVersion())
private String projectId(Project project) {
return project.getGroup() + ":" + project.getName() + ":" + project.getVersion();
}

void processConfiguration(Project project, Configuration configuration) {
Set<String> localProjectIds = project.getRootProject().getAllprojects().stream().map(this::projectId)
.collect(Collectors.toSet());
this.configurationDependencies.put(configuration,
new ResolvedConfigurationDependencies(projectDependencyIds, configuration.getResolvedConfiguration()));
new ResolvedConfigurationDependencies(localProjectIds, configuration.getResolvedConfiguration()));
}

DependencyDescriptor find(File file) {
Expand Down
Expand Up @@ -149,6 +149,7 @@ void multiModuleImplicitLayers() throws IOException {
assertThat(jarFile.getEntry(layerToolsJar)).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/charlie-1.2.3.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-jcl-5.2.5.RELEASE.jar")).isNotNull();
Expand All @@ -171,7 +172,8 @@ void multiModuleImplicitLayers() throws IOException {
assertThat(indexedLayers.get("spring-boot-loader")).containsExactly("org/");
assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies);
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");
assertThat(listLayers.task(":listLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
String listLayersOutput = listLayers.getOutput();
Expand Down Expand Up @@ -244,6 +246,7 @@ void multiModuleCustomLayers() throws IOException {
assertThat(jarFile.getEntry(layerToolsJar)).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/charlie-1.2.3.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-jcl-5.2.5.RELEASE.jar")).isNotNull();
Expand All @@ -259,6 +262,7 @@ void multiModuleCustomLayers() throws IOException {
Set<String> expectedSubprojectDependencies = new TreeSet<>();
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/charlie-1.2.3.jar");
Set<String> expectedDependencies = new TreeSet<>();
expectedDependencies.add("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar");
expectedDependencies.add("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar");
Expand Down Expand Up @@ -348,7 +352,7 @@ private boolean isInIndex(List<String> index, String file) {
private void writeSettingsGradle() {
try (PrintWriter writer = new PrintWriter(
new FileWriter(new File(this.gradleBuild.getProjectDir(), "settings.gradle")))) {
writer.println("include 'alpha', 'bravo'");
writer.println("include 'alpha', 'bravo', 'charlie'");
}
catch (IOException ex) {
throw new RuntimeException(ex);
Expand Down
Expand Up @@ -323,7 +323,7 @@ private void addContent() throws IOException {
return null;
}).given(resolvableDependencies).afterResolve(any(Action.class));
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) {
Expand Down
Expand Up @@ -7,6 +7,11 @@ subprojects {
apply plugin: 'java'
group = 'org.example.projects'
version = '1.2.3'
if (it.name == 'bravo') {
dependencies {
implementation(project(':charlie'))
}
}
}

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

bootJar {
Expand Down

0 comments on commit 45e6c12

Please sign in to comment.