Skip to content

Commit cd8c12d

Browse files
committed
Polish "Use ArgFile for classpath argument on Windows"
See gh-44305
1 parent a6b8083 commit cd8c12d

File tree

8 files changed

+228
-174
lines changed

8 files changed

+228
-174
lines changed

spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractAotMojo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ protected final void compileSourceFiles(URL[] classPath, File sourcesDirectory,
147147
JavaCompilerPluginConfiguration compilerConfiguration = new JavaCompilerPluginConfiguration(this.project);
148148
List<String> options = new ArrayList<>();
149149
options.add("-cp");
150-
options.add(ClasspathBuilder.build(classPath));
150+
options.add(ClasspathBuilder.forURLs(classPath).build().argument());
151151
options.add("-d");
152152
options.add(outputDirectory.toPath().toAbsolutePath().toString());
153153
String releaseVersion = compilerConfiguration.getReleaseVersion();

spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.apache.maven.toolchain.ToolchainManager;
4040

4141
import org.springframework.boot.loader.tools.FileUtils;
42+
import org.springframework.boot.maven.ClasspathBuilder.Classpath;
4243
import org.springframework.util.Assert;
4344
import org.springframework.util.ObjectUtils;
4445

@@ -345,12 +346,13 @@ private void addActiveProfileArgument(RunArguments arguments) {
345346

346347
private void addClasspath(List<String> args) throws MojoExecutionException {
347348
try {
348-
String classpath = ClasspathBuilder.build(getClassPathUrls());
349+
Classpath classpath = ClasspathBuilder.forURLs(getClassPathUrls()).build();
349350
if (getLog().isDebugEnabled()) {
350-
getLog().debug("Classpath for forked process: " + classpath);
351+
getLog().debug("Classpath for forked process: "
352+
+ classpath.elements().map(Object::toString).collect(Collectors.joining(File.separator)));
351353
}
352354
args.add("-cp");
353-
args.add(classpath);
355+
args.add(classpath.argument());
354356
}
355357
catch (Exception ex) {
356358
throw new MojoExecutionException("Could not build classpath", ex);

spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ArgFile.java

Lines changed: 0 additions & 81 deletions
This file was deleted.

spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ClasspathBuilder.java

Lines changed: 117 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,18 @@
2020
import java.io.IOException;
2121
import java.net.URISyntaxException;
2222
import java.net.URL;
23+
import java.nio.charset.Charset;
24+
import java.nio.charset.UnsupportedCharsetException;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import java.nio.file.Paths;
28+
import java.util.ArrayList;
29+
import java.util.Arrays;
30+
import java.util.Collections;
31+
import java.util.List;
2332
import java.util.Locale;
33+
import java.util.stream.Collectors;
34+
import java.util.stream.Stream;
2435

2536
import org.springframework.util.ObjectUtils;
2637
import org.springframework.util.StringUtils;
@@ -31,9 +42,23 @@
3142
* @author Stephane Nicoll
3243
* @author Dmytro Nosan
3344
*/
34-
final class ClasspathBuilder {
45+
class ClasspathBuilder {
3546

36-
private ClasspathBuilder() {
47+
private final List<URL> urls;
48+
49+
protected ClasspathBuilder(List<URL> urls) {
50+
this.urls = urls;
51+
}
52+
53+
/**
54+
* Builds a classpath string or an argument file representing the classpath, depending
55+
* on the operating system.
56+
* @param urls an array of {@link URL} representing the elements of the classpath
57+
* @return the classpath; on Windows, the path to an argument file is returned,
58+
* prefixed with '@'
59+
*/
60+
static ClasspathBuilder forURLs(List<URL> urls) {
61+
return new ClasspathBuilder(new ArrayList<>(urls));
3762
}
3863

3964
/**
@@ -43,47 +68,110 @@ private ClasspathBuilder() {
4368
* @return the classpath; on Windows, the path to an argument file is returned,
4469
* prefixed with '@'
4570
*/
46-
static String build(URL... urls) {
47-
if (ObjectUtils.isEmpty(urls)) {
48-
return "";
71+
static ClasspathBuilder forURLs(URL... urls) {
72+
return new ClasspathBuilder(Arrays.asList(urls));
73+
}
74+
75+
Classpath build() {
76+
if (ObjectUtils.isEmpty(this.urls)) {
77+
return new Classpath("", Collections.emptyList());
4978
}
50-
if (urls.length == 1) {
51-
return toFile(urls[0]).toString();
79+
if (this.urls.size() == 1) {
80+
Path file = toFile(this.urls.get(0));
81+
return new Classpath(file.toString(), List.of(file));
5282
}
53-
StringBuilder builder = new StringBuilder();
54-
for (URL url : urls) {
55-
if (!builder.isEmpty()) {
56-
builder.append(File.pathSeparator);
57-
}
58-
builder.append(toFile(url));
83+
List<Path> files = this.urls.stream().map(ClasspathBuilder::toFile).toList();
84+
String argument = files.stream().map(Object::toString).collect(Collectors.joining(File.pathSeparator));
85+
if (needsClasspathArgFile()) {
86+
argument = createArgFile(argument);
5987
}
60-
String classpath = builder.toString();
61-
if (runsOnWindows()) {
62-
try {
63-
return "@" + ArgFile.create(classpath);
64-
}
65-
catch (IOException ex) {
66-
return classpath;
67-
}
88+
return new Classpath(argument, files);
89+
}
90+
91+
protected boolean needsClasspathArgFile() {
92+
String os = System.getProperty("os.name");
93+
if (!StringUtils.hasText(os)) {
94+
return false;
6895
}
69-
return classpath;
96+
// Windows limits the maximum command length, so we use an argfile
97+
return os.toLowerCase(Locale.ROOT).contains("win");
7098
}
7199

72-
private static File toFile(URL url) {
100+
/**
101+
* Create a temporary file with the given {@code} classpath. Return a suitable
102+
* argument to load the file, that is the full path prefixed by {@code @}.
103+
* @param classpath the classpath to use
104+
* @return a suitable argument for the classpath using a file
105+
*/
106+
private String createArgFile(String classpath) {
73107
try {
74-
return new File(url.toURI());
108+
return "@" + writeClasspathToFile(classpath);
109+
}
110+
catch (IOException ex) {
111+
return classpath;
112+
}
113+
}
114+
115+
private Path writeClasspathToFile(CharSequence classpath) throws IOException {
116+
Path tempFile = Files.createTempFile("spring-boot-", ".argfile");
117+
tempFile.toFile().deleteOnExit();
118+
Files.writeString(tempFile, "\"" + escape(classpath) + "\"", getCharset());
119+
return tempFile;
120+
}
121+
122+
private static Charset getCharset() {
123+
String nativeEncoding = System.getProperty("native.encoding");
124+
if (nativeEncoding == null) {
125+
return Charset.defaultCharset();
126+
}
127+
try {
128+
return Charset.forName(nativeEncoding);
129+
}
130+
catch (UnsupportedCharsetException ex) {
131+
return Charset.defaultCharset();
132+
}
133+
}
134+
135+
private static String escape(CharSequence content) {
136+
return content.toString().replace("\\", "\\\\");
137+
}
138+
139+
private static Path toFile(URL url) {
140+
try {
141+
return Paths.get(url.toURI());
75142
}
76143
catch (URISyntaxException ex) {
77144
throw new IllegalArgumentException(ex);
78145
}
79146
}
80147

81-
private static boolean runsOnWindows() {
82-
String os = System.getProperty("os.name");
83-
if (!StringUtils.hasText(os)) {
84-
return false;
148+
static final class Classpath {
149+
150+
private final String argument;
151+
152+
private final List<Path> elements;
153+
154+
private Classpath(String argument, List<Path> elements) {
155+
this.argument = argument;
156+
this.elements = elements;
85157
}
86-
return os.toLowerCase(Locale.ROOT).contains("win");
158+
159+
/**
160+
* Return the {@code -cp} argument value.
161+
* @return the argument to use
162+
*/
163+
String argument() {
164+
return this.argument;
165+
}
166+
167+
/**
168+
* Return the classpath elements.
169+
* @return the JAR files to use
170+
*/
171+
Stream<Path> elements() {
172+
return this.elements.stream();
173+
}
174+
87175
}
88176

89177
}

spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/CommandLineBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ List<String> build() {
8282
}
8383
if (!this.classpathElements.isEmpty()) {
8484
commandLine.add("-cp");
85-
commandLine.add(ClasspathBuilder.build(this.classpathElements.toArray(URL[]::new)));
85+
commandLine.add(ClasspathBuilder.forURLs(this.classpathElements).build().argument());
8686
}
8787
commandLine.add(this.mainClass);
8888
if (!this.arguments.isEmpty()) {

spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ArgFileTests.java

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)