Skip to content

Commit 9411a4c

Browse files
committed
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
1 parent 1e402f9 commit 9411a4c

18 files changed

+229
-132
lines changed

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-customLayers.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,5 @@ task listLayers(type: JavaExec) {
4545
task extractLayers(type: JavaExec) {
4646
classpath = bootJar.outputs.files
4747
systemProperties = [ "jarmode": "tools" ]
48-
args "extract", "--layers", "--launcher"
48+
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
4949
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-implicitLayers.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,5 @@ task listLayers(type: JavaExec) {
2828
task extractLayers(type: JavaExec) {
2929
classpath = bootJar.outputs.files
3030
systemProperties = [ "jarmode": "tools" ]
31-
args "extract", "--layers", "--launcher"
31+
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
3232
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-layersWithCustomSourceSet.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@ task listLayers(type: JavaExec) {
3131
task extractLayers(type: JavaExec) {
3232
classpath = bootJar.outputs.files
3333
systemProperties = [ "jarmode": "tools" ]
34-
args "extract", "--layers", "--launcher"
34+
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
3535
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-multiModuleCustomLayers.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,5 @@ task listLayers(type: JavaExec) {
6262
task extractLayers(type: JavaExec) {
6363
classpath = bootJar.outputs.files
6464
systemProperties = [ "jarmode": "tools" ]
65-
args "extract", "--layers", "--launcher"
65+
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
6666
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-multiModuleImplicitLayers.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,5 @@ task listLayers(type: JavaExec) {
4040
task extractLayers(type: JavaExec) {
4141
classpath = bootJar.outputs.files
4242
systemProperties = [ "jarmode": "tools" ]
43-
args "extract", "--layers", "--launcher"
43+
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
4444
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-customLayers.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,5 @@ task listLayers(type: JavaExec) {
4646
task extractLayers(type: JavaExec) {
4747
classpath = bootWar.outputs.files
4848
systemProperties = [ "jarmode": "tools" ]
49-
args "extract", "--layers", "--launcher"
49+
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
5050
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-implicitLayers.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@ task listLayers(type: JavaExec) {
2929
task extractLayers(type: JavaExec) {
3030
classpath = bootWar.outputs.files
3131
systemProperties = [ "jarmode": "tools" ]
32-
args "extract", "--layers", "--launcher"
32+
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
3333
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-layersWithCustomSourceSet.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@ task listLayers(type: JavaExec) {
3232
task extractLayers(type: JavaExec) {
3333
classpath = bootWar.outputs.files
3434
systemProperties = [ "jarmode": "tools" ]
35-
args "extract", "--layers", "--launcher"
35+
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
3636
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-multiModuleCustomLayers.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,5 @@ task listLayers(type: JavaExec) {
6363
task extractLayers(type: JavaExec) {
6464
classpath = bootWar.outputs.files
6565
systemProperties = [ "jarmode": "tools" ]
66-
args "extract", "--layers", "--launcher"
66+
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
6767
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-multiModuleImplicitLayers.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,5 @@ task listLayers(type: JavaExec) {
4141
task extractLayers(type: JavaExec) {
4242
classpath = bootWar.outputs.files
4343
systemProperties = [ "jarmode": "tools" ]
44-
args "extract", "--layers", "--launcher"
44+
args "extract", "--layers", "--launcher", "--destination", ".", "--force"
4545
}

spring-boot-project/spring-boot-tools/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/ExtractCommand.java

+85-36
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.nio.file.attribute.BasicFileAttributeView;
2828
import java.nio.file.attribute.FileTime;
2929
import java.util.List;
30+
import java.util.Locale;
3031
import java.util.Map;
3132
import java.util.Set;
3233
import java.util.jar.JarEntry;
@@ -52,7 +53,7 @@ class ExtractCommand extends Command {
5253
/**
5354
* Option to create a launcher.
5455
*/
55-
static final Option LAUNCHER_OPTION = Option.of("launcher", null, "Whether to extract the Spring Boot launcher");
56+
static final Option LAUNCHER_OPTION = Option.flag("launcher", "Whether to extract the Spring Boot launcher");
5657

5758
/**
5859
* Option to extract layers.
@@ -63,13 +64,18 @@ class ExtractCommand extends Command {
6364
* Option to specify the destination to write to.
6465
*/
6566
static final Option DESTINATION_OPTION = Option.of("destination", "string",
66-
"Directory to extract files to. Defaults to the current working directory");
67+
"Directory to extract files to. Defaults to a directory named after the uber JAR (without the file extension)");
68+
69+
/**
70+
* Option to ignore non-empty directory error.
71+
*/
72+
static final Option FORCE_OPTION = Option.flag("force", "Whether to ignore non-empty directories, extract anyway");
6773

6874
private static final Option LIBRARIES_DIRECTORY_OPTION = Option.of("libraries", "string",
6975
"Name of the libraries directory. Only applicable when not using --launcher. Defaults to lib/");
7076

71-
private static final Option RUNNER_FILENAME_OPTION = Option.of("runner-filename", "string",
72-
"Name of the runner JAR file. Only applicable when not using --launcher. Defaults to runner.jar");
77+
private static final Option APPLICATION_FILENAME_OPTION = Option.of("application-filename", "string",
78+
"Name of the application JAR file. Only applicable when not using --launcher. Defaults to the uber JAR filename");
7379

7480
private final Context context;
7581

@@ -81,7 +87,8 @@ class ExtractCommand extends Command {
8187

8288
ExtractCommand(Context context, Layers layers) {
8389
super("extract", "Extract the contents from the jar", Options.of(LAUNCHER_OPTION, LAYERS_OPTION,
84-
DESTINATION_OPTION, LIBRARIES_DIRECTORY_OPTION, RUNNER_FILENAME_OPTION), Parameters.none());
90+
DESTINATION_OPTION, LIBRARIES_DIRECTORY_OPTION, APPLICATION_FILENAME_OPTION, FORCE_OPTION),
91+
Parameters.none());
8592
this.context = context;
8693
this.layers = layers;
8794
}
@@ -90,7 +97,8 @@ class ExtractCommand extends Command {
9097
void run(PrintStream out, Map<Option, String> options, List<String> parameters) {
9198
try {
9299
checkJarCompatibility();
93-
File destination = getWorkingDirectory(options);
100+
File destination = getDestination(options);
101+
checkDirectoryIsEmpty(options, destination);
94102
FileResolver fileResolver = getFileResolver(destination, options);
95103
fileResolver.createDirectories();
96104
if (options.containsKey(LAUNCHER_OPTION)) {
@@ -99,7 +107,7 @@ void run(PrintStream out, Map<Option, String> options, List<String> parameters)
99107
else {
100108
JarStructure jarStructure = getJarStructure();
101109
extractLibraries(fileResolver, jarStructure, options);
102-
createRunner(jarStructure, fileResolver, options);
110+
createApplication(jarStructure, fileResolver, options);
103111
}
104112
}
105113
catch (IOException ex) {
@@ -108,15 +116,36 @@ void run(PrintStream out, Map<Option, String> options, List<String> parameters)
108116
catch (LayersNotEnabledException ex) {
109117
printError(out, "Layers are not enabled");
110118
}
119+
catch (AbortException ex) {
120+
printError(out, ex.getMessage());
121+
}
122+
}
123+
124+
private static void checkDirectoryIsEmpty(Map<Option, String> options, File destination) {
125+
if (options.containsKey(FORCE_OPTION)) {
126+
return;
127+
}
128+
if (!destination.exists()) {
129+
return;
130+
}
131+
if (!destination.isDirectory()) {
132+
throw new AbortException(destination.getAbsoluteFile() + " already exists and is not a directory");
133+
}
134+
File[] files = destination.listFiles();
135+
if (files != null && files.length > 0) {
136+
throw new AbortException(destination.getAbsoluteFile() + " already exists and is not empty");
137+
}
111138
}
112139

113140
private void checkJarCompatibility() throws IOException {
114141
File file = this.context.getArchiveFile();
115142
try (ZipInputStream stream = new ZipInputStream(new FileInputStream(file))) {
116143
ZipEntry entry = stream.getNextEntry();
117-
Assert.state(entry != null,
118-
() -> "File '%s' is not compatible; ensure jar file is valid and launch script is not enabled"
119-
.formatted(file));
144+
if (entry == null) {
145+
throw new AbortException(
146+
"File '%s' is not compatible; ensure jar file is valid and launch script is not enabled"
147+
.formatted(file));
148+
}
120149
}
121150
}
122151

@@ -149,20 +178,31 @@ private static String getLibrariesDirectory(Map<Option, String> options) {
149178
}
150179

151180
private FileResolver getFileResolver(File destination, Map<Option, String> options) {
152-
String runnerFilename = getRunnerFilename(options);
181+
String applicationFilename = getApplicationFilename(options);
153182
if (!options.containsKey(LAYERS_OPTION)) {
154-
return new NoLayersFileResolver(destination, runnerFilename);
183+
return new NoLayersFileResolver(destination, applicationFilename);
155184
}
156185
Layers layers = getLayers();
157186
Set<String> layersToExtract = StringUtils.commaDelimitedListToSet(options.get(LAYERS_OPTION));
158-
return new LayersFileResolver(destination, layers, layersToExtract, runnerFilename);
187+
return new LayersFileResolver(destination, layers, layersToExtract, applicationFilename);
159188
}
160189

161-
private File getWorkingDirectory(Map<Option, String> options) {
190+
private File getDestination(Map<Option, String> options) {
162191
if (options.containsKey(DESTINATION_OPTION)) {
163-
return new File(options.get(DESTINATION_OPTION));
192+
File destination = new File(options.get(DESTINATION_OPTION));
193+
if (destination.isAbsolute()) {
194+
return destination;
195+
}
196+
return new File(this.context.getWorkingDir(), destination.getPath());
197+
}
198+
return new File(this.context.getWorkingDir(), stripExtension(this.context.getArchiveFile().getName()));
199+
}
200+
201+
private static String stripExtension(String name) {
202+
if (name.toLowerCase(Locale.ROOT).endsWith(".jar") || name.toLowerCase(Locale.ROOT).endsWith(".war")) {
203+
return name.substring(0, name.length() - 4);
164204
}
165-
return this.context.getWorkingDir();
205+
return name;
166206
}
167207

168208
private JarStructure getJarStructure() {
@@ -199,9 +239,9 @@ private Layers getLayers() {
199239
return Layers.get(this.context);
200240
}
201241

202-
private void createRunner(JarStructure jarStructure, FileResolver fileResolver, Map<Option, String> options)
242+
private void createApplication(JarStructure jarStructure, FileResolver fileResolver, Map<Option, String> options)
203243
throws IOException {
204-
File file = fileResolver.resolveRunner();
244+
File file = fileResolver.resolveApplication();
205245
if (file == null) {
206246
return;
207247
}
@@ -221,11 +261,11 @@ private void createRunner(JarStructure jarStructure, FileResolver fileResolver,
221261
}
222262
}
223263

224-
private String getRunnerFilename(Map<Option, String> options) {
225-
if (options.containsKey(RUNNER_FILENAME_OPTION)) {
226-
return options.get(RUNNER_FILENAME_OPTION);
264+
private String getApplicationFilename(Map<Option, String> options) {
265+
if (options.containsKey(APPLICATION_FILENAME_OPTION)) {
266+
return options.get(APPLICATION_FILENAME_OPTION);
227267
}
228-
return "runner.jar";
268+
return this.context.getArchiveFile().getName();
229269
}
230270

231271
private static boolean isType(Entry entry, Type type) {
@@ -338,23 +378,24 @@ default File resolve(ZipEntry entry, String newName) throws IOException {
338378
File resolve(String originalName, String newName) throws IOException;
339379

340380
/**
341-
* Resolves the file for the runner.
342-
* @return the file for the runner or {@code null} if the runner should be skipped
381+
* Resolves the file for the application.
382+
* @return the file for the application or {@code null} if the application should
383+
* be skipped
343384
* @throws IOException if something went wrong
344385
*/
345-
File resolveRunner() throws IOException;
386+
File resolveApplication() throws IOException;
346387

347388
}
348389

349390
private static final class NoLayersFileResolver implements FileResolver {
350391

351392
private final File directory;
352393

353-
private final String runnerFilename;
394+
private final String applicationFilename;
354395

355-
private NoLayersFileResolver(File directory, String runnerFilename) {
396+
private NoLayersFileResolver(File directory, String applicationFilename) {
356397
this.directory = directory;
357-
this.runnerFilename = runnerFilename;
398+
this.applicationFilename = applicationFilename;
358399
}
359400

360401
@Override
@@ -367,8 +408,8 @@ public File resolve(String originalName, String newName) throws IOException {
367408
}
368409

369410
@Override
370-
public File resolveRunner() throws IOException {
371-
return resolve(this.runnerFilename, this.runnerFilename);
411+
public File resolveApplication() throws IOException {
412+
return resolve(this.applicationFilename, this.applicationFilename);
372413
}
373414

374415
}
@@ -381,13 +422,13 @@ private static final class LayersFileResolver implements FileResolver {
381422

382423
private final File directory;
383424

384-
private final String runnerFilename;
425+
private final String applicationFilename;
385426

386-
LayersFileResolver(File directory, Layers layers, Set<String> layersToExtract, String runnerFilename) {
427+
LayersFileResolver(File directory, Layers layers, Set<String> layersToExtract, String applicationFilename) {
387428
this.layers = layers;
388429
this.layersToExtract = layersToExtract;
389430
this.directory = directory;
390-
this.runnerFilename = runnerFilename;
431+
this.applicationFilename = applicationFilename;
391432
}
392433

393434
@Override
@@ -410,12 +451,12 @@ public File resolve(String originalName, String newName) throws IOException {
410451
}
411452

412453
@Override
413-
public File resolveRunner() throws IOException {
454+
public File resolveApplication() throws IOException {
414455
String layer = this.layers.getApplicationLayerName();
415456
if (shouldExtractLayer(layer)) {
416457
File directory = getLayerDirectory(layer);
417-
return assertFileIsContainedInDirectory(directory, new File(directory, this.runnerFilename),
418-
this.runnerFilename);
458+
return assertFileIsContainedInDirectory(directory, new File(directory, this.applicationFilename),
459+
this.applicationFilename);
419460
}
420461
return null;
421462
}
@@ -433,4 +474,12 @@ private boolean shouldExtractLayer(String layer) {
433474

434475
}
435476

477+
private static final class AbortException extends RuntimeException {
478+
479+
AbortException(String message) {
480+
super(message);
481+
}
482+
483+
}
484+
436485
}

spring-boot-project/spring-boot-tools/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/ExtractLayersCommand.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,10 @@ String getDeprecationMessage() {
5858
@Override
5959
void run(PrintStream out, Map<Option, String> options, List<String> parameters) {
6060
Map<Option, String> rewrittenOptions = new HashMap<>();
61-
if (options.containsKey(DESTINATION_OPTION)) {
62-
rewrittenOptions.put(ExtractCommand.DESTINATION_OPTION, options.get(DESTINATION_OPTION));
63-
}
61+
rewrittenOptions.put(ExtractCommand.DESTINATION_OPTION, options.getOrDefault(DESTINATION_OPTION, "."));
6462
rewrittenOptions.put(ExtractCommand.LAYERS_OPTION, StringUtils.collectionToCommaDelimitedString(parameters));
6563
rewrittenOptions.put(ExtractCommand.LAUNCHER_OPTION, null);
64+
rewrittenOptions.put(ExtractCommand.FORCE_OPTION, null);
6665
this.delegate.run(out, rewrittenOptions, Collections.emptyList());
6766
}
6867

0 commit comments

Comments
 (0)