Adjust defaults for ExtractCommand

It now extracts the contents of the JAR in a folder named after the JAR
without the extension. It now also checks if the folder is empty.
There's a new --force option to skip those checks.

The "runner.jar" is now named like the uber JAR from which the
extraction has been started.

See gh-38276
This commit is contained in:
Moritz Halbritter 2024-03-12 11:17:44 +01:00
parent 1e402f9f41
commit 9411a4ce99
18 changed files with 229 additions and 132 deletions

View File

@ -45,5 +45,5 @@ task listLayers(type: JavaExec) {
task extractLayers(type: JavaExec) {
classpath = bootJar.outputs.files
systemProperties = [ "jarmode": "tools" ]
args "extract", "--layers", "--launcher"
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
}

View File

@ -28,5 +28,5 @@ task listLayers(type: JavaExec) {
task extractLayers(type: JavaExec) {
classpath = bootJar.outputs.files
systemProperties = [ "jarmode": "tools" ]
args "extract", "--layers", "--launcher"
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
}

View File

@ -31,5 +31,5 @@ task listLayers(type: JavaExec) {
task extractLayers(type: JavaExec) {
classpath = bootJar.outputs.files
systemProperties = [ "jarmode": "tools" ]
args "extract", "--layers", "--launcher"
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
}

View File

@ -62,5 +62,5 @@ task listLayers(type: JavaExec) {
task extractLayers(type: JavaExec) {
classpath = bootJar.outputs.files
systemProperties = [ "jarmode": "tools" ]
args "extract", "--layers", "--launcher"
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
}

View File

@ -40,5 +40,5 @@ task listLayers(type: JavaExec) {
task extractLayers(type: JavaExec) {
classpath = bootJar.outputs.files
systemProperties = [ "jarmode": "tools" ]
args "extract", "--layers", "--launcher"
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
}

View File

@ -46,5 +46,5 @@ task listLayers(type: JavaExec) {
task extractLayers(type: JavaExec) {
classpath = bootWar.outputs.files
systemProperties = [ "jarmode": "tools" ]
args "extract", "--layers", "--launcher"
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
}

View File

@ -29,5 +29,5 @@ task listLayers(type: JavaExec) {
task extractLayers(type: JavaExec) {
classpath = bootWar.outputs.files
systemProperties = [ "jarmode": "tools" ]
args "extract", "--layers", "--launcher"
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
}

View File

@ -32,5 +32,5 @@ task listLayers(type: JavaExec) {
task extractLayers(type: JavaExec) {
classpath = bootWar.outputs.files
systemProperties = [ "jarmode": "tools" ]
args "extract", "--layers", "--launcher"
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
}

View File

@ -63,5 +63,5 @@ task listLayers(type: JavaExec) {
task extractLayers(type: JavaExec) {
classpath = bootWar.outputs.files
systemProperties = [ "jarmode": "tools" ]
args "extract", "--layers", "--launcher"
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
}

View File

@ -41,5 +41,5 @@ task listLayers(type: JavaExec) {
task extractLayers(type: JavaExec) {
classpath = bootWar.outputs.files
systemProperties = [ "jarmode": "tools" ]
args "extract", "--layers", "--launcher"
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
}

View File

@ -27,6 +27,7 @@ import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.FileTime;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
@ -52,7 +53,7 @@ class ExtractCommand extends Command {
/**
* Option to create a launcher.
*/
static final Option LAUNCHER_OPTION = Option.of("launcher", null, "Whether to extract the Spring Boot launcher");
static final Option LAUNCHER_OPTION = Option.flag("launcher", "Whether to extract the Spring Boot launcher");
/**
* Option to extract layers.
@ -63,13 +64,18 @@ class ExtractCommand extends Command {
* Option to specify the destination to write to.
*/
static final Option DESTINATION_OPTION = Option.of("destination", "string",
"Directory to extract files to. Defaults to the current working directory");
"Directory to extract files to. Defaults to a directory named after the uber JAR (without the file extension)");
/**
* Option to ignore non-empty directory error.
*/
static final Option FORCE_OPTION = Option.flag("force", "Whether to ignore non-empty directories, extract anyway");
private static final Option LIBRARIES_DIRECTORY_OPTION = Option.of("libraries", "string",
"Name of the libraries directory. Only applicable when not using --launcher. Defaults to lib/");
private static final Option RUNNER_FILENAME_OPTION = Option.of("runner-filename", "string",
"Name of the runner JAR file. Only applicable when not using --launcher. Defaults to runner.jar");
private static final Option APPLICATION_FILENAME_OPTION = Option.of("application-filename", "string",
"Name of the application JAR file. Only applicable when not using --launcher. Defaults to the uber JAR filename");
private final Context context;
@ -81,7 +87,8 @@ class ExtractCommand extends Command {
ExtractCommand(Context context, Layers layers) {
super("extract", "Extract the contents from the jar", Options.of(LAUNCHER_OPTION, LAYERS_OPTION,
DESTINATION_OPTION, LIBRARIES_DIRECTORY_OPTION, RUNNER_FILENAME_OPTION), Parameters.none());
DESTINATION_OPTION, LIBRARIES_DIRECTORY_OPTION, APPLICATION_FILENAME_OPTION, FORCE_OPTION),
Parameters.none());
this.context = context;
this.layers = layers;
}
@ -90,7 +97,8 @@ class ExtractCommand extends Command {
void run(PrintStream out, Map<Option, String> options, List<String> parameters) {
try {
checkJarCompatibility();
File destination = getWorkingDirectory(options);
File destination = getDestination(options);
checkDirectoryIsEmpty(options, destination);
FileResolver fileResolver = getFileResolver(destination, options);
fileResolver.createDirectories();
if (options.containsKey(LAUNCHER_OPTION)) {
@ -99,7 +107,7 @@ class ExtractCommand extends Command {
else {
JarStructure jarStructure = getJarStructure();
extractLibraries(fileResolver, jarStructure, options);
createRunner(jarStructure, fileResolver, options);
createApplication(jarStructure, fileResolver, options);
}
}
catch (IOException ex) {
@ -108,15 +116,36 @@ class ExtractCommand extends Command {
catch (LayersNotEnabledException ex) {
printError(out, "Layers are not enabled");
}
catch (AbortException ex) {
printError(out, ex.getMessage());
}
}
private static void checkDirectoryIsEmpty(Map<Option, String> options, File destination) {
if (options.containsKey(FORCE_OPTION)) {
return;
}
if (!destination.exists()) {
return;
}
if (!destination.isDirectory()) {
throw new AbortException(destination.getAbsoluteFile() + " already exists and is not a directory");
}
File[] files = destination.listFiles();
if (files != null && files.length > 0) {
throw new AbortException(destination.getAbsoluteFile() + " already exists and is not empty");
}
}
private void checkJarCompatibility() throws IOException {
File file = this.context.getArchiveFile();
try (ZipInputStream stream = new ZipInputStream(new FileInputStream(file))) {
ZipEntry entry = stream.getNextEntry();
Assert.state(entry != null,
() -> "File '%s' is not compatible; ensure jar file is valid and launch script is not enabled"
.formatted(file));
if (entry == null) {
throw new AbortException(
"File '%s' is not compatible; ensure jar file is valid and launch script is not enabled"
.formatted(file));
}
}
}
@ -149,20 +178,31 @@ class ExtractCommand extends Command {
}
private FileResolver getFileResolver(File destination, Map<Option, String> options) {
String runnerFilename = getRunnerFilename(options);
String applicationFilename = getApplicationFilename(options);
if (!options.containsKey(LAYERS_OPTION)) {
return new NoLayersFileResolver(destination, runnerFilename);
return new NoLayersFileResolver(destination, applicationFilename);
}
Layers layers = getLayers();
Set<String> layersToExtract = StringUtils.commaDelimitedListToSet(options.get(LAYERS_OPTION));
return new LayersFileResolver(destination, layers, layersToExtract, runnerFilename);
return new LayersFileResolver(destination, layers, layersToExtract, applicationFilename);
}
private File getWorkingDirectory(Map<Option, String> options) {
private File getDestination(Map<Option, String> options) {
if (options.containsKey(DESTINATION_OPTION)) {
return new File(options.get(DESTINATION_OPTION));
File destination = new File(options.get(DESTINATION_OPTION));
if (destination.isAbsolute()) {
return destination;
}
return new File(this.context.getWorkingDir(), destination.getPath());
}
return this.context.getWorkingDir();
return new File(this.context.getWorkingDir(), stripExtension(this.context.getArchiveFile().getName()));
}
private static String stripExtension(String name) {
if (name.toLowerCase(Locale.ROOT).endsWith(".jar") || name.toLowerCase(Locale.ROOT).endsWith(".war")) {
return name.substring(0, name.length() - 4);
}
return name;
}
private JarStructure getJarStructure() {
@ -199,9 +239,9 @@ class ExtractCommand extends Command {
return Layers.get(this.context);
}
private void createRunner(JarStructure jarStructure, FileResolver fileResolver, Map<Option, String> options)
private void createApplication(JarStructure jarStructure, FileResolver fileResolver, Map<Option, String> options)
throws IOException {
File file = fileResolver.resolveRunner();
File file = fileResolver.resolveApplication();
if (file == null) {
return;
}
@ -221,11 +261,11 @@ class ExtractCommand extends Command {
}
}
private String getRunnerFilename(Map<Option, String> options) {
if (options.containsKey(RUNNER_FILENAME_OPTION)) {
return options.get(RUNNER_FILENAME_OPTION);
private String getApplicationFilename(Map<Option, String> options) {
if (options.containsKey(APPLICATION_FILENAME_OPTION)) {
return options.get(APPLICATION_FILENAME_OPTION);
}
return "runner.jar";
return this.context.getArchiveFile().getName();
}
private static boolean isType(Entry entry, Type type) {
@ -338,11 +378,12 @@ class ExtractCommand extends Command {
File resolve(String originalName, String newName) throws IOException;
/**
* Resolves the file for the runner.
* @return the file for the runner or {@code null} if the runner should be skipped
* Resolves the file for the application.
* @return the file for the application or {@code null} if the application should
* be skipped
* @throws IOException if something went wrong
*/
File resolveRunner() throws IOException;
File resolveApplication() throws IOException;
}
@ -350,11 +391,11 @@ class ExtractCommand extends Command {
private final File directory;
private final String runnerFilename;
private final String applicationFilename;
private NoLayersFileResolver(File directory, String runnerFilename) {
private NoLayersFileResolver(File directory, String applicationFilename) {
this.directory = directory;
this.runnerFilename = runnerFilename;
this.applicationFilename = applicationFilename;
}
@Override
@ -367,8 +408,8 @@ class ExtractCommand extends Command {
}
@Override
public File resolveRunner() throws IOException {
return resolve(this.runnerFilename, this.runnerFilename);
public File resolveApplication() throws IOException {
return resolve(this.applicationFilename, this.applicationFilename);
}
}
@ -381,13 +422,13 @@ class ExtractCommand extends Command {
private final File directory;
private final String runnerFilename;
private final String applicationFilename;
LayersFileResolver(File directory, Layers layers, Set<String> layersToExtract, String runnerFilename) {
LayersFileResolver(File directory, Layers layers, Set<String> layersToExtract, String applicationFilename) {
this.layers = layers;
this.layersToExtract = layersToExtract;
this.directory = directory;
this.runnerFilename = runnerFilename;
this.applicationFilename = applicationFilename;
}
@Override
@ -410,12 +451,12 @@ class ExtractCommand extends Command {
}
@Override
public File resolveRunner() throws IOException {
public File resolveApplication() throws IOException {
String layer = this.layers.getApplicationLayerName();
if (shouldExtractLayer(layer)) {
File directory = getLayerDirectory(layer);
return assertFileIsContainedInDirectory(directory, new File(directory, this.runnerFilename),
this.runnerFilename);
return assertFileIsContainedInDirectory(directory, new File(directory, this.applicationFilename),
this.applicationFilename);
}
return null;
}
@ -433,4 +474,12 @@ class ExtractCommand extends Command {
}
private static final class AbortException extends RuntimeException {
AbortException(String message) {
super(message);
}
}
}

View File

@ -58,11 +58,10 @@ class ExtractLayersCommand extends Command {
@Override
void run(PrintStream out, Map<Option, String> options, List<String> parameters) {
Map<Option, String> rewrittenOptions = new HashMap<>();
if (options.containsKey(DESTINATION_OPTION)) {
rewrittenOptions.put(ExtractCommand.DESTINATION_OPTION, options.get(DESTINATION_OPTION));
}
rewrittenOptions.put(ExtractCommand.DESTINATION_OPTION, options.getOrDefault(DESTINATION_OPTION, "."));
rewrittenOptions.put(ExtractCommand.LAYERS_OPTION, StringUtils.collectionToCommaDelimitedString(parameters));
rewrittenOptions.put(ExtractCommand.LAUNCHER_OPTION, null);
rewrittenOptions.put(ExtractCommand.FORCE_OPTION, null);
this.delegate.run(out, rewrittenOptions, Collections.emptyList());
}

View File

@ -119,46 +119,46 @@ class ExtractCommandTests extends AbstractTests {
class Extract {
@Test
void extractLibrariesAndCreatesRunner() throws IOException {
void extractLibrariesAndCreatesApplication() throws IOException {
run(ExtractCommandTests.this.archive);
List<String> filenames = listFilenames();
assertThat(filenames).contains("lib/dependency-1.jar")
.contains("lib/dependency-2.jar")
.contains("lib/dependency-3-SNAPSHOT.jar")
.contains("runner.jar")
.doesNotContain("org/springframework/boot/loader/launch/JarLauncher.class");
assertThat(filenames).contains("test/lib/dependency-1.jar")
.contains("test/lib/dependency-2.jar")
.contains("test/lib/dependency-3-SNAPSHOT.jar")
.contains("test/test.jar")
.doesNotContain("test/org/springframework/boot/loader/launch/JarLauncher.class");
}
@Test
void extractLibrariesAndCreatesRunnerInDestination() throws IOException {
void extractLibrariesAndCreatesApplicationInDestination() throws IOException {
run(ExtractCommandTests.this.archive, "--destination", file("out").getAbsolutePath());
List<String> filenames = listFilenames();
assertThat(filenames).contains("out/lib/dependency-1.jar")
.contains("out/lib/dependency-2.jar")
.contains("out/lib/dependency-3-SNAPSHOT.jar")
.contains("out/runner.jar");
.contains("out/test.jar");
}
@Test
void runnerNameAndLibrariesDirectoriesCanBeCustomized() throws IOException {
run(ExtractCommandTests.this.archive, "--runner-filename", "runner-customized.jar", "--libraries",
void applicationNameAndLibrariesDirectoriesCanBeCustomized() throws IOException {
run(ExtractCommandTests.this.archive, "--application-filename", "application-customized.jar", "--libraries",
"dependencies");
List<String> filenames = listFilenames();
assertThat(filenames).contains("dependencies/dependency-1.jar")
.contains("dependencies/dependency-2.jar")
.contains("dependencies/dependency-3-SNAPSHOT.jar");
File runner = file("runner-customized.jar");
assertThat(runner).exists();
Map<String, String> attributes = getJarManifestAttributes(runner);
assertThat(filenames).contains("test/dependencies/dependency-1.jar")
.contains("test/dependencies/dependency-2.jar")
.contains("test/dependencies/dependency-3-SNAPSHOT.jar");
File application = file("test/application-customized.jar");
assertThat(application).exists();
Map<String, String> attributes = getJarManifestAttributes(application);
assertThat(attributes).containsEntry("Class-Path",
"dependencies/dependency-1.jar dependencies/dependency-2.jar dependencies/dependency-3-SNAPSHOT.jar");
}
@Test
void runnerContainsManifestEntries() throws IOException {
void applicationContainsManifestEntries() throws IOException {
run(ExtractCommandTests.this.archive);
File runner = file("runner.jar");
Map<String, String> attributes = getJarManifestAttributes(runner);
File application = file("test/test.jar");
Map<String, String> attributes = getJarManifestAttributes(application);
assertThat(attributes).containsEntry("Main-Class", "org.example.Main")
.containsEntry("Class-Path", "lib/dependency-1.jar lib/dependency-2.jar lib/dependency-3-SNAPSHOT.jar")
.containsEntry("Some-Attribute", "Some-Value")
@ -167,27 +167,27 @@ class ExtractCommandTests extends AbstractTests {
}
@Test
void runnerContainsApplicationClassesAndResources() throws IOException {
void applicationContainsApplicationClassesAndResources() throws IOException {
run(ExtractCommandTests.this.archive);
File runner = file("runner.jar");
List<String> entryNames = getJarEntryNames(runner);
File application = file("test/test.jar");
List<String> entryNames = getJarEntryNames(application);
assertThat(entryNames).contains("application.properties");
}
@Test
void appliesFileTimes() {
run(ExtractCommandTests.this.archive);
assertThat(file("lib/dependency-1.jar")).exists().satisfies(ExtractCommandTests.this::timeAttributes);
assertThat(file("lib/dependency-2.jar")).exists().satisfies(ExtractCommandTests.this::timeAttributes);
assertThat(file("lib/dependency-3-SNAPSHOT.jar")).exists()
assertThat(file("test/lib/dependency-1.jar")).exists().satisfies(ExtractCommandTests.this::timeAttributes);
assertThat(file("test/lib/dependency-2.jar")).exists().satisfies(ExtractCommandTests.this::timeAttributes);
assertThat(file("test/lib/dependency-3-SNAPSHOT.jar")).exists()
.satisfies(ExtractCommandTests.this::timeAttributes);
}
@Test
void runnerDoesntContainLibraries() throws IOException {
void applicationDoesntContainLibraries() throws IOException {
run(ExtractCommandTests.this.archive);
File runner = file("runner.jar");
List<String> entryNames = getJarEntryNames(runner);
File application = file("test/test.jar");
List<String> entryNames = getJarEntryNames(application);
assertThat(entryNames).doesNotContain("BOOT-INF/lib/dependency-1.jar", "BOOT-INF/lib/dependency-2.jar");
}
@ -197,7 +197,42 @@ class ExtractCommandTests extends AbstractTests {
try (FileWriter writer = new FileWriter(file)) {
writer.write("text");
}
assertThatIllegalStateException().isThrownBy(() -> run(file)).withMessageContaining("not compatible");
TestPrintStream out = run(file);
assertThat(out).contains("is not compatible; ensure jar file is valid and launch script is not enabled");
}
@Test
void shouldFailIfDirectoryIsNotEmpty() throws IOException {
File destination = file("out");
Files.createDirectories(destination.toPath());
Files.createFile(new File(destination, "file.txt").toPath());
TestPrintStream out = run(ExtractCommandTests.this.archive, "--destination", destination.getAbsolutePath());
assertThat(out).contains("already exists and is not empty");
}
@Test
void shouldNotFailIfDirectoryExistsButIsEmpty() throws IOException {
File destination = file("out");
Files.createDirectories(destination.toPath());
run(ExtractCommandTests.this.archive, "--destination", destination.getAbsolutePath());
List<String> filenames = listFilenames();
assertThat(filenames).contains("out/lib/dependency-1.jar")
.contains("out/lib/dependency-2.jar")
.contains("out/lib/dependency-3-SNAPSHOT.jar")
.contains("out/test.jar");
}
@Test
void shouldNotFailIfDirectoryIsNotEmptyButForceIsPassed() throws IOException {
File destination = file("out");
Files.createDirectories(destination.toPath());
Files.createFile(new File(destination, "file.txt").toPath());
run(ExtractCommandTests.this.archive, "--destination", destination.getAbsolutePath(), "--force");
List<String> filenames = listFilenames();
assertThat(filenames).contains("out/lib/dependency-1.jar")
.contains("out/lib/dependency-2.jar")
.contains("out/lib/dependency-3-SNAPSHOT.jar")
.contains("out/test.jar");
}
}
@ -206,23 +241,23 @@ class ExtractCommandTests extends AbstractTests {
class ExtractWithLayers {
@Test
void extractLibrariesAndCreatesRunner() throws IOException {
void extractLibrariesAndCreatesApplication() throws IOException {
run(ExtractCommandTests.this.archive, "--layers");
List<String> filenames = listFilenames();
assertThat(filenames).contains("dependencies/lib/dependency-1.jar")
.contains("dependencies/lib/dependency-2.jar")
.contains("snapshot-dependencies/lib/dependency-3-SNAPSHOT.jar")
.contains("application/runner.jar");
assertThat(filenames).contains("test/dependencies/lib/dependency-1.jar")
.contains("test/dependencies/lib/dependency-2.jar")
.contains("test/snapshot-dependencies/lib/dependency-3-SNAPSHOT.jar")
.contains("test/application/test.jar");
}
@Test
void extractsOnlySelectedLayers() throws IOException {
run(ExtractCommandTests.this.archive, "--layers", "dependencies");
List<String> filenames = listFilenames();
assertThat(filenames).contains("dependencies/lib/dependency-1.jar")
.contains("dependencies/lib/dependency-2.jar")
.doesNotContain("snapshot-dependencies/lib/dependency-3-SNAPSHOT.jar")
.doesNotContain("application/runner.jar");
assertThat(filenames).contains("test/dependencies/lib/dependency-1.jar")
.contains("test/dependencies/lib/dependency-2.jar")
.doesNotContain("test/snapshot-dependencies/lib/dependency-3-SNAPSHOT.jar")
.doesNotContain("test/application/test.jar");
}
@Test
@ -241,14 +276,14 @@ class ExtractCommandTests extends AbstractTests {
void extract() throws IOException {
run(ExtractCommandTests.this.archive, "--launcher");
List<String> filenames = listFilenames();
assertThat(filenames).contains("META-INF/MANIFEST.MF")
.contains("BOOT-INF/classpath.idx")
.contains("BOOT-INF/layers.idx")
.contains("BOOT-INF/lib/dependency-1.jar")
.contains("BOOT-INF/lib/dependency-2.jar")
.contains("BOOT-INF/lib/dependency-3-SNAPSHOT.jar")
.contains("BOOT-INF/classes/application.properties")
.contains("org/springframework/boot/loader/launch/JarLauncher.class");
assertThat(filenames).contains("test/META-INF/MANIFEST.MF")
.contains("test/BOOT-INF/classpath.idx")
.contains("test/BOOT-INF/layers.idx")
.contains("test/BOOT-INF/lib/dependency-1.jar")
.contains("test/BOOT-INF/lib/dependency-2.jar")
.contains("test/BOOT-INF/lib/dependency-3-SNAPSHOT.jar")
.contains("test/BOOT-INF/classes/application.properties")
.contains("test/org/springframework/boot/loader/launch/JarLauncher.class");
}
@Test
@ -267,14 +302,14 @@ class ExtractCommandTests extends AbstractTests {
void extract() throws IOException {
run(ExtractCommandTests.this.archive, "--launcher", "--layers");
List<String> filenames = listFilenames();
assertThat(filenames).contains("application/META-INF/MANIFEST.MF")
.contains("application/BOOT-INF/classpath.idx")
.contains("application/BOOT-INF/layers.idx")
.contains("dependencies/BOOT-INF/lib/dependency-1.jar")
.contains("dependencies/BOOT-INF/lib/dependency-2.jar")
.contains("snapshot-dependencies/BOOT-INF/lib/dependency-3-SNAPSHOT.jar")
.contains("application/BOOT-INF/classes/application.properties")
.contains("spring-boot-loader/org/springframework/boot/loader/launch/JarLauncher.class");
assertThat(filenames).contains("test/application/META-INF/MANIFEST.MF")
.contains("test/application/BOOT-INF/classpath.idx")
.contains("test/application/BOOT-INF/layers.idx")
.contains("test/dependencies/BOOT-INF/lib/dependency-1.jar")
.contains("test/dependencies/BOOT-INF/lib/dependency-2.jar")
.contains("test/snapshot-dependencies/BOOT-INF/lib/dependency-3-SNAPSHOT.jar")
.contains("test/application/BOOT-INF/classes/application.properties")
.contains("test/spring-boot-loader/org/springframework/boot/loader/launch/JarLauncher.class");
}
@Test
@ -288,14 +323,14 @@ class ExtractCommandTests extends AbstractTests {
void extractsOnlySelectedLayers() throws IOException {
run(ExtractCommandTests.this.archive, "--launcher", "--layers", "dependencies");
List<String> filenames = listFilenames();
assertThat(filenames).doesNotContain("application/META-INF/MANIFEST.MF")
.doesNotContain("application/BOOT-INF/classpath.idx")
.doesNotContain("application/BOOT-INF/layers.idx")
.contains("dependencies/BOOT-INF/lib/dependency-1.jar")
.contains("dependencies/BOOT-INF/lib/dependency-2.jar")
.doesNotContain("snapshot-dependencies/BOOT-INF/lib/dependency-3-SNAPSHOT.jar")
.doesNotContain("application/BOOT-INF/classes/application.properties")
.doesNotContain("spring-boot-loader/org/springframework/boot/loader/launch/JarLauncher.class");
assertThat(filenames).doesNotContain("test/application/META-INF/MANIFEST.MF")
.doesNotContain("test/application/BOOT-INF/classpath.idx")
.doesNotContain("test/application/BOOT-INF/layers.idx")
.contains("test/dependencies/BOOT-INF/lib/dependency-1.jar")
.contains("test/dependencies/BOOT-INF/lib/dependency-2.jar")
.doesNotContain("test/snapshot-dependencies/BOOT-INF/lib/dependency-3-SNAPSHOT.jar")
.doesNotContain("test/application/BOOT-INF/classes/application.properties")
.doesNotContain("test/spring-boot-loader/org/springframework/boot/loader/launch/JarLauncher.class");
}
}

View File

@ -172,9 +172,10 @@ class ExtractLayersCommandTests {
writer.write("text");
}
given(this.context.getArchiveFile()).willReturn(file);
assertThatIllegalStateException()
.isThrownBy(() -> this.command.run(System.out, Collections.emptyMap(), Collections.emptyList()))
.withMessageContaining("not compatible");
try (TestPrintStream out = new TestPrintStream(this)) {
this.command.run(out, Collections.emptyMap(), Collections.emptyList());
assertThat(out).contains("is not compatible");
}
}
@Test

View File

@ -62,15 +62,25 @@ class TestPrintStream extends PrintStream implements AssertProvider<PrintStreamA
void hasSameContentAsResource(String resource) {
try {
InputStream stream = this.actual.testClass.getResourceAsStream(resource);
String content = FileCopyUtils.copyToString(new InputStreamReader(stream, StandardCharsets.UTF_8));
Assertions.assertThat(this.actual).hasToString(content);
try (InputStream stream = this.actual.testClass.getResourceAsStream(resource)) {
Assertions.assertThat(stream).as("Resource '%s'", resource).isNotNull();
String content = FileCopyUtils.copyToString(new InputStreamReader(stream, StandardCharsets.UTF_8));
hasSameContent(content);
}
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
void hasSameContent(String content) {
Assertions.assertThat(this.actual).hasToString(content);
}
void contains(String text) {
Assertions.assertThat(this.actual.toString()).contains(text);
}
}
}

View File

@ -6,8 +6,9 @@ Usage:
java -Djarmode=tools -jar test.jar extract [options]
Options:
--launcher Whether to extract the Spring Boot launcher
--layers string list Layers to extract
--destination string Directory to extract files to. Defaults to the current working directory
--libraries string Name of the libraries directory. Only applicable when not using --launcher. Defaults to lib/
--runner-filename string Name of the runner JAR file. Only applicable when not using --launcher. Defaults to runner.jar
--launcher Whether to extract the Spring Boot launcher
--layers string list Layers to extract
--destination string Directory to extract files to. Defaults to a directory named after the uber JAR (without the file extension)
--libraries string Name of the libraries directory. Only applicable when not using --launcher. Defaults to lib/
--application-filename string Name of the application JAR file. Only applicable when not using --launcher. Defaults to the uber JAR filename
--force Whether to ignore non-empty directories, extract anyway

View File

@ -6,8 +6,9 @@ Usage:
java -Djarmode=tools -jar test.jar extract [options]
Options:
--launcher Whether to extract the Spring Boot launcher
--layers string list Layers to extract
--destination string Directory to extract files to. Defaults to the current working directory
--libraries string Name of the libraries directory. Only applicable when not using --launcher. Defaults to lib/
--runner-filename string Name of the runner JAR file. Only applicable when not using --launcher. Defaults to runner.jar
--launcher Whether to extract the Spring Boot launcher
--layers string list Layers to extract
--destination string Directory to extract files to. Defaults to a directory named after the uber JAR (without the file extension)
--libraries string Name of the libraries directory. Only applicable when not using --launcher. Defaults to lib/
--application-filename string Name of the application JAR file. Only applicable when not using --launcher. Defaults to the uber JAR filename
--force Whether to ignore non-empty directories, extract anyway

View File

@ -4,8 +4,9 @@ Usage:
java -Djarmode=tools -jar test.jar extract [options]
Options:
--launcher Whether to extract the Spring Boot launcher
--layers string list Layers to extract
--destination string Directory to extract files to. Defaults to the current working directory
--libraries string Name of the libraries directory. Only applicable when not using --launcher. Defaults to lib/
--runner-filename string Name of the runner JAR file. Only applicable when not using --launcher. Defaults to runner.jar
--launcher Whether to extract the Spring Boot launcher
--layers string list Layers to extract
--destination string Directory to extract files to. Defaults to a directory named after the uber JAR (without the file extension)
--libraries string Name of the libraries directory. Only applicable when not using --launcher. Defaults to lib/
--application-filename string Name of the application JAR file. Only applicable when not using --launcher. Defaults to the uber JAR filename
--force Whether to ignore non-empty directories, extract anyway