Put project deps in app layer and make customization easier

Previously, when building a layered jar with Gradle, project
dependencies were treated the same as any other dependency, being
included in the dependencies or snapshot dependencies layer based
on their version.

This commit updates the default layering when using Gradle to include
project dependencies in the application layer by default. The DSL has
also been updated to allow their layer to be customized using new
includeProjectDependencies() and excludeProjectDependencies() methods
rather than relying on including and excluding them via a
group:artifact:version pattern.

Closes gh-23431
This commit is contained in:
Andy Wilkinson
2020-10-15 12:19:18 +01:00
parent ad6ea94112
commit 84f96033c5
11 changed files with 292 additions and 35 deletions

View File

@@ -140,6 +140,50 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
assertExtractedLayers(layerNames, indexedLayers);
}
@TestTemplate
void multiModuleImplicitLayers() throws IOException {
writeSettingsGradle();
writeMainClass();
writeResource();
assertThat(this.gradleBuild.build("bootJar").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/alpha-1.2.3.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/bravo-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/library-1.0-SNAPSHOT.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/classes/example/Main.class")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/classes/static/file.txt")).isNotNull();
indexedLayers = readLayerIndex(jarFile);
}
List<String> layerNames = Arrays.asList("dependencies", "spring-boot-loader", "snapshot-dependencies",
"application");
assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames);
Set<String> expectedDependencies = new TreeSet<>();
expectedDependencies.add("BOOT-INF/lib/commons-lang3-3.9.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");
Set<String> expectedSnapshotDependencies = new TreeSet<>();
expectedSnapshotDependencies.add("BOOT-INF/lib/library-1.0-SNAPSHOT.jar");
(layerToolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(layerToolsJar);
assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies);
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/");
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);
}
@TestTemplate
void customLayers() throws IOException {
writeMainClass();

View File

@@ -29,8 +29,11 @@ import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import org.gradle.api.Action;
import org.gradle.api.DomainObjectSet;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.artifacts.ResolvableDependencies;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.artifacts.ResolvedConfiguration;
@@ -41,6 +44,7 @@ import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
import org.junit.jupiter.api.Test;
import org.springframework.boot.loader.tools.JarModeLibrary;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -56,6 +60,7 @@ import static org.mockito.Mockito.mock;
* @author Scott Frederick
* @author Paddy Drury
*/
@ClassPathExclusions("kotlin-daemon-client-*")
class BootJarTests extends AbstractBootArchiveTests<BootJar> {
BootJarTests() {
@@ -307,6 +312,10 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
given(configuration.getResolvedConfiguration()).willReturn(resolvedConfiguration);
ResolvableDependencies resolvableDependencies = mock(ResolvableDependencies.class);
given(configuration.getIncoming()).willReturn(resolvableDependencies);
DependencySet dependencies = mock(DependencySet.class);
DomainObjectSet<ProjectDependency> projectDependencies = mock(DomainObjectSet.class);
given(dependencies.withType(ProjectDependency.class)).willReturn(projectDependencies);
given(configuration.getAllDependencies()).willReturn(dependencies);
willAnswer((invocation) -> {
invocation.getArgument(0, Action.class).execute(resolvableDependencies);
return null;