Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apache-maven/src/assembly/component.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ under the License.
<includes>
<include>*.cmd</include>
<include>*.conf</include>
<include>*.java</include>
</includes>
<lineEnding>dos</lineEnding>
</fileSet>
Expand Down
132 changes: 132 additions & 0 deletions apache-maven/src/assembly/maven/bin/JvmConfigParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* 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.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;
import java.util.stream.Stream;

/**
* Parses .mvn/jvm.config file for Windows batch scripts.
* This avoids the complexity of parsing special characters (pipes, quotes, etc.) in batch scripts.
*
* Usage: java JvmConfigParser.java <jvm.config-path> <maven-project-basedir>
*
* 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) {
System.err.println("Usage: java JvmConfigParser.java <jvm.config-path> <maven-project-basedir>");
System.exit(1);
}

Path jvmConfigPath = Paths.get(args[0]);
String mavenProjectBasedir = args[1];

if (!Files.exists(jvmConfigPath)) {
// No jvm.config file - output nothing
return;
}

try (Stream<String> lines = Files.lines(jvmConfigPath, StandardCharsets.UTF_8)) {
StringBuilder result = new StringBuilder();

lines.forEach(line -> {
// Remove comments
int commentIndex = line.indexOf('#');
if (commentIndex >= 0) {
line = line.substring(0, commentIndex);
}

// Trim whitespace
line = line.trim();

// Skip empty lines
if (line.isEmpty()) {
return;
}

// Replace MAVEN_PROJECTBASEDIR placeholders
line = line.replace("${MAVEN_PROJECTBASEDIR}", mavenProjectBasedir);
line = line.replace("$MAVEN_PROJECTBASEDIR", mavenProjectBasedir);

// Parse line into individual arguments (split on spaces, respecting quotes)
List<String> parsed = parseArguments(line);

// Append each argument quoted
for (String arg : parsed) {
if (result.length() > 0) {
result.append(' ');
}
result.append('"').append(arg).append('"');
}
});

System.out.print(result.toString());
System.out.flush(); // Ensure output is flushed before exit (important on Windows)
} catch (IOException e) {
System.err.println("Error reading jvm.config: " + e.getMessage());
System.exit(1);
}
}

/**
* Parse a line into individual arguments, respecting quoted strings.
* Quotes are stripped from the arguments.
*/
private static List<String> parseArguments(String line) {
List<String> args = new ArrayList<>();
StringBuilder current = new StringBuilder();
boolean inQuotes = false;
boolean inSingleQuotes = false;

for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);

if (c == '"' && !inSingleQuotes) {
inQuotes = !inQuotes;
// Don't include the quote character itself
} else if (c == '\'' && !inQuotes) {
inSingleQuotes = !inSingleQuotes;
// Don't include the quote character itself
} else if (c == ' ' && !inQuotes && !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;
}
}

67 changes: 45 additions & 22 deletions apache-maven/src/assembly/maven/bin/mvn
Original file line number Diff line number Diff line change
Expand Up @@ -166,30 +166,55 @@ 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)
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' ' '
# Compile and run JvmConfigParser
# Use a temporary directory for compilation to avoid conflicts
jvm_parser_dir="${TMPDIR:-/tmp}/mvn-jvm-parser-$$"
mkdir -p "$jvm_parser_dir"

# Compile the parser
"$JAVACMD" -version >/dev/null 2>&1 || {
echo "Error: Java not found. Please set JAVA_HOME." >&2
return 1
}

javac_cmd="${JAVACMD%java*}javac"
if [ ! -x "$javac_cmd" ]; then
# Try to find javac in JAVA_HOME
if [ -n "$JAVA_HOME" ]; then
javac_cmd="$JAVA_HOME/bin/javac"
fi
fi

"$javac_cmd" -d "$jvm_parser_dir" "$MAVEN_HOME/bin/JvmConfigParser.java" >/dev/null 2>&1
if [ $? -eq 0 ]; then
# Run the parser and capture output
result=$("$JAVACMD" -cp "$jvm_parser_dir" JvmConfigParser "$1" "$MAVEN_PROJECTBASEDIR" 2>/dev/null)
# Clean up
rm -rf "$jvm_parser_dir"
echo "$result"
else
# Fallback: if compilation fails, just skip jvm.config
rm -rf "$jvm_parser_dir"
echo "Warning: Failed to compile JvmConfigParser, skipping jvm.config" >&2
fi
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
LAUNCHER_JAR=`echo "$MAVEN_HOME"/boot/plexus-classworlds-*.jar`
LAUNCHER_CLASS=org.codehaus.plexus.classworlds.launcher.Launcher

Expand Down Expand Up @@ -239,6 +264,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 \
Expand All @@ -251,13 +277,10 @@ 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"

eval exec "$cmd"
49 changes: 18 additions & 31 deletions apache-maven/src/assembly/maven/bin/mvn.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -177,38 +177,25 @@ 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!"
)
)
)
)
@endlocal & set JVM_CONFIG_MAVEN_OPTS=%JVM_CONFIG_MAVEN_OPTS%
rem Use Java to parse jvm.config to avoid batch script parsing issues with special characters
rem This handles pipes, quotes, @, and other special characters correctly
rem Use random temp directory to avoid conflicts between different Maven invocations
set "JVM_CONFIG_PARSER_DIR=%TEMP%\mvn-jvm-parser-%RANDOM%-%RANDOM%"
mkdir "%JVM_CONFIG_PARSER_DIR%"
set "JVM_CONFIG_TEMP=%TEMP%\mvn-jvm-config-%RANDOM%.txt"
"%JAVACMD:java.exe=javac.exe%" -d "%JVM_CONFIG_PARSER_DIR%" "%MAVEN_HOME%\bin\JvmConfigParser.java" >nul 2>&1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as on posix/unix: why run javac? We are on 17 and can easily run single-source files using java.

"%JAVACMD%" -cp "%JVM_CONFIG_PARSER_DIR%" JvmConfigParser "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" "%MAVEN_PROJECTBASEDIR%" > "%JVM_CONFIG_TEMP%" 2>nul
rem Read the single line from temp file
set /p JVM_CONFIG_MAVEN_OPTS=<"%JVM_CONFIG_TEMP%"

rem Cleanup temp files and directory
del "%JVM_CONFIG_TEMP%" 2>nul
rmdir /s /q "%JVM_CONFIG_PARSER_DIR%" 2>nul

:endReadJvmConfig

Expand Down Expand Up @@ -286,4 +273,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%
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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 <a href="https://github.com/apache/maven/issues/11363">gh-11363</a>:
* 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
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"));
}
}
Loading
Loading