Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
152 changes: 152 additions & 0 deletions apache-maven/src/assembly/maven/bin/JvmConfigParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* 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;

/**
* 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 <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 {
String result = parseJvmConfig(jvmConfigPath, mavenProjectBasedir);
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<String> 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<String> 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<String> parseArguments(String line) {
List<String> 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;
}
}
81 changes: 60 additions & 21 deletions apache-maven/src/assembly/maven/bin/mvn
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 \
Expand All @@ -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"
86 changes: 56 additions & 30 deletions apache-maven/src/assembly/maven/bin/mvn.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -177,38 +177,59 @@ 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%"
)

rem Run parser in a subshell (cmd /c) to ensure file handles are released before we read
cmd /c ""%JAVACMD%" "%MAVEN_HOME%\bin\JvmConfigParser.java" "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" "%MAVEN_PROJECTBASEDIR%" > "%JVM_CONFIG_TEMP%" 2>&1"
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%" (
echo Parser output: 1>&2
type "%JVM_CONFIG_TEMP%" 1>&2
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

Expand Down Expand Up @@ -251,6 +272,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% ^
Expand Down Expand Up @@ -286,4 +312,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%
Loading
Loading