Skip to content

Allow project dependencies to be assigned to layers by their coordinates #21207

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

Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
import java.util.Map;
import java.util.Set;

import org.gradle.api.artifacts.ArtifactCollection;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.artifacts.ResolvedConfiguration;
import org.gradle.api.artifacts.component.ComponentIdentifier;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
import org.gradle.api.file.FileCopyDetails;
import org.gradle.api.specs.Spec;

Expand All @@ -43,6 +45,7 @@
* @author Madhura Bhave
* @author Scott Frederick
* @author Phillip Webb
* @author Paddy Drury
* @see BootZipCopyAction
*/
class LayerResolver {
Expand Down Expand Up @@ -110,7 +113,7 @@ private void processConfiguration(Configuration configuration) {
if (configuration.isCanBeResolved()
&& !DEPRECATED_FOR_RESOLUTION_CONFIGURATIONS.contains(configuration.getName())) {
this.configurationDependencies.put(configuration,
new ResolvedConfigurationDependencies(configuration.getIncoming().getArtifacts()));
new ResolvedConfigurationDependencies(configuration.getResolvedConfiguration()));
}
}

Expand All @@ -133,14 +136,15 @@ private static class ResolvedConfigurationDependencies {

private final Map<File, LibraryCoordinates> artifactCoordinates = new LinkedHashMap<>();

ResolvedConfigurationDependencies(ArtifactCollection resolvedDependencies) {
if (resolvedDependencies != null) {
for (ResolvedArtifactResult resolvedArtifact : resolvedDependencies.getArtifacts()) {
ResolvedConfigurationDependencies(ResolvedConfiguration resolvedConfiguration) {
if (resolvedConfiguration != null) {
for (ResolvedArtifact resolvedArtifact : resolvedConfiguration.getResolvedArtifacts()) {
ComponentIdentifier identifier = resolvedArtifact.getId().getComponentIdentifier();
if (identifier instanceof ModuleComponentIdentifier) {
if (identifier instanceof ModuleComponentIdentifier
|| identifier instanceof ProjectComponentIdentifier) {
this.artifactCoordinates.put(resolvedArtifact.getFile(),
new ModuleComponentIdentifierLibraryCoordinates(
(ModuleComponentIdentifier) identifier));
new ModuleVersionIdentifierLibraryCoordinates(
resolvedArtifact.getModuleVersion().getId()));
}
}
}
Expand All @@ -153,13 +157,13 @@ LibraryCoordinates find(File file) {
}

/**
* Adapts a {@link ModuleComponentIdentifier} to {@link LibraryCoordinates}.
* Adapts a {@link ModuleVersionIdentifier} to {@link LibraryCoordinates}.
*/
private static class ModuleComponentIdentifierLibraryCoordinates implements LibraryCoordinates {
private static class ModuleVersionIdentifierLibraryCoordinates implements LibraryCoordinates {

private final ModuleComponentIdentifier identifier;
private final ModuleVersionIdentifier identifier;

ModuleComponentIdentifierLibraryCoordinates(ModuleComponentIdentifier identifier) {
ModuleVersionIdentifierLibraryCoordinates(ModuleVersionIdentifier identifier) {
this.identifier = identifier;
}

Expand All @@ -170,7 +174,7 @@ public String getGroupId() {

@Override
public String getArtifactId() {
return this.identifier.getModule();
return this.identifier.getName();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
*
* @author Andy Wilkinson
* @author Madhura Bhave
* @author Paddy Drury
*/
class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {

Expand Down Expand Up @@ -176,6 +177,64 @@ void customLayers() throws IOException {
assertExtractedLayers(layerNames, indexedLayers);
}

@TestTemplate
void projectDependenciesCanBeIncludedInCustomLayer() throws IOException {
writeSettingsGradle();
writeMainClass();
writeResource();
BuildResult build = this.gradleBuild.build("bootJar");
assertThat(build.task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
Map<String, List<String>> indexedLayers;
String layerToolsJar = "BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName();
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/foo-1.2.3.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/bar-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();
assertThat(jarFile.getEntry("BOOT-INF/lib/commons-io-2.7-SNAPSHOT.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/classes/example/Main.class")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/classes/static/file.txt")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/layers.idx")).isNotNull();
indexedLayers = readLayerIndex(jarFile);
}
List<String> layerNames = Arrays.asList("dependencies", "commons-dependencies", "snapshot-dependencies",
"subproject-dependencies", "static", "app");
assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames);
Set<String> expectedSubprojectDependencies = new TreeSet<>();
expectedSubprojectDependencies.add("BOOT-INF/lib/foo-1.2.3.jar");
expectedSubprojectDependencies.add("BOOT-INF/lib/bar-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");
List<String> expectedSnapshotDependencies = new ArrayList<>();
expectedSnapshotDependencies.add("BOOT-INF/lib/commons-io-2.7-SNAPSHOT.jar");
(layerToolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(layerToolsJar);
assertThat(indexedLayers.get("subproject-dependencies"))
.containsExactlyElementsOf(expectedSubprojectDependencies);
assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies);
assertThat(indexedLayers.get("commons-dependencies")).containsExactly("BOOT-INF/lib/commons-lang3-3.9.jar");
assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies);
assertThat(indexedLayers.get("static")).containsExactly("BOOT-INF/classes/static/");
List<String> appLayer = new ArrayList<>(indexedLayers.get("app"));
Set<String> nonLoaderEntries = new TreeSet<>();
nonLoaderEntries.add("BOOT-INF/classes/example/");
nonLoaderEntries.add("BOOT-INF/classpath.idx");
nonLoaderEntries.add("BOOT-INF/layers.idx");
nonLoaderEntries.add("META-INF/");
assertThat(appLayer).containsSubsequence(nonLoaderEntries);
appLayer.removeAll(nonLoaderEntries);
assertThat(appLayer).containsExactly("org/");
BuildResult listLayers = this.gradleBuild.build("listLayers");
assertThat(listLayers.task(":listLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
String listLayersOutput = listLayers.getOutput();
assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames);
BuildResult extractLayers = this.gradleBuild.build("extractLayers");
assertThat(extractLayers.task(":extractLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertExtractedLayers(layerNames, indexedLayers);
}

private void assertExtractedLayers(List<String> layerNames, Map<String, List<String>> indexedLayers)
throws IOException {
Map<String, List<String>> extractedLayers = readExtractedLayers(this.gradleBuild.getProjectDir(), layerNames);
Expand All @@ -201,6 +260,16 @@ private boolean isInIndex(List<String> index, String file) {
return false;
}

private void writeSettingsGradle() {
File settings = new File(this.gradleBuild.getProjectDir(), "settings.gradle");
try (PrintWriter writer = new PrintWriter(new FileWriter(settings))) {
writer.println("include 'foo', 'bar'");
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}

private void writeMainClass() {
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example");
examplePackage.mkdirs();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@
import java.util.zip.ZipEntry;

import org.gradle.api.Action;
import org.gradle.api.artifacts.ArtifactCollection;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ResolvableDependencies;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.artifacts.ResolvedConfiguration;
import org.gradle.api.artifacts.ResolvedModuleVersion;
import org.gradle.api.artifacts.component.ComponentArtifactIdentifier;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
import org.junit.jupiter.api.Test;

import org.springframework.boot.gradle.tasks.bundling.BootJarTests.TestBootJar;
Expand All @@ -51,6 +53,7 @@
* @author Andy Wilkinson
* @author Madhura Bhave
* @author Scott Frederick
* @author Paddy Drury
*/
class BootJarTests extends AbstractBootArchiveTests<TestBootJar> {

Expand Down Expand Up @@ -99,22 +102,26 @@ void whenJarIsLayeredThenLayersIndexIsPresentAndCorrect() throws IOException {
try (JarFile jarFile = new JarFile(createLayeredJar())) {
List<String> entryNames = getEntryNames(jarFile);
assertThat(entryNames).contains("BOOT-INF/lib/first-library.jar", "BOOT-INF/lib/second-library.jar",
"BOOT-INF/lib/third-library-SNAPSHOT.jar", "BOOT-INF/classes/com/example/Application.class",
"BOOT-INF/classes/application.properties", "BOOT-INF/classes/static/test.css");
"BOOT-INF/lib/third-library-SNAPSHOT.jar", "BOOT-INF/lib/first-project-library.jar",
"BOOT-INF/lib/second-project-library-SNAPSHOT.jar",
"BOOT-INF/classes/com/example/Application.class", "BOOT-INF/classes/application.properties",
"BOOT-INF/classes/static/test.css");
List<String> index = entryLines(jarFile, "BOOT-INF/layers.idx");
assertThat(getLayerNames(index)).containsExactly("dependencies", "spring-boot-loader",
"snapshot-dependencies", "application");
String layerToolsJar = "BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName();
List<String> expected = new ArrayList<>();
expected.add("- \"dependencies\":");
expected.add(" - \"BOOT-INF/lib/first-library.jar\"");
expected.add(" - \"BOOT-INF/lib/first-project-library.jar\"");
expected.add(" - \"BOOT-INF/lib/second-library.jar\"");
if (!layerToolsJar.contains("SNAPSHOT")) {
expected.add(" - \"" + layerToolsJar + "\"");
}
expected.add("- \"spring-boot-loader\":");
expected.add(" - \"org/\"");
expected.add("- \"snapshot-dependencies\":");
expected.add(" - \"BOOT-INF/lib/second-project-library-SNAPSHOT.jar\"");
if (layerToolsJar.contains("SNAPSHOT")) {
expected.add(" - \"" + layerToolsJar + "\"");
}
Expand Down Expand Up @@ -145,8 +152,10 @@ void whenJarIsLayeredWithCustomStrategiesThenLayersIndexIsPresentAndCorrect() th
try (JarFile jarFile = new JarFile(jar)) {
List<String> entryNames = getEntryNames(jar);
assertThat(entryNames).contains("BOOT-INF/lib/first-library.jar", "BOOT-INF/lib/second-library.jar",
"BOOT-INF/lib/third-library-SNAPSHOT.jar", "BOOT-INF/classes/com/example/Application.class",
"BOOT-INF/classes/application.properties", "BOOT-INF/classes/static/test.css");
"BOOT-INF/lib/third-library-SNAPSHOT.jar", "BOOT-INF/lib/first-project-library.jar",
"BOOT-INF/lib/second-project-library-SNAPSHOT.jar",
"BOOT-INF/classes/com/example/Application.class", "BOOT-INF/classes/application.properties",
"BOOT-INF/classes/static/test.css");
List<String> index = entryLines(jarFile, "BOOT-INF/layers.idx");
assertThat(getLayerNames(index)).containsExactly("my-deps", "my-internal-deps", "my-snapshot-deps",
"resources", "application");
Expand All @@ -156,8 +165,10 @@ void whenJarIsLayeredWithCustomStrategiesThenLayersIndexIsPresentAndCorrect() th
expected.add(" - \"" + layerToolsJar + "\"");
expected.add("- \"my-internal-deps\":");
expected.add(" - \"BOOT-INF/lib/first-library.jar\"");
expected.add(" - \"BOOT-INF/lib/first-project-library.jar\"");
expected.add(" - \"BOOT-INF/lib/second-library.jar\"");
expected.add("- \"my-snapshot-deps\":");
expected.add(" - \"BOOT-INF/lib/second-project-library-SNAPSHOT.jar\"");
expected.add(" - \"BOOT-INF/lib/third-library-SNAPSHOT.jar\"");
expected.add("- \"resources\":");
expected.add(" - \"BOOT-INF/classes/static/\"");
Expand All @@ -179,14 +190,19 @@ void jarsInLibAreStored() throws IOException {
assertThat(jarFile.getEntry("BOOT-INF/lib/second-library.jar").getMethod()).isEqualTo(ZipEntry.STORED);
assertThat(jarFile.getEntry("BOOT-INF/lib/third-library-SNAPSHOT.jar").getMethod())
.isEqualTo(ZipEntry.STORED);
assertThat(jarFile.getEntry("BOOT-INF/lib/first-project-library.jar").getMethod())
.isEqualTo(ZipEntry.STORED);
assertThat(jarFile.getEntry("BOOT-INF/lib/second-project-library-SNAPSHOT.jar").getMethod())
.isEqualTo(ZipEntry.STORED);
}
}

@Test
void whenJarIsLayeredClasspathIndexPointsToLayeredLibs() throws IOException {
try (JarFile jarFile = new JarFile(createLayeredJar())) {
assertThat(entryLines(jarFile, "BOOT-INF/classpath.idx")).containsExactly("- \"first-library.jar\"",
"- \"second-library.jar\"", "- \"third-library-SNAPSHOT.jar\"");
"- \"second-library.jar\"", "- \"third-library-SNAPSHOT.jar\"", "- \"first-project-library.jar\"",
"- \"second-project-library-SNAPSHOT.jar\"");
}
}

Expand All @@ -209,7 +225,8 @@ void classpathIndexPointsToBootInfLibs() throws IOException {
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classpath-Index"))
.isEqualTo("BOOT-INF/classpath.idx");
assertThat(entryLines(jarFile, "BOOT-INF/classpath.idx")).containsExactly("- \"first-library.jar\"",
"- \"second-library.jar\"", "- \"third-library-SNAPSHOT.jar\"");
"- \"second-library.jar\"", "- \"third-library-SNAPSHOT.jar\"", "- \"first-project-library.jar\"",
"- \"second-project-library-SNAPSHOT.jar\"");
}
}

Expand Down Expand Up @@ -251,34 +268,55 @@ private void addContent() throws IOException {
File css = new File(staticResources, "test.css");
css.createNewFile();
bootJar.classpath(classesJavaMain, resourcesMain, jarFile("first-library.jar"), jarFile("second-library.jar"),
jarFile("third-library-SNAPSHOT.jar"));
Set<ResolvedArtifactResult> artifacts = new LinkedHashSet<>();
jarFile("third-library-SNAPSHOT.jar"), jarFile("first-project-library.jar"),
jarFile("second-project-library-SNAPSHOT.jar"));
Set<ResolvedArtifact> artifacts = new LinkedHashSet<>();
artifacts.add(mockLibraryArtifact("first-library.jar", "com.example", "first-library", "1.0.0"));
artifacts.add(mockLibraryArtifact("second-library.jar", "com.example", "second-library", "1.0.0"));
artifacts.add(
mockLibraryArtifact("third-library-SNAPSHOT.jar", "com.example", "third-library", "1.0.0.SNAPSHOT"));
ArtifactCollection resolvedDependencies = mock(ArtifactCollection.class);
given(resolvedDependencies.getArtifacts()).willReturn(artifacts);
ResolvableDependencies resolvableDependencies = mock(ResolvableDependencies.class);
given(resolvableDependencies.getArtifacts()).willReturn(resolvedDependencies);
artifacts
.add(mockProjectArtifact("first-project-library.jar", "com.example", "first-project-library", "1.0.0"));
artifacts.add(mockProjectArtifact("second-project-library-SNAPSHOT.jar", "com.example",
"second-project-library", "1.0.0.SNAPSHOT"));
ResolvedConfiguration resolvedConfiguration = mock(ResolvedConfiguration.class);
given(resolvedConfiguration.getResolvedArtifacts()).willReturn(artifacts);
Configuration configuration = mock(Configuration.class);
given(configuration.isCanBeResolved()).willReturn(true);
given(configuration.getIncoming()).willReturn(resolvableDependencies);
given(configuration.getResolvedConfiguration()).willReturn(resolvedConfiguration);
bootJar.setConfiguration(Collections.singleton(configuration));
}

private ResolvedArtifactResult mockLibraryArtifact(String fileName, String group, String module, String version) {
ModuleComponentIdentifier identifier = mock(ModuleComponentIdentifier.class);
given(identifier.getGroup()).willReturn(group);
given(identifier.getModule()).willReturn(module);
given(identifier.getVersion()).willReturn(version);
private ResolvedArtifact mockLibraryArtifact(String fileName, String group, String module, String version) {
ModuleComponentIdentifier moduleComponentIdentifier = mock(ModuleComponentIdentifier.class);
ComponentArtifactIdentifier libraryArtifactId = mock(ComponentArtifactIdentifier.class);
given(libraryArtifactId.getComponentIdentifier()).willReturn(identifier);
ResolvedArtifactResult libraryArtifact = mock(ResolvedArtifactResult.class);
given(libraryArtifactId.getComponentIdentifier()).willReturn(moduleComponentIdentifier);
ResolvedArtifact libraryArtifact = mockArtifact(fileName, group, module, version);
given(libraryArtifact.getId()).willReturn(libraryArtifactId);
return libraryArtifact;
}

private ResolvedArtifact mockProjectArtifact(String fileName, String group, String module, String version) {
ProjectComponentIdentifier projectComponentIdentifier = mock(ProjectComponentIdentifier.class);
ComponentArtifactIdentifier projectArtifactId = mock(ComponentArtifactIdentifier.class);
given(projectArtifactId.getComponentIdentifier()).willReturn(projectComponentIdentifier);
ResolvedArtifact projectArtifact = mockArtifact(fileName, group, module, version);
given(projectArtifact.getId()).willReturn(projectArtifactId);
return projectArtifact;
}

private ResolvedArtifact mockArtifact(String fileName, String group, String module, String version) {
ModuleVersionIdentifier moduleVersionIdentifier = mock(ModuleVersionIdentifier.class);
given(moduleVersionIdentifier.getGroup()).willReturn(group);
given(moduleVersionIdentifier.getName()).willReturn(module);
given(moduleVersionIdentifier.getVersion()).willReturn(version);
ResolvedModuleVersion moduleVersion = mock(ResolvedModuleVersion.class);
given(moduleVersion.getId()).willReturn(moduleVersionIdentifier);
ResolvedArtifact libraryArtifact = mock(ResolvedArtifact.class);
File file = new File(this.temp, fileName).getAbsoluteFile();
System.out.println(file);
given(libraryArtifact.getFile()).willReturn(file);
given(libraryArtifact.getId()).willReturn(libraryArtifactId);
given(libraryArtifact.getModuleVersion()).willReturn(moduleVersion);
return libraryArtifact;
}

Expand Down
Loading