From 5a77122abeeffc8382e5b9552c632fdf0c1a4dee Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 19 Mar 2024 14:08:10 +0100 Subject: [PATCH] Create classpath argfile on windows for run tasks Closes gh-17766 --- .../boot/maven/AbstractRunMojo.java | 68 ++++++++++++++++++- .../boot/maven/AbstractRunMojoTests.java | 44 ++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/AbstractRunMojoTests.java 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 f2b9c7bc261..881bb16a322 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 @@ -20,10 +20,14 @@ import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -41,6 +45,7 @@ import org.apache.maven.toolchain.ToolchainManager; import org.springframework.boot.loader.tools.FileUtils; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** * Base class to run a Spring Boot application. @@ -50,6 +55,7 @@ import org.springframework.util.ObjectUtils; * @author David Liu * @author Daniel Young * @author Dmytro Nosan + * @author Moritz Halbritter * @since 1.3.0 * @see RunMojo * @see StartMojo @@ -239,6 +245,10 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { JavaProcessExecutor processExecutor = new JavaProcessExecutor(this.session, this.toolchainManager); File workingDirectoryToUse = (this.workingDirectory != null) ? this.workingDirectory : this.project.getBasedir(); + if (getLog().isDebugEnabled()) { + getLog().debug("Working directory: " + workingDirectoryToUse); + getLog().debug("Java arguments: " + String.join(" ", args)); + } run(processExecutor, workingDirectoryToUse, args, determineEnvironmentVariables()); } @@ -351,13 +361,40 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { getLog().debug("Classpath for forked process: " + classpath); } args.add("-cp"); - args.add(classpath.toString()); + if (needsClasspathArgFile()) { + args.add("@" + writeClasspathArgFile(classpath.toString())); + } + else { + args.add(classpath.toString()); + } } catch (Exception ex) { throw new MojoExecutionException("Could not build classpath", ex); } } + private boolean needsClasspathArgFile() { + // Windows limits the maximum command length, so we use an argfile there + return runsOnWindows(); + } + + private boolean runsOnWindows() { + String os = System.getProperty("os.name"); + if (!StringUtils.hasLength(os)) { + if (getLog().isWarnEnabled()) { + getLog().warn("System property os.name is not set"); + } + return false; + } + return os.toLowerCase(Locale.ROOT).contains("win"); + } + + private Path writeClasspathArgFile(String classpath) throws IOException { + ArgFile argFile = ArgFile.create(); + argFile.write(classpath); + return argFile.getPath(); + } + protected URL[] getClassPathUrls() throws MojoExecutionException { try { List urls = new ArrayList<>(); @@ -372,7 +409,6 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { } } - @SuppressWarnings("removal") private void addAdditionalClasspathLocations(List urls) throws MalformedURLException { Assert.state(ObjectUtils.isEmpty(this.directories) || ObjectUtils.isEmpty(this.additionalClasspathElements), "Either additionalClasspathElements or directories (deprecated) should be set, not both"); @@ -437,4 +473,32 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { } + static class ArgFile { + + private final Path path; + + ArgFile(Path path) { + this.path = path; + } + + void write(String content) throws IOException { + String escaped = escape(content); + Files.writeString(this.path, "\"" + escaped + "\"", StandardOpenOption.APPEND); + } + + Path getPath() { + return this.path; + } + + private String escape(String content) { + return content.replace("\\", "\\\\"); + } + + static ArgFile create() throws IOException { + Path file = Files.createTempFile("spring-boot-", ".argfile"); + return new ArgFile(file); + } + + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/AbstractRunMojoTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/AbstractRunMojoTests.java new file mode 100644 index 00000000000..1e2dd8e7d0c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/AbstractRunMojoTests.java @@ -0,0 +1,44 @@ +/* + * 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. + * 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.IOException; +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.maven.AbstractRunMojo.ArgFile; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AbstractRunMojo}. + * + * @author Moritz Halbritter + */ +class AbstractRunMojoTests { + + @Test + void argfileEscapesContent() throws IOException { + ArgFile file = ArgFile.create(); + file.write("some \\ content"); + file.write("And even more content"); + assertThat(file.getPath()).content(StandardCharsets.UTF_8) + .isEqualTo("\"some \\\\ content\"\"And even more content\""); + } + +}