diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractAotMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractAotMojo.java index 24bf2f4f597..aae376b2965 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractAotMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractAotMojo.java @@ -145,32 +145,31 @@ public abstract class AbstractAotMojo extends AbstractDependencyFilterMojo { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) { JavaCompilerPluginConfiguration compilerConfiguration = new JavaCompilerPluginConfiguration(this.project); - List options = new ArrayList<>(); - options.add("-cp"); - options.add(ClasspathBuilder.forURLs(classPath).build().toString()); - options.add("-d"); - options.add(outputDirectory.toPath().toAbsolutePath().toString()); + List args = new ArrayList<>(); + args.addAll(ClassPath.of(classPath).args(false)); + args.add("-d"); + args.add(outputDirectory.toPath().toAbsolutePath().toString()); String releaseVersion = compilerConfiguration.getReleaseVersion(); if (releaseVersion != null) { - options.add("--release"); - options.add(releaseVersion); + args.add("--release"); + args.add(releaseVersion); } else { String source = compilerConfiguration.getSourceMajorVersion(); if (source != null) { - options.add("--source"); - options.add(source); + args.add("--source"); + args.add(source); } String target = compilerConfiguration.getTargetMajorVersion(); if (target != null) { - options.add("--target"); - options.add(target); + args.add("--target"); + args.add(target); } } - options.addAll(new RunArguments(this.compilerArguments).getArgs()); + args.addAll(new RunArguments(this.compilerArguments).getArgs()); Iterable compilationUnits = fileManager.getJavaFileObjectsFromPaths(sourceFiles); Errors errors = new Errors(); - CompilationTask task = compiler.getTask(null, fileManager, errors, options, null, compilationUnits); + CompilationTask task = compiler.getTask(null, fileManager, errors, args, null, compilationUnits); boolean result = task.call(); if (!result || errors.hasReportedErrors()) { throw new IllegalStateException("Unable to compile generated source" + errors); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java index 66ed6a6a0cd..efafd471e0c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java @@ -39,7 +39,6 @@ import org.apache.maven.project.MavenProject; import org.apache.maven.toolchain.ToolchainManager; import org.springframework.boot.loader.tools.FileUtils; -import org.springframework.boot.maven.ClasspathBuilder.Classpath; /** * Base class to run a Spring Boot application. @@ -58,6 +57,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * The Maven project. + * * @since 1.0.0 */ @Parameter(defaultValue = "${project}", readonly = true, required = true) @@ -65,6 +65,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * The current Maven session. This is used for toolchain manager API calls. + * * @since 2.3.0 */ @Parameter(defaultValue = "${session}", readonly = true) @@ -72,17 +73,20 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * The toolchain manager to use to locate a custom JDK. + * * @since 2.3.0 */ @Component private ToolchainManager toolchainManager; /** - * Add maven resources to the classpath directly, this allows live in-place editing of - * resources. Duplicate resources are removed from {@code target/classes} to prevent - * them from appearing twice if {@code ClassLoader.getResources()} is called. Please - * consider adding {@code spring-boot-devtools} to your project instead as it provides - * this feature and many more. + * Add maven resources to the classpath directly, this allows live in-place + * editing of resources. Duplicate resources are removed from + * {@code target/classes} to prevent them from appearing twice if + * {@code ClassLoader.getResources()} is called. Please consider adding + * {@code spring-boot-devtools} to your project instead as it provides this + * feature and many more. + * * @since 1.0.0 */ @Parameter(property = "spring-boot.run.addResources", defaultValue = "false") @@ -90,6 +94,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * Path to agent jars. + * * @since 2.2.0 */ @Parameter(property = "spring-boot.run.agents") @@ -97,22 +102,26 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * Flag to say that the agent requires -noverify. + * * @since 1.0.0 */ @Parameter(property = "spring-boot.run.noverify") private boolean noverify = false; /** - * Current working directory to use for the application. If not specified, basedir - * will be used. + * Current working directory to use for the application. If not specified, + * basedir will be used. + * * @since 1.5.0 */ @Parameter(property = "spring-boot.run.workingDirectory") private File workingDirectory; /** - * JVM arguments that should be associated with the forked process used to run the - * application. On command line, make sure to wrap multiple values between quotes. + * JVM arguments that should be associated with the forked process used to run + * the application. On command line, make sure to wrap multiple values between + * quotes. + * * @since 1.1.0 */ @Parameter(property = "spring-boot.run.jvmArguments") @@ -120,14 +129,16 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * List of JVM system properties to pass to the process. + * * @since 2.1.0 */ @Parameter private Map systemPropertyVariables; /** - * List of Environment variables that should be associated with the forked process - * used to run the application. + * List of Environment variables that should be associated with the forked + * process used to run the application. + * * @since 2.1.0 */ @Parameter @@ -135,6 +146,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * Arguments that should be passed to the application. + * * @since 1.0.0 */ @Parameter @@ -142,8 +154,9 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * Arguments from the command line that should be passed to the application. Use - * spaces to separate multiple arguments and make sure to wrap multiple values between - * quotes. When specified, takes precedence over {@link #arguments}. + * spaces to separate multiple arguments and make sure to wrap multiple values + * between quotes. When specified, takes precedence over {@link #arguments}. + * * @since 2.2.3 */ @Parameter(property = "spring-boot.run.arguments") @@ -151,32 +164,36 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * The spring profiles to activate. Convenience shortcut of specifying the - * 'spring.profiles.active' argument. On command line use commas to separate multiple - * profiles. + * 'spring.profiles.active' argument. On command line use commas to separate + * multiple profiles. + * * @since 1.3.0 */ @Parameter(property = "spring-boot.run.profiles") private String[] profiles; /** - * The name of the main class. If not specified the first compiled class found that - * contains a 'main' method will be used. + * The name of the main class. If not specified the first compiled class found + * that contains a 'main' method will be used. + * * @since 1.0.0 */ @Parameter(property = "spring-boot.run.main-class") private String mainClass; /** - * Additional classpath elements that should be added to the classpath. An element can - * be a directory with classes and resources or a jar file. + * Additional classpath elements that should be added to the classpath. An + * element can be a directory with classes and resources or a jar file. + * * @since 3.2.0 */ @Parameter(property = "spring-boot.run.additional-classpath-elements") private String[] additionalClasspathElements; /** - * Directory containing the classes and resource files that should be used to run the - * application. + * Directory containing the classes and resource files that should be used to + * run the application. + * * @since 1.0.0 */ @Parameter(defaultValue = "${project.build.outputDirectory}", required = true) @@ -184,6 +201,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * Skip the execution. + * * @since 1.3.2 */ @Parameter(property = "spring-boot.run.skip", defaultValue = "false") @@ -206,9 +224,10 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { } /** - * Returns the directories that contain the application's classes and resources. When - * the application's main class has not been configured, each directory is searched in - * turn for an appropriate main class. + * Returns the directories that contain the application's classes and resources. + * When the application's main class has not been configured, each directory is + * searched in turn for an appropriate main class. + * * @return the directories that contain the application's classes and resources * @since 3.1.0 */ @@ -237,6 +256,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * Run the application. + * * @param processExecutor the {@link JavaProcessExecutor} to use * @param workingDirectory the working directory of the forked JVM * @param args the arguments (JVM arguments and application arguments) @@ -250,6 +270,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * Resolve the application arguments to use. + * * @return a {@link RunArguments} defining the application arguments */ protected RunArguments resolveApplicationArguments() { @@ -261,6 +282,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * Resolve the environment variables to use. + * * @return an {@link EnvVariables} defining the environment variables */ protected EnvVariables resolveEnvVariables() { @@ -281,15 +303,15 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { /** * Resolve the JVM arguments to use. + * * @return a {@link RunArguments} defining the JVM arguments */ protected RunArguments resolveJvmArguments() { StringBuilder stringBuilder = new StringBuilder(); if (this.systemPropertyVariables != null) { - stringBuilder.append(this.systemPropertyVariables.entrySet() - .stream() - .map((e) -> SystemPropertyFormatter.format(e.getKey(), e.getValue())) - .collect(Collectors.joining(" "))); + stringBuilder.append(this.systemPropertyVariables.entrySet().stream() + .map((e) -> SystemPropertyFormatter.format(e.getKey(), e.getValue())) + .collect(Collectors.joining(" "))); } if (this.jvmArguments != null) { stringBuilder.append(" ").append(this.jvmArguments); @@ -333,14 +355,12 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { private void addClasspath(List args) throws MojoExecutionException { try { - Classpath classpath = ClasspathBuilder.forURLs(getClassPathUrls()).build(); + ClassPath classpath = ClassPath.of(getClassPathUrls()); if (getLog().isDebugEnabled()) { getLog().debug("Classpath for forked process: " + classpath); } - args.add("-cp"); - args.add(classpath.argument()); - } - catch (Exception ex) { + args.addAll(classpath.args(true)); + } catch (Exception ex) { throw new MojoExecutionException("Could not build classpath", ex); } } @@ -353,8 +373,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { addProjectClasses(urls); addDependencies(urls); return urls.toArray(new URL[0]); - } - catch (IOException ex) { + } catch (IOException ex) { throw new MojoExecutionException("Unable to build classpath", ex); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ClassPath.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ClassPath.java new file mode 100644 index 00000000000..7c4fe76ffec --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ClassPath.java @@ -0,0 +1,143 @@ +/* + * Copyright 2012-2025 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.maven; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.function.UnaryOperator; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +import org.springframework.util.StringUtils; + +/** + * Encapsulates a class path and allows argument parameters to be created. On Windows an + * argument file is used whenever possible since the maximum command line length is + * limited. + * + * @author Stephane Nicoll + * @author Dmytro Nosan + * @author Phillip Webb + */ +final class ClassPath { + + private static final Collector JOIN_BY_PATH_SEPARATOR = Collectors + .joining(File.pathSeparator); + + private final boolean preferArgFile; + + private final String path; + + private ClassPath(boolean preferArgFile, String path) { + this.preferArgFile = preferArgFile; + this.path = path; + } + + /** + * Return the args to append to a java command line call (including {@code -cp}). + * @param allowArgFile if an arg file can be used + * @return the command line arguments + */ + List args(boolean allowArgFile) { + return (!this.path.isEmpty()) ? List.of("-cp", classPathArg(allowArgFile)) : Collections.emptyList(); + } + + private String classPathArg(boolean allowArgFile) { + if (this.preferArgFile && allowArgFile) { + try { + return "@" + createArgFile(); + } + catch (IOException ex) { + return this.path; + } + } + return this.path; + } + + private Path createArgFile() throws IOException { + Path argFile = Files.createTempFile("spring-boot-", ".argfile"); + argFile.toFile().deleteOnExit(); + Files.writeString(argFile, "\"" + this.path.replace("\\", "\\\\") + "\"", charset()); + return argFile; + } + + private Charset charset() { + try { + String nativeEncoding = System.getProperty("native.encoding"); + return (nativeEncoding != null) ? Charset.forName(nativeEncoding) : Charset.defaultCharset(); + } + catch (UnsupportedCharsetException ex) { + return Charset.defaultCharset(); + } + } + + /** + * Factory method to create a {@link ClassPath} of the given URLs. + * @param urls the class path URLs + * @return a new {@link ClassPath} instance + */ + static ClassPath of(URL... urls) { + return of(Arrays.asList(urls)); + } + + /** + * Factory method to create a {@link ClassPath} of the given URLs. + * @param urls the class path URLs + * @return a new {@link ClassPath} instance + */ + static ClassPath of(List urls) { + return of(System::getProperty, urls); + } + + /** + * Factory method to create a {@link ClassPath} of the given URLs. + * @param getSystemProperty {@link UnaryOperator} allowing access to system properties + * @param urls the class path URLs + * @return a new {@link ClassPath} instance + */ + static ClassPath of(UnaryOperator getSystemProperty, List urls) { + boolean preferrArgFile = urls.size() > 1 && isWindows(getSystemProperty); + return new ClassPath(preferrArgFile, + urls.stream().map(ClassPath::toPathString).collect(JOIN_BY_PATH_SEPARATOR)); + } + + private static boolean isWindows(UnaryOperator getSystemProperty) { + String os = getSystemProperty.apply("os.name"); + return StringUtils.hasText(os) && os.toLowerCase(Locale.ROOT).contains("win"); + } + + private static String toPathString(URL url) { + try { + return Paths.get(url.toURI()).toString(); + } + catch (URISyntaxException ex) { + throw new IllegalArgumentException(ex); + } + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ClasspathBuilder.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ClasspathBuilder.java deleted file mode 100644 index 24263f73e3f..00000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ClasspathBuilder.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2012-2025 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.maven; - -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.Charset; -import java.nio.charset.UnsupportedCharsetException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; - -/** - * Helper class to build the -cp (classpath) argument of a java process. - * - * @author Stephane Nicoll - * @author Dmytro Nosan - */ -class ClasspathBuilder { - - private final List urls; - - protected ClasspathBuilder(List urls) { - this.urls = urls; - } - - /** - * Creates a ClasspathBuilder instance using the specified list of URLs. - * @param urls a list of {@link URL} objects representing the elements of the - * classpath - * @return a new instance of {@code ClasspathBuilder} - */ - static ClasspathBuilder forURLs(List urls) { - return new ClasspathBuilder(new ArrayList<>(urls)); - } - - /** - * Creates a ClasspathBuilder instance using the specified array of URLs. - * @param urls an array of {@link URL} objects representing the elements of the - * classpath - * @return a new instance of {@code ClasspathBuilder} - */ - static ClasspathBuilder forURLs(URL... urls) { - return new ClasspathBuilder(Arrays.asList(urls)); - } - - /** - * Builds {@link Classpath} that containing a classpath argument and its corresponding - * classpath elements. - * @return a {@code Classpath} - */ - Classpath build() { - if (ObjectUtils.isEmpty(this.urls)) { - return new Classpath("", Collections.emptyList()); - } - if (this.urls.size() == 1) { - Path file = toFile(this.urls.get(0)); - return new Classpath(file.toString(), List.of(file)); - } - List files = this.urls.stream().map(ClasspathBuilder::toFile).toList(); - String argument = files.stream().map(Object::toString).collect(Collectors.joining(File.pathSeparator)); - if (needsClasspathArgFile()) { - argument = createArgFile(argument); - } - return new Classpath(argument, files); - } - - /** - * Determines if an argument file should be used for the classpath based on the - * operating system. On Windows, argument files are used due to the command length - * limitation. - * @return {@code true} if an argument file is required for the classpath, - * {@code false} otherwise - */ - protected boolean needsClasspathArgFile() { - String os = System.getProperty("os.name"); - if (!StringUtils.hasText(os)) { - return false; - } - // Windows limits the maximum command length, so we use an argfile - return os.toLowerCase(Locale.ROOT).contains("win"); - } - - /** - * Create a temporary file with the given {@code} classpath. Return a suitable - * argument to load the file, that is the full path prefixed by {@code @}. - * @param classpath the classpath to use - * @return a suitable argument for the classpath using a file - */ - private String createArgFile(String classpath) { - try { - return "@" + writeClasspathToFile(classpath); - } - catch (IOException ex) { - return classpath; - } - } - - private Path writeClasspathToFile(CharSequence classpath) throws IOException { - Path tempFile = Files.createTempFile("spring-boot-", ".argfile"); - tempFile.toFile().deleteOnExit(); - Files.writeString(tempFile, "\"" + escape(classpath) + "\"", getCharset()); - return tempFile; - } - - private static Charset getCharset() { - String nativeEncoding = System.getProperty("native.encoding"); - if (nativeEncoding == null) { - return Charset.defaultCharset(); - } - try { - return Charset.forName(nativeEncoding); - } - catch (UnsupportedCharsetException ex) { - return Charset.defaultCharset(); - } - } - - private static String escape(CharSequence content) { - return content.toString().replace("\\", "\\\\"); - } - - private static Path toFile(URL url) { - try { - return Paths.get(url.toURI()); - } - catch (URISyntaxException ex) { - throw new IllegalArgumentException(ex); - } - } - - /** - * Classpath consisting of a {@code -cp} argument and its associated elements. - */ - static final class Classpath { - - private final String argument; - - private final List elements; - - private Classpath(String argument, List elements) { - this.argument = argument; - this.elements = elements; - } - - /** - * Return the {@code -cp} argument value; on Windows, the path to an argument file - * is returned, prefixed with '@'. - * @return the argument to use - */ - String argument() { - return this.argument; - } - - /** - * Return the classpath elements. - * @return the JAR files to use - */ - Stream elements() { - return this.elements.stream(); - } - - @Override - public String toString() { - return elements().map(Path::toString).collect(Collectors.joining(File.pathSeparator)); - } - - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/CommandLineBuilder.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/CommandLineBuilder.java index 1e09773bd45..2c207eb1b52 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/CommandLineBuilder.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/CommandLineBuilder.java @@ -80,10 +80,7 @@ final class CommandLineBuilder { if (!this.options.isEmpty()) { commandLine.addAll(this.options); } - if (!this.classpathElements.isEmpty()) { - commandLine.add("-cp"); - commandLine.add(ClasspathBuilder.forURLs(this.classpathElements).build().argument()); - } + commandLine.addAll(ClassPath.of(this.classpathElements).args(true)); commandLine.add(this.mainClass); if (!this.arguments.isEmpty()) { commandLine.addAll(this.arguments); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ClassPathTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ClassPathTests.java new file mode 100644 index 00000000000..b06c06ea4b0 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ClassPathTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2025 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.maven; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.UnaryOperator; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for {@link ClassPath}. + * + * @author Dmytro Nosan + * @author Stephane Nicoll + * @author Phillip Webb + */ +class ClassPathTests { + + @Test + void argsWhenNoClassPathReturnsEmptyList() { + assertThat(ClassPath.of(Collections.emptyList()).args(false)).isEmpty(); + } + + @Test + void argsWhenSingleUrlOnWindowsUsesPath(@TempDir Path temp) throws Exception { + Path path = temp.resolve("test.jar"); + ClassPath classPath = ClassPath.of(onWindows(), List.of(path.toUri().toURL())); + assertThat(classPath.args(true)).containsExactly("-cp", path.toString()); + } + + @Test + void argsWhenSingleUrlNotOnWindowsUsesPath(@TempDir Path temp) throws Exception { + Path path = temp.resolve("test.jar"); + ClassPath classPath = ClassPath.of(onLinux(), List.of(path.toUri().toURL())); + assertThat(classPath.args(true)).containsExactly("-cp", path.toString()); + } + + @Test + void argsWhenMultipleUrlsOnWindowsAndAllowedUsesArgFile(@TempDir Path temp) throws Exception { + Path path1 = temp.resolve("test1.jar"); + Path path2 = temp.resolve("test2.jar"); + ClassPath classPath = ClassPath.of(onWindows(), List.of(path1.toUri().toURL(), path2.toUri().toURL())); + List args = classPath.args(true); + assertThat(args.get(0)).isEqualTo("-cp"); + assertThat(args.get(1)).startsWith("@"); + assertThat(Paths.get(args.get(1).substring(1))) + .hasContent("\"" + (path1 + File.pathSeparator + path2).replace("\\", "\\\\") + "\""); + } + + @Test + void argsWhenMultipleUrlsOnWindowsAndNotAllowedUsesPath(@TempDir Path temp) throws Exception { + Path path1 = temp.resolve("test1.jar"); + Path path2 = temp.resolve("test2.jar"); + ClassPath classPath = ClassPath.of(onWindows(), List.of(path1.toUri().toURL(), path2.toUri().toURL())); + assertThat(classPath.args(false)).containsExactly("-cp", path1 + File.pathSeparator + path2); + } + + @Test + void argsWhenMultipleUrlsNotOnWindowsUsesPath(@TempDir Path temp) throws Exception { + Path path1 = temp.resolve("test1.jar"); + Path path2 = temp.resolve("test2.jar"); + ClassPath classPath = ClassPath.of(onLinux(), List.of(path1.toUri().toURL(), path2.toUri().toURL())); + assertThat(classPath.args(true)).containsExactly("-cp", path1 + File.pathSeparator + path2); + } + + private UnaryOperator onWindows() { + return Map.of("os.name", "windows")::get; + } + + private UnaryOperator onLinux() { + return Map.of("os.name", "linux")::get; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ClasspathBuilderTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ClasspathBuilderTests.java deleted file mode 100644 index 39e4b1cdc22..00000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ClasspathBuilderTests.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2012-2025 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.maven; - -import java.io.File; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.EnabledOnOs; -import org.junit.jupiter.api.condition.OS; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.boot.maven.ClasspathBuilder.Classpath; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ClasspathBuilder}. - * - * @author Dmytro Nosan - * @author Stephane Nicoll - */ -class ClasspathBuilderTests { - - @Nested - @EnabledOnOs(OS.WINDOWS) - class WindowsTests { - - @Test - void buildWithEmptyClassPath() { - Classpath classpath = ClasspathBuilder.forURLs().build(); - assertThat(classpath.argument()).isEmpty(); - assertThat(classpath.elements()).isEmpty(); - } - - @Test - void buildWithSingleClassPathURL(@TempDir Path tempDir) throws Exception { - Path file = tempDir.resolve("test.jar"); - Classpath classpath = ClasspathBuilder.forURLs(file.toUri().toURL()).build(); - assertThat(classpath.argument()).isEqualTo(file.toString()); - assertThat(classpath.elements()).singleElement().isEqualTo(file); - } - - @Test - void buildWithMultipleClassPathURLs(@TempDir Path tempDir) throws Exception { - Path file = tempDir.resolve("test.jar"); - Path file1 = tempDir.resolve("test1.jar"); - String classpath = ClasspathBuilder.forURLs(file.toUri().toURL(), file1.toUri().toURL()).build().argument(); - assertThat(classpath).startsWith("@"); - assertThat(Paths.get(classpath.substring(1))) - .hasContent("\"" + (file + File.pathSeparator + file1).replace("\\", "\\\\") + "\""); - } - - } - - @Nested - @DisabledOnOs(OS.WINDOWS) - class UnixTests { - - @Test - void buildWithEmptyClassPath() { - Classpath classpath = ClasspathBuilder.forURLs().build(); - assertThat(classpath.argument()).isEmpty(); - assertThat(classpath.elements()).isEmpty(); - } - - @Test - void buildWithSingleClassPathURL(@TempDir Path tempDir) throws Exception { - Path file = tempDir.resolve("test.jar"); - Classpath classpath = ClasspathBuilder.forURLs(file.toUri().toURL()).build(); - assertThat(classpath.argument()).isEqualTo(file.toString()); - assertThat(classpath.elements()).singleElement().isEqualTo(file); - } - - @Test - void buildWithMultipleClassPathURLs(@TempDir Path tempDir) throws Exception { - Path file = tempDir.resolve("test.jar"); - Path file1 = tempDir.resolve("test1.jar"); - assertThat(ClasspathBuilder.forURLs(file.toUri().toURL(), file1.toUri().toURL()).build().argument()) - .isEqualTo(file + File.pathSeparator + file1); - } - - } - -}