Implement extract and list-layers command

Adds a new jarmode called 'tools'. This provides two commands,
'extract' and 'list-layers'. list-layers is the same as list from
the layertools.

extract is able to extract the JAR in four different modes:

- CDS compatible extraction with libraries in a lib folder and a runner
.jar
- CDS compatible as above, but with layers
- Launcher based
- Launcher based with layers. This is essentially the same as extract
  from the layertools

The commands in layertools have been deprecated in favor of the commands
in 'tools'.

This also changes the behavior of layers.enabled from the Gradle and
Maven plugin: before this commit, layers.enabled prevents the inclusion
of the layer index file as well as the layertools JAR.
After this commit, layers.enabled only prevents the inclusion of the
layer index file.

layer.includeLayerTools have been deprecated in favor of includeTools,
and the layertools JAR has been renamed to tools.

Closes gh-38276
This commit is contained in:
Moritz Halbritter
2024-02-09 15:04:46 +01:00
parent 2c4fb5baaa
commit 793aca60d2
121 changed files with 2780 additions and 612 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@@ -66,6 +66,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Andy Wilkinson
* @author Madhura Bhave
* @author Scott Frederick
* @author Moritz Halbritter
*/
abstract class AbstractBootArchiveIntegrationTests {
@@ -332,6 +333,18 @@ abstract class AbstractBootArchiveIntegrationTests {
.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
}
@TestTemplate
void notUpToDateWhenBuiltWithToolsAndThenWithoutTools() {
assertThat(this.gradleBuild.scriptProperty("includeTools", "")
.build(this.taskName)
.task(":" + this.taskName)
.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.scriptProperty("includeTools", "includeTools = false")
.build(this.taskName)
.task(":" + this.taskName)
.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
}
@TestTemplate
void layersWithCustomSourceSet() {
assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome())
@@ -345,7 +358,7 @@ abstract class AbstractBootArchiveIntegrationTests {
assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome())
.isEqualTo(TaskOutcome.SUCCESS);
Map<String, List<String>> indexedLayers;
String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName();
String layerToolsJar = this.libPath + JarModeLibrary.TOOLS.getName();
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
assertThat(jarFile.getEntry(this.libPath + "commons-lang3-3.9.jar")).isNotNull();
@@ -397,7 +410,7 @@ abstract class AbstractBootArchiveIntegrationTests {
assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome())
.isEqualTo(TaskOutcome.SUCCESS);
Map<String, List<String>> indexedLayers;
String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName();
String layerToolsJar = this.libPath + JarModeLibrary.TOOLS.getName();
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
assertThat(jarFile.getEntry(this.libPath + "alpha-1.2.3.jar")).isNotNull();
@@ -443,7 +456,7 @@ abstract class AbstractBootArchiveIntegrationTests {
BuildResult build = this.gradleBuild.build(this.taskName);
assertThat(build.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
Map<String, List<String>> indexedLayers;
String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName();
String layerToolsJar = this.libPath + JarModeLibrary.TOOLS.getName();
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
assertThat(jarFile.getEntry(this.libPath + "commons-lang3-3.9.jar")).isNotNull();
@@ -490,7 +503,7 @@ abstract class AbstractBootArchiveIntegrationTests {
BuildResult build = this.gradleBuild.build(this.taskName);
assertThat(build.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
Map<String, List<String>> indexedLayers;
String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName();
String layerToolsJar = this.libPath + JarModeLibrary.TOOLS.getName();
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
assertThat(jarFile.getEntry(this.libPath + "alpha-1.2.3.jar")).isNotNull();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@@ -80,6 +80,7 @@ import static org.mockito.Mockito.mock;
* @param <T> the type of the concrete BootArchive implementation
* @author Andy Wilkinson
* @author Scott Frederick
* @author Moritz Halbritter
*/
abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
@@ -496,7 +497,7 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Lib")).isEqualTo(this.libPath);
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Layers-Index"))
.isEqualTo(this.indexPath + "layers.idx");
assertThat(getEntryNames(jarFile)).contains(this.libPath + JarModeLibrary.LAYER_TOOLS.getName());
assertThat(getEntryNames(jarFile)).contains(this.libPath + JarModeLibrary.TOOLS.getName());
}
}
@@ -530,7 +531,7 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
List<String> index = entryLines(jarFile, this.indexPath + "layers.idx");
assertThat(getLayerNames(index)).containsExactly("dependencies", "spring-boot-loader",
"snapshot-dependencies", "application");
String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName();
String layerToolsJar = this.libPath + JarModeLibrary.TOOLS.getName();
List<String> expected = new ArrayList<>();
expected.add("- \"dependencies\":");
expected.add(" - \"" + this.libPath + "first-library.jar\"");
@@ -584,7 +585,7 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
List<String> index = entryLines(jarFile, this.indexPath + "layers.idx");
assertThat(getLayerNames(index)).containsExactly("my-deps", "my-internal-deps", "my-snapshot-deps",
"resources", "application");
String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName();
String layerToolsJar = this.libPath + JarModeLibrary.TOOLS.getName();
List<String> expected = new ArrayList<>();
expected.add("- \"my-deps\":");
expected.add(" - \"" + layerToolsJar + "\"");
@@ -614,15 +615,32 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
@Test
void whenArchiveIsLayeredThenLayerToolsAreAddedToTheJar() throws IOException {
List<String> entryNames = getEntryNames(createLayeredJar());
assertThat(entryNames).contains(this.libPath + JarModeLibrary.LAYER_TOOLS.getName());
assertThat(entryNames).contains(this.libPath + JarModeLibrary.TOOLS.getName());
}
@Test
void shouldAddToolsToTheJar() throws IOException {
this.task.getMainClass().set("com.example.Main");
executeTask();
List<String> entryNames = getEntryNames(this.task.getArchiveFile().get().getAsFile());
assertThat(entryNames).isNotEmpty().contains(this.libPath + JarModeLibrary.TOOLS.getName());
}
@Test
@SuppressWarnings("removal")
void whenArchiveIsLayeredAndIncludeLayerToolsIsFalseThenLayerToolsAreNotAddedToTheJar() throws IOException {
List<String> entryNames = getEntryNames(
createLayeredJar((configuration) -> configuration.getIncludeLayerTools().set(false)));
assertThat(entryNames).isNotEmpty()
.doesNotContain(this.indexPath + "layers/dependencies/lib/spring-boot-jarmode-layertools.jar");
assertThat(entryNames).isNotEmpty().doesNotContain(this.libPath + JarModeLibrary.TOOLS.getName());
}
@Test
void whenIncludeToolsIsFalseThenToolsAreNotAddedToTheJar() throws IOException {
this.task.getIncludeTools().set(false);
this.task.getMainClass().set("com.example.Main");
executeTask();
List<String> entryNames = getEntryNames(this.task.getArchiveFile().get().getAsFile());
assertThat(entryNames).isNotEmpty().doesNotContain(this.libPath + JarModeLibrary.TOOLS.getName());
}
protected File jarFile(String name) throws IOException {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@@ -67,7 +67,7 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
assertThat(output).containsPattern("1\\. .*classes");
assertThat(output).containsPattern("2\\. .*library-1.0-SNAPSHOT.jar");
assertThat(output).containsPattern("3\\. .*commons-lang3-3.9.jar");
assertThat(output).containsPattern("4\\. .*spring-boot-jarmode-layertools.*.jar");
assertThat(output).containsPattern("4\\. .*spring-boot-jarmode-tools.*.jar");
assertThat(output).doesNotContain("5. ");
}
@@ -77,7 +77,7 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
BuildResult result = this.gradleBuild.build("launch");
String output = result.getOutput();
assertThat(output).containsPattern("1\\. .*classes");
assertThat(output).containsPattern("2\\. .*spring-boot-jarmode-layertools.*.jar");
assertThat(output).containsPattern("2\\. .*spring-boot-jarmode-tools.*.jar");
assertThat(output).containsPattern("3\\. .*library-1.0-SNAPSHOT.jar");
assertThat(output).containsPattern("4\\. .*commons-lang3-3.9.jar");
assertThat(output).doesNotContain("5. ");