diff --git a/apache-maven/src/assembly/component.xml b/apache-maven/src/assembly/component.xml
index 4d75c9a38ca8..5f55a310c8bd 100644
--- a/apache-maven/src/assembly/component.xml
+++ b/apache-maven/src/assembly/component.xml
@@ -68,6 +68,7 @@ under the License.
*.cmd
*.conf
+ *.java
dos
diff --git a/apache-maven/src/assembly/maven/bin/JvmConfigParser.java b/apache-maven/src/assembly/maven/bin/JvmConfigParser.java
new file mode 100644
index 000000000000..41b87569dca1
--- /dev/null
+++ b/apache-maven/src/assembly/maven/bin/JvmConfigParser.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.
+ */
+
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parses .mvn/jvm.config file for Windows batch/Unix shell scripts.
+ * This avoids the complexity of parsing special characters (pipes, quotes, etc.) in scripts.
+ *
+ * Usage: java JvmConfigParser.java [output-file]
+ *
+ * If output-file is provided, writes result to that file (avoids Windows file locking issues).
+ * Otherwise, outputs to stdout.
+ *
+ * Outputs: Single line with space-separated quoted arguments (safe for batch scripts)
+ */
+public class JvmConfigParser {
+ public static void main(String[] args) {
+ if (args.length < 2 || args.length > 3) {
+ System.err.println("Usage: java JvmConfigParser.java [output-file]");
+ System.exit(1);
+ }
+
+ Path jvmConfigPath = Paths.get(args[0]);
+ String mavenProjectBasedir = args[1];
+ Path outputFile = args.length == 3 ? Paths.get(args[2]) : null;
+
+ if (!Files.exists(jvmConfigPath)) {
+ // No jvm.config file - output nothing (create empty file if output specified)
+ if (outputFile != null) {
+ try {
+ Files.writeString(outputFile, "", StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ System.err.println("ERROR: Failed to write output file: " + e.getMessage());
+ System.err.flush();
+ System.exit(1);
+ }
+ }
+ return;
+ }
+
+ try {
+ String result = parseJvmConfig(jvmConfigPath, mavenProjectBasedir);
+ if (outputFile != null) {
+ // Write directly to file - this ensures proper file handle cleanup on Windows
+ // Add newline at end for Windows 'for /f' command compatibility
+ try (Writer writer = Files.newBufferedWriter(outputFile, StandardCharsets.UTF_8)) {
+ writer.write(result);
+ if (!result.isEmpty()) {
+ writer.write(System.lineSeparator());
+ }
+ }
+ } else {
+ System.out.print(result);
+ System.out.flush();
+ }
+ } catch (IOException e) {
+ // If jvm.config exists but can't be read, this is a configuration error
+ // Print clear error and exit with error code to prevent Maven from running
+ System.err.println("ERROR: Failed to read .mvn/jvm.config: " + e.getMessage());
+ System.err.println("Please check file permissions and syntax.");
+ System.err.flush();
+ System.exit(1);
+ }
+ }
+
+ /**
+ * Parse jvm.config file and return formatted arguments.
+ * Package-private for testing.
+ */
+ static String parseJvmConfig(Path jvmConfigPath, String mavenProjectBasedir) throws IOException {
+ StringBuilder result = new StringBuilder();
+
+ for (String line : Files.readAllLines(jvmConfigPath, StandardCharsets.UTF_8)) {
+ line = processLine(line, mavenProjectBasedir);
+ if (line.isEmpty()) {
+ continue;
+ }
+
+ List parsed = parseArguments(line);
+ appendQuotedArguments(result, parsed);
+ }
+
+ return result.toString();
+ }
+
+ /**
+ * Process a single line: remove comments, trim whitespace, and replace placeholders.
+ */
+ private static String processLine(String line, String mavenProjectBasedir) {
+ // Remove comments
+ int commentIndex = line.indexOf('#');
+ if (commentIndex >= 0) {
+ line = line.substring(0, commentIndex);
+ }
+
+ // Trim whitespace
+ line = line.trim();
+
+ // Replace MAVEN_PROJECTBASEDIR placeholders
+ line = line.replace("${MAVEN_PROJECTBASEDIR}", mavenProjectBasedir);
+ line = line.replace("$MAVEN_PROJECTBASEDIR", mavenProjectBasedir);
+
+ return line;
+ }
+
+ /**
+ * Append parsed arguments as quoted strings to the result builder.
+ */
+ private static void appendQuotedArguments(StringBuilder result, List args) {
+ for (String arg : args) {
+ if (result.length() > 0) {
+ result.append(' ');
+ }
+ result.append('"').append(arg).append('"');
+ }
+ }
+
+ /**
+ * Parse a line into individual arguments, respecting quoted strings.
+ * Quotes are stripped from the arguments.
+ */
+ private static List parseArguments(String line) {
+ List args = new ArrayList<>();
+ StringBuilder current = new StringBuilder();
+ boolean inDoubleQuotes = false;
+ boolean inSingleQuotes = false;
+
+ for (int i = 0; i < line.length(); i++) {
+ char c = line.charAt(i);
+
+ if (c == '"' && !inSingleQuotes) {
+ inDoubleQuotes = !inDoubleQuotes;
+ } else if (c == '\'' && !inDoubleQuotes) {
+ inSingleQuotes = !inSingleQuotes;
+ } else if (c == ' ' && !inDoubleQuotes && !inSingleQuotes) {
+ // Space outside quotes - end of argument
+ if (current.length() > 0) {
+ args.add(current.toString());
+ current.setLength(0);
+ }
+ } else {
+ current.append(c);
+ }
+ }
+
+ // Add last argument
+ if (current.length() > 0) {
+ args.add(current.toString());
+ }
+
+ return args;
+ }
+}
\ No newline at end of file
diff --git a/apache-maven/src/assembly/maven/bin/mvn b/apache-maven/src/assembly/maven/bin/mvn
index 8559d47af557..1a8e6a2fdccc 100755
--- a/apache-maven/src/assembly/maven/bin/mvn
+++ b/apache-maven/src/assembly/maven/bin/mvn
@@ -166,30 +166,66 @@ find_file_argument_basedir() {
}
# concatenates all lines of a file and replaces variables
+# Uses Java-based parser to handle all special characters correctly
+# This avoids shell parsing issues with pipes, quotes, @, and other special characters
+# and ensures POSIX compliance (no xargs -0, awk, or complex sed needed)
+# Set MAVEN_DEBUG_SCRIPT=1 to enable debug logging
concat_lines() {
if [ -f "$1" ]; then
- # First convert all CR to LF using tr
- tr '\r' '\n' < "$1" | \
- sed -e '/^$/d' -e 's/#.*$//' | \
- # Replace LF with NUL for xargs
- tr '\n' '\0' | \
- # Split into words and process each argument
- # Use -0 with NUL to avoid special behaviour on quotes
- xargs -n 1 -0 | \
- while read -r arg; do
- # Replace variables first
- arg=$(echo "$arg" | sed \
- -e "s@\${MAVEN_PROJECTBASEDIR}@$MAVEN_PROJECTBASEDIR@g" \
- -e "s@\$MAVEN_PROJECTBASEDIR@$MAVEN_PROJECTBASEDIR@g")
-
- echo "$arg"
- done | \
- tr '\n' ' '
+ # Use Java source-launch mode (JDK 11+) to run JvmConfigParser directly
+ # This avoids the need for compilation and temporary directories
+
+ # Debug logging
+ if [ -n "$MAVEN_DEBUG_SCRIPT" ]; then
+ echo "[DEBUG] Found jvm.config file at: $1" >&2
+ echo "[DEBUG] Running JvmConfigParser with Java: $JAVACMD" >&2
+ echo "[DEBUG] Parser arguments: $MAVEN_HOME/bin/JvmConfigParser.java $1 $MAVEN_PROJECTBASEDIR" >&2
+ fi
+
+ # Verify Java is available
+ "$JAVACMD" -version >/dev/null 2>&1 || {
+ echo "Error: Java not found. Please set JAVA_HOME." >&2
+ return 1
+ }
+
+ # Run the parser using source-launch mode
+ # Capture both stdout and stderr for comprehensive error reporting
+ parser_output=$("$JAVACMD" "$MAVEN_HOME/bin/JvmConfigParser.java" "$1" "$MAVEN_PROJECTBASEDIR" 2>&1)
+ parser_exit=$?
+
+ if [ -n "$MAVEN_DEBUG_SCRIPT" ]; then
+ echo "[DEBUG] JvmConfigParser exit code: $parser_exit" >&2
+ echo "[DEBUG] JvmConfigParser output: $parser_output" >&2
+ fi
+
+ if [ $parser_exit -ne 0 ]; then
+ # Parser failed - print comprehensive error information
+ echo "ERROR: JvmConfigParser failed with exit code $parser_exit" >&2
+ echo " jvm.config path: $1" >&2
+ echo " Maven basedir: $MAVEN_PROJECTBASEDIR" >&2
+ echo " Java command: $JAVACMD" >&2
+ echo " Parser output:" >&2
+ echo "$parser_output" | sed 's/^/ /' >&2
+ exit 1
+ fi
+
+ echo "$parser_output"
fi
}
MAVEN_PROJECTBASEDIR="`find_maven_basedir "$@"`"
-MAVEN_OPTS="$MAVEN_OPTS `concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config"`"
+# Read JVM config and append to MAVEN_OPTS, preserving special characters
+_jvm_config="`concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config"`"
+if [ -n "$_jvm_config" ]; then
+ if [ -n "$MAVEN_OPTS" ]; then
+ MAVEN_OPTS="$MAVEN_OPTS $_jvm_config"
+ else
+ MAVEN_OPTS="$_jvm_config"
+ fi
+fi
+if [ -n "$MAVEN_DEBUG_SCRIPT" ]; then
+ echo "[DEBUG] Final MAVEN_OPTS: $MAVEN_OPTS" >&2
+fi
LAUNCHER_JAR=`echo "$MAVEN_HOME"/boot/plexus-classworlds-*.jar`
LAUNCHER_CLASS=org.codehaus.plexus.classworlds.launcher.Launcher
@@ -239,6 +275,7 @@ handle_args() {
handle_args "$@"
MAVEN_MAIN_CLASS=${MAVEN_MAIN_CLASS:=org.apache.maven.cling.MavenCling}
+# Build command string for eval
cmd="\"$JAVACMD\" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
@@ -251,13 +288,15 @@ cmd="\"$JAVACMD\" \
\"-Dmaven.multiModuleProjectDirectory=$MAVEN_PROJECTBASEDIR\" \
$LAUNCHER_CLASS \
$MAVEN_ARGS"
+
# Add remaining arguments with proper quoting
for arg in "$@"; do
cmd="$cmd \"$arg\""
done
-# Debug: print the command that will be executed
-#echo "About to execute:"
-#echo "$cmd"
+if [ -n "$MAVEN_DEBUG_SCRIPT" ]; then
+ echo "[DEBUG] Launching JVM with command:" >&2
+ echo "[DEBUG] $cmd" >&2
+fi
eval exec "$cmd"
diff --git a/apache-maven/src/assembly/maven/bin/mvn.cmd b/apache-maven/src/assembly/maven/bin/mvn.cmd
index a3e8600df3d1..f25f85858f7a 100644
--- a/apache-maven/src/assembly/maven/bin/mvn.cmd
+++ b/apache-maven/src/assembly/maven/bin/mvn.cmd
@@ -177,38 +177,57 @@ cd /d "%EXEC_DIR%"
:endDetectBaseDir
+rem Initialize JVM_CONFIG_MAVEN_OPTS to empty to avoid inheriting from environment
+set JVM_CONFIG_MAVEN_OPTS=
+
if not exist "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadJvmConfig
-@setlocal EnableExtensions EnableDelayedExpansion
-set JVM_CONFIG_MAVEN_OPTS=
-for /F "usebackq tokens=* delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do (
- set "line=%%a"
-
- rem Skip empty lines and full-line comments
- echo !line! | findstr /b /r /c:"[ ]*#" >nul
- if errorlevel 1 (
- rem Handle end-of-line comments by taking everything before #
- for /f "tokens=1* delims=#" %%i in ("!line!") do set "line=%%i"
-
- rem Trim leading/trailing spaces while preserving spaces in quotes
- set "trimmed=!line!"
- for /f "tokens=* delims= " %%i in ("!trimmed!") do set "trimmed=%%i"
- for /l %%i in (1,1,100) do if "!trimmed:~-1!"==" " set "trimmed=!trimmed:~0,-1!"
-
- rem Replace MAVEN_PROJECTBASEDIR placeholders
- set "trimmed=!trimmed:${MAVEN_PROJECTBASEDIR}=%MAVEN_PROJECTBASEDIR%!"
- set "trimmed=!trimmed:$MAVEN_PROJECTBASEDIR=%MAVEN_PROJECTBASEDIR%!"
-
- if not "!trimmed!"=="" (
- if "!JVM_CONFIG_MAVEN_OPTS!"=="" (
- set "JVM_CONFIG_MAVEN_OPTS=!trimmed!"
- ) else (
- set "JVM_CONFIG_MAVEN_OPTS=!JVM_CONFIG_MAVEN_OPTS! !trimmed!"
- )
- )
- )
+rem Use Java source-launch mode (JDK 11+) to parse jvm.config
+rem This avoids batch script parsing issues with special characters (pipes, quotes, @, etc.)
+rem Use temp file approach with cmd /c to ensure proper file handle release
+
+set "JVM_CONFIG_TEMP=%TEMP%\mvn-jvm-config-%RANDOM%-%RANDOM%.txt"
+
+rem Debug logging (set MAVEN_DEBUG_SCRIPT=1 to enable)
+if defined MAVEN_DEBUG_SCRIPT (
+ echo [DEBUG] Found .mvn\jvm.config file at: %MAVEN_PROJECTBASEDIR%\.mvn\jvm.config
+ echo [DEBUG] Using temp file: %JVM_CONFIG_TEMP%
+ echo [DEBUG] Running JvmConfigParser with Java: %JAVACMD%
+ echo [DEBUG] Parser arguments: "%MAVEN_HOME%\bin\JvmConfigParser.java" "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" "%MAVEN_PROJECTBASEDIR%" "%JVM_CONFIG_TEMP%"
+)
+
+rem Run parser with output file as third argument - Java writes directly to file to avoid Windows file locking issues
+"%JAVACMD%" "%MAVEN_HOME%\bin\JvmConfigParser.java" "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" "%MAVEN_PROJECTBASEDIR%" "%JVM_CONFIG_TEMP%"
+set JVM_CONFIG_EXIT=%ERRORLEVEL%
+
+if defined MAVEN_DEBUG_SCRIPT (
+ echo [DEBUG] JvmConfigParser exit code: %JVM_CONFIG_EXIT%
+)
+
+rem Check if parser failed
+if %JVM_CONFIG_EXIT% neq 0 (
+ echo ERROR: Failed to parse .mvn/jvm.config file 1>&2
+ echo jvm.config path: %MAVEN_PROJECTBASEDIR%\.mvn\jvm.config 1>&2
+ echo Java command: %JAVACMD% 1>&2
+ if exist "%JVM_CONFIG_TEMP%" (
+ del "%JVM_CONFIG_TEMP%" 2>nul
+ )
+ exit /b 1
+)
+
+rem Read the output file
+if exist "%JVM_CONFIG_TEMP%" (
+ if defined MAVEN_DEBUG_SCRIPT (
+ echo [DEBUG] Temp file contents:
+ type "%JVM_CONFIG_TEMP%"
+ )
+ for /f "usebackq tokens=*" %%i in ("%JVM_CONFIG_TEMP%") do set "JVM_CONFIG_MAVEN_OPTS=%%i"
+ del "%JVM_CONFIG_TEMP%" 2>nul
+)
+
+if defined MAVEN_DEBUG_SCRIPT (
+ echo [DEBUG] Final JVM_CONFIG_MAVEN_OPTS: %JVM_CONFIG_MAVEN_OPTS%
)
-@endlocal & set JVM_CONFIG_MAVEN_OPTS=%JVM_CONFIG_MAVEN_OPTS%
:endReadJvmConfig
@@ -251,6 +270,11 @@ for %%i in ("%MAVEN_HOME%"\boot\plexus-classworlds-*) do set LAUNCHER_JAR="%%i"
set LAUNCHER_CLASS=org.codehaus.plexus.classworlds.launcher.Launcher
if "%MAVEN_MAIN_CLASS%"=="" @set MAVEN_MAIN_CLASS=org.apache.maven.cling.MavenCling
+if defined MAVEN_DEBUG_SCRIPT (
+ echo [DEBUG] Launching JVM with command:
+ echo [DEBUG] "%JAVACMD%" %INTERNAL_MAVEN_OPTS% %MAVEN_OPTS% %JVM_CONFIG_MAVEN_OPTS% %MAVEN_DEBUG_OPTS% --enable-native-access=ALL-UNNAMED -classpath %LAUNCHER_JAR% "-Dclassworlds.conf=%CLASSWORLDS_CONF%" "-Dmaven.home=%MAVEN_HOME%" "-Dmaven.mainClass=%MAVEN_MAIN_CLASS%" "-Dlibrary.jline.path=%MAVEN_HOME%\lib\jline-native" "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %LAUNCHER_CLASS% %MAVEN_ARGS% %*
+)
+
"%JAVACMD%" ^
%INTERNAL_MAVEN_OPTS% ^
%MAVEN_OPTS% ^
@@ -286,4 +310,4 @@ if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
-exit /b %ERROR_CODE%
+exit /b %ERROR_CODE%
\ No newline at end of file
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh10937QuotedPipesInMavenOptsTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh10937QuotedPipesInMavenOptsTest.java
index 9d954afb4f35..0765147c8011 100644
--- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh10937QuotedPipesInMavenOptsTest.java
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh10937QuotedPipesInMavenOptsTest.java
@@ -42,6 +42,8 @@ void testIt() throws Exception {
Verifier verifier = newVerifier(basedir.toString());
verifier.setEnvironmentVariable("MAVEN_OPTS", "-Dprop.maven-opts=\"foo|bar\"");
+ // Enable debug logging for launcher script to diagnose jvm.config parsing issues
+ verifier.setEnvironmentVariable("MAVEN_DEBUG_SCRIPT", "1");
verifier.addCliArguments("validate");
verifier.execute();
verifier.verifyErrorFreeLog();
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11363PipeSymbolsInJvmConfigTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11363PipeSymbolsInJvmConfigTest.java
new file mode 100644
index 000000000000..4603bf09cf9c
--- /dev/null
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11363PipeSymbolsInJvmConfigTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.apache.maven.it;
+
+import java.nio.file.Path;
+import java.util.Properties;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * This is a test set for gh-11363:
+ * Verify that pipe symbols in .mvn/jvm.config are properly handled and don't cause shell command parsing errors.
+ */
+public class MavenITgh11363PipeSymbolsInJvmConfigTest extends AbstractMavenIntegrationTestCase {
+
+ /**
+ * Verify that pipe symbols in .mvn/jvm.config are properly handled
+ */
+ @Test
+ void testPipeSymbolsInJvmConfig() throws Exception {
+ Path basedir = extractResources("/gh-11363-pipe-symbols-jvm-config")
+ .getAbsoluteFile()
+ .toPath();
+
+ Verifier verifier = newVerifier(basedir.toString());
+ verifier.setForkJvm(true); // Use forked JVM to test .mvn/jvm.config processing
+ // Enable debug logging for launcher script to diagnose jvm.config parsing issues
+ verifier.setEnvironmentVariable("MAVEN_DEBUG_SCRIPT", "1");
+ verifier.addCliArguments("validate");
+ verifier.execute();
+ verifier.verifyErrorFreeLog();
+
+ Properties props = verifier.loadProperties("target/pom.properties");
+ assertEquals("de|*.de|my.company.mirror.de", props.getProperty("project.properties.pom.prop.nonProxyHosts"));
+ assertEquals("value|with|pipes", props.getProperty("project.properties.pom.prop.with.pipes"));
+ }
+}
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11485AtSignInJvmConfigTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11485AtSignInJvmConfigTest.java
new file mode 100644
index 000000000000..c23128946ea7
--- /dev/null
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11485AtSignInJvmConfigTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.apache.maven.it;
+
+import java.io.File;
+import java.util.Properties;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * This is a test set for GH-11485:
+ * Verify that @ character in .mvn/jvm.config values is handled correctly.
+ * This is important for Jenkins workspaces like workspace/project_PR-350@2
+ */
+public class MavenITgh11485AtSignInJvmConfigTest extends AbstractMavenIntegrationTestCase {
+
+ @Test
+ public void testAtSignInJvmConfig() throws Exception {
+ File testDir = extractResources("/gh-11485-at-sign");
+
+ Verifier verifier = newVerifier(testDir.getAbsolutePath());
+ verifier.addCliArgument(
+ "-Dexpression.outputFile=" + new File(testDir, "target/pom.properties").getAbsolutePath());
+ verifier.setForkJvm(true); // custom .mvn/jvm.config
+ // Enable debug logging for launcher script to diagnose jvm.config parsing issues
+ verifier.setEnvironmentVariable("MAVEN_DEBUG_SCRIPT", "1");
+ verifier.addCliArgument("validate");
+ verifier.execute();
+ verifier.verifyErrorFreeLog();
+
+ Properties props = verifier.loadProperties("target/pom.properties");
+ String expectedPath = testDir.getAbsolutePath().replace('\\', '/');
+ assertEquals(
+ expectedPath + "/workspace@2/test",
+ props.getProperty("project.properties.pathWithAtProp").replace('\\', '/'),
+ "Path with @ character should be preserved");
+ assertEquals(
+ "value@test",
+ props.getProperty("project.properties.propWithAtProp"),
+ "Property value with @ character should be preserved");
+ }
+
+ @Test
+ public void testAtSignInCommandLineProperty() throws Exception {
+ File testDir = extractResources("/gh-11485-at-sign");
+
+ Verifier verifier = newVerifier(testDir.getAbsolutePath());
+ verifier.addCliArgument(
+ "-Dexpression.outputFile=" + new File(testDir, "target/pom.properties").getAbsolutePath());
+ verifier.setForkJvm(true); // custom .mvn/jvm.config
+ // Pass a path with @ character via command line (simulating Jenkins workspace)
+ String jenkinsPath = testDir.getAbsolutePath().replace('\\', '/') + "/jenkins.workspace/proj@2";
+ verifier.addCliArgument("-Dcmdline.path=" + jenkinsPath);
+ verifier.addCliArgument("-Dcmdline.value=test@value");
+ verifier.addCliArgument("validate");
+ verifier.execute();
+ verifier.verifyErrorFreeLog();
+
+ Properties props = verifier.loadProperties("target/pom.properties");
+ assertEquals(
+ jenkinsPath,
+ props.getProperty("project.properties.cmdlinePath").replace('\\', '/'),
+ "Command-line path with @ character should be preserved");
+ assertEquals(
+ "test@value",
+ props.getProperty("project.properties.cmdlineValue"),
+ "Command-line value with @ character should be preserved");
+ }
+}
+
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng4559SpacesInJvmOptsTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng4559SpacesInJvmOptsTest.java
index e7279842264d..78799608bdef 100644
--- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng4559SpacesInJvmOptsTest.java
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng4559SpacesInJvmOptsTest.java
@@ -42,6 +42,8 @@ void testIt() throws Exception {
Verifier verifier = newVerifier(basedir.toString());
verifier.setEnvironmentVariable("MAVEN_OPTS", "-Dprop.maven-opts=\"foo bar\"");
+ // Enable debug logging for launcher script to diagnose jvm.config parsing issues
+ verifier.setEnvironmentVariable("MAVEN_DEBUG_SCRIPT", "1");
verifier.addCliArguments("validate");
verifier.execute();
verifier.verifyErrorFreeLog();
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6255FixConcatLines.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6255FixConcatLines.java
index 687aa9a4dc7f..6ac39e4c8956 100644
--- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6255FixConcatLines.java
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6255FixConcatLines.java
@@ -46,7 +46,7 @@ class MavenITmng6255FixConcatLines extends AbstractMavenIntegrationTestCase {
@Test
@Disabled
void testJvmConfigFileCR() throws Exception {
- runWithLineEndings("\r");
+ runWithLineEndings("\r", "cr");
}
/**
@@ -56,7 +56,7 @@ void testJvmConfigFileCR() throws Exception {
*/
@Test
void testJvmConfigFileLF() throws Exception {
- runWithLineEndings("\n");
+ runWithLineEndings("\n", "lf");
}
/**
@@ -66,10 +66,10 @@ void testJvmConfigFileLF() throws Exception {
*/
@Test
void testJvmConfigFileCRLF() throws Exception {
- runWithLineEndings("\r\n");
+ runWithLineEndings("\r\n", "crlf");
}
- protected void runWithLineEndings(String lineEndings) throws Exception {
+ protected void runWithLineEndings(String lineEndings, String test) throws Exception {
File baseDir = extractResources("/mng-6255");
File mvnDir = new File(baseDir, ".mvn");
@@ -77,14 +77,16 @@ protected void runWithLineEndings(String lineEndings) throws Exception {
createJvmConfigFile(jvmConfig, lineEndings, "-Djvm.config=ok", "-Xms256m", "-Xmx512m");
Verifier verifier = newVerifier(baseDir.getAbsolutePath());
+ // Use different log file for each test to avoid overwriting
+ verifier.setLogFileName("log-" + test + ".txt");
verifier.addCliArgument(
- "-Dexpression.outputFile=" + new File(baseDir, "expression.properties").getAbsolutePath());
+ "-Dexpression.outputFile=" + new File(baseDir, "expression-" + test + ".properties").getAbsolutePath());
verifier.setForkJvm(true); // custom .mvn/jvm.config
verifier.addCliArgument("validate");
verifier.execute();
verifier.verifyErrorFreeLog();
- Properties props = verifier.loadProperties("expression.properties");
+ Properties props = verifier.loadProperties("expression-" + test + ".properties");
assertEquals("ok", props.getProperty("project.properties.jvm-config"));
}
diff --git a/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/.mvn/jvm.config b/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/.mvn/jvm.config
new file mode 100644
index 000000000000..fa129e3da219
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/.mvn/jvm.config
@@ -0,0 +1,3 @@
+# Test for MNG-11363: Maven 4 fails to parse pipe symbols in .mvn/jvm.config
+-Dhttp.nonProxyHosts=de|*.de|my.company.mirror.de
+-Dprop.with.pipes="value|with|pipes"
diff --git a/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/pom.xml b/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/pom.xml
new file mode 100644
index 000000000000..52f90ad94181
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/pom.xml
@@ -0,0 +1,59 @@
+
+
+
+ 4.0.0
+
+ org.apache.maven.its.mng11363
+ test
+ 1.0
+
+ Maven Integration Test :: MNG-11363
+ Verify that JVM args can contain pipe symbols in .mvn/jvm.config.
+
+
+ ${http.nonProxyHosts}
+ ${prop.with.pipes}
+
+
+
+
+
+ org.apache.maven.its.plugins
+ maven-it-plugin-expression
+ 2.1-SNAPSHOT
+
+
+ test
+
+ eval
+
+ validate
+
+ target/pom.properties
+
+ project/properties
+
+
+
+
+
+
+
+
diff --git a/its/core-it-suite/src/test/resources/gh-11485-at-sign/.mvn/jvm.config b/its/core-it-suite/src/test/resources/gh-11485-at-sign/.mvn/jvm.config
new file mode 100644
index 000000000000..ec92d7c5f558
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11485-at-sign/.mvn/jvm.config
@@ -0,0 +1,3 @@
+-Dpath.with.at=${MAVEN_PROJECTBASEDIR}/workspace@2/test
+-Dprop.with.at=value@test
+
diff --git a/its/core-it-suite/src/test/resources/gh-11485-at-sign/pom.xml b/its/core-it-suite/src/test/resources/gh-11485-at-sign/pom.xml
new file mode 100644
index 000000000000..9fdbc2444b6e
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11485-at-sign/pom.xml
@@ -0,0 +1,70 @@
+
+
+
+ 4.0.0
+
+ org.apache.maven.its.gh11485
+ test
+ 1.0
+ pom
+
+ Test @ character in jvm.config
+
+ Verify that @ character in jvm.config values is handled correctly.
+ This is important for Jenkins workspaces like workspace/project_PR-350@2
+
+
+
+ ${path.with.at}
+ ${prop.with.at}
+ ${cmdline.path}
+ ${cmdline.value}
+
+
+
+
+
+ org.apache.maven.its.plugins
+ maven-it-plugin-expression
+ 2.1-SNAPSHOT
+
+
+ validate
+
+ eval
+
+
+ target/pom.properties
+
+ project/properties/pathWithAtProp
+ project/properties/propWithAtProp
+ project/properties/cmdlinePath
+ project/properties/cmdlineValue
+
+
+
+
+
+
+
+
+
diff --git a/its/core-it-support/maven-it-helper/src/main/java/org/apache/maven/it/Verifier.java b/its/core-it-support/maven-it-helper/src/main/java/org/apache/maven/it/Verifier.java
index 1904d9715208..d8c678058038 100644
--- a/its/core-it-support/maven-it-helper/src/main/java/org/apache/maven/it/Verifier.java
+++ b/its/core-it-support/maven-it-helper/src/main/java/org/apache/maven/it/Verifier.java
@@ -296,6 +296,30 @@ public void execute() throws VerificationException {
System.err.println("Warning: Could not prepend command line to log file: " + e.getMessage());
}
}
+
+ // Save stdout/stderr to files if not empty (captures shell script debug output)
+ if (logFileName != null) {
+ String logBaseName = logFileName.endsWith(".txt")
+ ? logFileName.substring(0, logFileName.length() - 4)
+ : logFileName;
+ if (stdout.size() > 0) {
+ try {
+ Path stdoutFile = basedir.resolve(logBaseName + "-stdout.txt");
+ Files.writeString(stdoutFile, stdout.toString(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ System.err.println("Warning: Could not write stdout file: " + e.getMessage());
+ }
+ }
+ if (stderr.size() > 0) {
+ try {
+ Path stderrFile = basedir.resolve(logBaseName + "-stderr.txt");
+ Files.writeString(stderrFile, stderr.toString(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ System.err.println("Warning: Could not write stderr file: " + e.getMessage());
+ }
+ }
+ }
+
if (ret > 0) {
String dump;
try {
@@ -478,15 +502,18 @@ private String formatCommandLine(ExecutorRequest request, ExecutorHelper.Mode mo
}
}
- // Add environment variables that would be set
+ // Add environment variables that would be set (excluding MAVEN_OPTS which is handled separately)
if (request.environmentVariables().isPresent() && !request.environmentVariables().get().isEmpty()) {
cmdLine.append("\n# Environment variables:");
for (Map.Entry entry : request.environmentVariables().get().entrySet()) {
- cmdLine.append("\n# ").append(entry.getKey()).append("=").append(entry.getValue());
+ if (!"MAVEN_OPTS".equals(entry.getKey())) {
+ cmdLine.append("\n# ").append(entry.getKey()).append("=").append(entry.getValue());
+ }
}
}
- // Add JVM arguments that would be set via MAVEN_OPTS
+ // Compute the final MAVEN_OPTS value (combining env var + jvmArgs)
+ // This matches what ForkedMavenExecutor does
List jvmArgs = new ArrayList<>();
if (!request.userHomeDirectory().equals(ExecutorRequest.getCanonicalPath(Paths.get(System.getProperty("user.home"))))) {
jvmArgs.add("-Duser.home=" + request.userHomeDirectory().toString());
@@ -500,8 +527,23 @@ private String formatCommandLine(ExecutorRequest request, ExecutorHelper.Mode mo
.toList());
}
+ // Build the final MAVEN_OPTS value
+ StringBuilder mavenOpts = new StringBuilder();
+ if (request.environmentVariables().isPresent()) {
+ String existingMavenOpts = request.environmentVariables().get().get("MAVEN_OPTS");
+ if (existingMavenOpts != null && !existingMavenOpts.isEmpty()) {
+ mavenOpts.append(existingMavenOpts);
+ }
+ }
if (!jvmArgs.isEmpty()) {
- cmdLine.append("\n# MAVEN_OPTS=").append(String.join(" ", jvmArgs));
+ if (mavenOpts.length() > 0) {
+ mavenOpts.append(" ");
+ }
+ mavenOpts.append(String.join(" ", jvmArgs));
+ }
+
+ if (mavenOpts.length() > 0) {
+ cmdLine.append("\n# MAVEN_OPTS=").append(mavenOpts.toString());
}
if (request.skipMavenRc()) {