This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch fix/maven-args-parsing in repository https://gitbox.apache.org/repos/asf/maven.git
commit f3a7fb92113beed72312e6c5f15655ef778fddd6 Author: Guillaume Nodet <[email protected]> AuthorDate: Mon Mar 23 13:06:47 2026 +0100 [MNG-8571] Fix MAVEN_ARGS parsing to handle quoted strings with spaces Replace naive String.split(" ") with a proper argument parser that handles single and double quoted strings, preserving backslashes for Windows paths. This fixes MAVEN_ARGS values like: -f "C:\Program Files\project\pom.xml" Applied to both EmbeddedMavenExecutor and ForkedMavenExecutor. Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../executor/embedded/EmbeddedMavenExecutor.java | 50 ++++++++++++++++++++-- .../cling/executor/forked/ForkedMavenExecutor.java | 48 +++++++++++++++++++-- .../embedded/EmbeddedMavenExecutorTest.java | 35 +++++++++++++++ .../executor/forked/ForkedMavenExecutorTest.java | 34 +++++++++++++++ 4 files changed, 159 insertions(+), 8 deletions(-) diff --git a/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java index e5eda275d5..8986bb371b 100644 --- a/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java +++ b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java @@ -31,7 +31,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -225,9 +224,9 @@ protected Context doCreate(Path mavenHome, ExecutorRequest executorRequest) { ArrayList<String> mavenArgs = new ArrayList<>(); String mavenArgsEnv = System.getenv("MAVEN_ARGS"); if (useMavenArgsEnv && mavenArgsEnv != null && !mavenArgsEnv.isEmpty()) { - Arrays.stream(mavenArgsEnv.split(" ")) - .filter(s -> !s.trim().isEmpty()) - .forEach(s -> mavenArgs.add(0, s)); + List<String> parsed = parseArguments(mavenArgsEnv); + Collections.reverse(parsed); + mavenArgs.addAll(parsed); } Properties properties = prepareProperties(executorRequest); @@ -441,4 +440,47 @@ protected String getMavenVersion(Class<?> clazz) throws IOException { return UNKNOWN_VERSION; } } + + /** + * Parses a string of arguments respecting quoted strings. + * Handles both single and double quotes, and preserves backslashes + * (important for Windows paths). + */ + static List<String> parseArguments(String args) { + List<String> result = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + boolean inDoubleQuotes = false; + boolean inSingleQuotes = false; + for (int i = 0; i < args.length(); i++) { + char c = args.charAt(i); + if (inDoubleQuotes) { + if (c == '"') { + inDoubleQuotes = false; + } else { + current.append(c); + } + } else if (inSingleQuotes) { + if (c == '\'') { + inSingleQuotes = false; + } else { + current.append(c); + } + } else if (c == '"') { + inDoubleQuotes = true; + } else if (c == '\'') { + inSingleQuotes = true; + } else if (Character.isWhitespace(c)) { + if (!current.isEmpty()) { + result.add(current.toString()); + current.setLength(0); + } + } else { + current.append(c); + } + } + if (!current.isEmpty()) { + result.add(current.toString()); + } + return result; + } } diff --git a/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutor.java b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutor.java index a559a24baf..4bea89c720 100644 --- a/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutor.java +++ b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutor.java @@ -27,7 +27,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -116,9 +115,7 @@ protected int doExecute(ExecutorRequest executorRequest) throws ExecutorExceptio String mavenArgsEnv = System.getenv("MAVEN_ARGS"); if (useMavenArgsEnv && mavenArgsEnv != null && !mavenArgsEnv.isEmpty()) { - Arrays.stream(mavenArgsEnv.split(" ")) - .filter(s -> !s.trim().isEmpty()) - .forEach(cmdAndArguments::add); + cmdAndArguments.addAll(parseArguments(mavenArgsEnv)); } cmdAndArguments.addAll(executorRequest.arguments()); @@ -223,4 +220,47 @@ public void close() throws ExecutorException { // nothing yet } } + + /** + * Parses a string of arguments respecting quoted strings. + * Handles both single and double quotes, and preserves backslashes + * (important for Windows paths). + */ + static List<String> parseArguments(String args) { + List<String> result = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + boolean inDoubleQuotes = false; + boolean inSingleQuotes = false; + for (int i = 0; i < args.length(); i++) { + char c = args.charAt(i); + if (inDoubleQuotes) { + if (c == '"') { + inDoubleQuotes = false; + } else { + current.append(c); + } + } else if (inSingleQuotes) { + if (c == '\'') { + inSingleQuotes = false; + } else { + current.append(c); + } + } else if (c == '"') { + inDoubleQuotes = true; + } else if (c == '\'') { + inSingleQuotes = true; + } else if (Character.isWhitespace(c)) { + if (!current.isEmpty()) { + result.add(current.toString()); + current.setLength(0); + } + } else { + current.append(c); + } + } + if (!current.isEmpty()) { + result.add(current.toString()); + } + return result; + } } diff --git a/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutorTest.java b/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutorTest.java index c214fc6ffe..45beda77a9 100644 --- a/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutorTest.java +++ b/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutorTest.java @@ -18,8 +18,13 @@ */ package org.apache.maven.cling.executor.embedded; +import java.util.List; + import org.apache.maven.api.cli.Executor; import org.apache.maven.cling.executor.MavenExecutorTestSupport; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Embedded executor UT @@ -30,4 +35,34 @@ public class EmbeddedMavenExecutorTest extends MavenExecutorTestSupport { protected Executor doSelectExecutor() { return new EmbeddedMavenExecutor(); } + + @Test + void testParseArgumentsSimple() { + assertEquals( + List.of("-T", "4", "clean", "install"), EmbeddedMavenExecutor.parseArguments("-T 4 clean install")); + } + + @Test + void testParseArgumentsDoubleQuoted() { + assertEquals( + List.of("-f", "C:\\Program Files\\project\\pom.xml"), + EmbeddedMavenExecutor.parseArguments("-f \"C:\\Program Files\\project\\pom.xml\"")); + } + + @Test + void testParseArgumentsSingleQuoted() { + assertEquals( + List.of("-f", "/path with spaces/pom.xml"), + EmbeddedMavenExecutor.parseArguments("-f '/path with spaces/pom.xml'")); + } + + @Test + void testParseArgumentsEmpty() { + assertEquals(List.of(), EmbeddedMavenExecutor.parseArguments("")); + } + + @Test + void testParseArgumentsExtraWhitespace() { + assertEquals(List.of("clean", "install"), EmbeddedMavenExecutor.parseArguments(" clean install ")); + } } diff --git a/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutorTest.java b/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutorTest.java index 5555e0ba34..8e6ee7cad8 100644 --- a/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutorTest.java +++ b/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutorTest.java @@ -18,8 +18,13 @@ */ package org.apache.maven.cling.executor.forked; +import java.util.List; + import org.apache.maven.api.cli.Executor; import org.apache.maven.cling.executor.MavenExecutorTestSupport; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Forked executor UT @@ -30,4 +35,33 @@ public class ForkedMavenExecutorTest extends MavenExecutorTestSupport { protected Executor doSelectExecutor() { return new ForkedMavenExecutor(); } + + @Test + void testParseArgumentsSimple() { + assertEquals(List.of("-T", "4", "clean", "install"), ForkedMavenExecutor.parseArguments("-T 4 clean install")); + } + + @Test + void testParseArgumentsDoubleQuoted() { + assertEquals( + List.of("-f", "C:\\Program Files\\project\\pom.xml"), + ForkedMavenExecutor.parseArguments("-f \"C:\\Program Files\\project\\pom.xml\"")); + } + + @Test + void testParseArgumentsSingleQuoted() { + assertEquals( + List.of("-f", "/path with spaces/pom.xml"), + ForkedMavenExecutor.parseArguments("-f '/path with spaces/pom.xml'")); + } + + @Test + void testParseArgumentsEmpty() { + assertEquals(List.of(), ForkedMavenExecutor.parseArguments("")); + } + + @Test + void testParseArgumentsExtraWhitespace() { + assertEquals(List.of("clean", "install"), ForkedMavenExecutor.parseArguments(" clean install ")); + } }
