You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/07/28 19:48:52 UTC
(maven) branch maven-3.10.x updated: [3.10.x] Convert paths for MSYS2 and jvm.config placeholders in bin/mvn (#12567)
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch maven-3.10.x
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/maven-3.10.x by this push:
new 9a6120ee84 [3.10.x] Convert paths for MSYS2 and jvm.config placeholders in bin/mvn (#12567)
9a6120ee84 is described below
commit 9a6120ee84ba05cadc6b07f6ea9a0075386d02c5
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Tue Jul 28 21:48:24 2026 +0200
[3.10.x] Convert paths for MSYS2 and jvm.config placeholders in bin/mvn (#12567)
Backport of #12546 to the 3.10.x branch. Adapted for the different script
location (src/bin/mvn vs src/assembly/maven/bin/mvn) and the simpler
jvm.config handling (no JvmConfigParser on this branch).
Key changes:
- Detect MSYS2 (MSYS_NT-*) alongside MinGW in uname matching
- Guard against missing cygpath(1) — warn and fall back to POSIX paths
- Convert all paths (MAVEN_HOME, CLASSWORLDS_JAR, MAVEN_PROJECTBASEDIR,
jline-native) for both Cygwin and MinGW/MSYS2, not just Cygwin
- Convert jline-native path separately to avoid mixed separators
- Use printf instead of echo for values holding paths (avoid backslash
expansion in dash/ash)
- Add shell test covering Cygwin, MinGW, MSYS2, and missing-cygpath
fallback, wired into the build via exec-maven-plugin
Co-authored-by: Claude Opus 4.6 <no...@anthropic.com>
---
apache-maven/pom.xml | 36 +++
apache-maven/src/bin/mvn | 53 +++--
.../src/test/scripts/test-mvn-path-conversion.sh | 250 +++++++++++++++++++++
3 files changed, 326 insertions(+), 13 deletions(-)
diff --git a/apache-maven/pom.xml b/apache-maven/pom.xml
index 777ea58b95..9399fd4a6a 100644
--- a/apache-maven/pom.xml
+++ b/apache-maven/pom.xml
@@ -357,5 +357,41 @@ under the License.
<finalName>${project.artifactId}</finalName>
</build>
</profile>
+ <!-- Runs the bin/mvn path conversion test as part of the build. The test is
+ a POSIX shell script driving the launcher, so it only applies to the
+ platforms that use bin/mvn; Windows ships and runs bin/mvn.cmd. -->
+ <profile>
+ <id>shell-script-tests</id>
+ <activation>
+ <os>
+ <family>unix</family>
+ </os>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>3.6.3</version>
+ <executions>
+ <execution>
+ <id>test-mvn-path-conversion</id>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ <phase>test</phase>
+ <configuration>
+ <skip>${skipTests}</skip>
+ <executable>sh</executable>
+ <arguments>
+ <argument>${project.basedir}/src/test/scripts/test-mvn-path-conversion.sh</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
</profiles>
</project>
diff --git a/apache-maven/src/bin/mvn b/apache-maven/src/bin/mvn
index 2b3dc375fc..0b53cde28d 100755
--- a/apache-maven/src/bin/mvn
+++ b/apache-maven/src/bin/mvn
@@ -44,14 +44,37 @@ if [ -z "$MAVEN_SKIP_RC" ] ; then
fi
+# Note on echo vs printf: several POSIX shells (dash and the other Almquist
+# derivatives commonly installed as /bin/sh) expand backslash escapes in echo.
+# Windows paths are full of backslashes, so C:\tmp\build would be printed with a
+# tab in place of \t. Every value that may hold a path is therefore emitted with
+# printf '%s' rather than echo; plain literals may still use echo.
+
# OS specific support. $var _must_ be set to either true or false.
+# MINGW* matches the MinGW32/MinGW64 shells (e.g. Git Bash, the shell used by
+# "shell: bash" steps of GitHub Actions Windows runners), MSYS* the MSYS2 shell.
+# All of them run on top of a POSIX emulation layer that mangles POSIX paths
+# passed to native (Windows) processes such as the JVM, so paths have to be
+# converted to their native Windows form explicitly.
cygwin=false;
mingw=false;
case "`uname`" in
CYGWIN*) cygwin=true;;
- MINGW*) mingw=true;;
+ MINGW*|MSYS*) mingw=true;;
esac
+# Cygwin and MSYS2 both ship cygpath(1), but bail out of the path conversion if
+# it is unexpectedly missing: substituting empty paths would break the launch in
+# a way that is much harder to diagnose than keeping the POSIX paths.
+if $cygwin || $mingw ; then
+ if ! ( \unset -f command; \command -v cygpath ) > /dev/null 2>&1 ; then
+ echo "Warning: cygpath was not found on the PATH, Apache Maven cannot convert paths to the Windows format." >&2
+ echo "Some plugins may misbehave when they receive POSIX paths." >&2
+ cygwin=false
+ mingw=false
+ fi
+fi
+
## resolve links - $0 may be a link to Maven's home
PRG="$0"
@@ -109,16 +132,20 @@ fi
CLASSWORLDS_JAR=`echo "${MAVEN_HOME}"/boot/plexus-classworlds-*.jar`
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# For Cygwin and MinGW, switch paths to Windows format before running java(1)
+JLINE_NATIVE_PATH="$MAVEN_HOME/lib/jline-native"
+if $cygwin || $mingw ; then
[ -n "$MAVEN_HOME" ] &&
- MAVEN_HOME=`cygpath --path --windows "$MAVEN_HOME"`
+ MAVEN_HOME=`cygpath --windows "$MAVEN_HOME"`
[ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ JAVA_HOME=`cygpath --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$CLASSWORLDS_JAR" ] &&
- CLASSWORLDS_JAR=`cygpath --path --windows "$CLASSWORLDS_JAR"`
+ CLASSWORLDS_JAR=`cygpath --windows "$CLASSWORLDS_JAR"`
+ # converted separately, appending to the already converted MAVEN_HOME would
+ # produce a path mixing both separators, e.g. C:\maven/lib/jline-native
+ JLINE_NATIVE_PATH=`cygpath --windows "$JLINE_NATIVE_PATH"`
fi
# traverses directory structure from process work directory to filesystem root
@@ -138,7 +165,7 @@ find_maven_basedir() {
fi
wdir=`cd "$wdir/.."; pwd`
done
- echo "${basedir}"
+ printf '%s\n' "${basedir}"
)
}
@@ -168,7 +195,7 @@ find_file_argument_basedir() {
found_file_switch=1
fi
done
- echo "${basedir}"
+ printf '%s\n' "${basedir}"
)
}
@@ -182,11 +209,11 @@ concat_lines() {
MAVEN_PROJECTBASEDIR="${MAVEN_BASEDIR:-`find_maven_basedir "$@"`}"
MAVEN_OPTS="`concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config"` $MAVEN_OPTS"
-# For Cygwin, switch project base directory path to Windows format before
-# executing Maven otherwise this will cause Maven not to consider it.
-if $cygwin ; then
+# For Cygwin and MinGW, switch project base directory path to Windows format
+# before executing Maven otherwise this will cause Maven not to consider it.
+if $cygwin || $mingw ; then
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
- MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+ MAVEN_PROJECTBASEDIR=`cygpath --windows "$MAVEN_PROJECTBASEDIR"`
fi
export MAVEN_PROJECTBASEDIR
@@ -211,7 +238,7 @@ exec "$JAVACMD" \
-classpath "${CLASSWORLDS_JAR}" \
"-Dclassworlds.conf=${MAVEN_HOME}/bin/m2.conf" \
"-Dmaven.home=${MAVEN_HOME}" \
- "-Dlibrary.jline.path=${MAVEN_HOME}/lib/jline-native" \
+ "-Dlibrary.jline.path=$JLINE_NATIVE_PATH" \
"-Dorg.jline.terminal.exec.redirectPipeCreationMode=native,reflection" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${CLASSWORLDS_LAUNCHER} ${MAVEN_ARGS} "$@"
diff --git a/apache-maven/src/test/scripts/test-mvn-path-conversion.sh b/apache-maven/src/test/scripts/test-mvn-path-conversion.sh
new file mode 100644
index 0000000000..3cbc74d8e4
--- /dev/null
+++ b/apache-maven/src/test/scripts/test-mvn-path-conversion.sh
@@ -0,0 +1,250 @@
+#!/bin/sh
+
+# 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.
+
+# -----------------------------------------------------------------------------
+# Tests the Cygwin/MinGW/MSYS2 path conversion performed by bin/mvn.
+#
+# The test runs on any POSIX platform: a throw-away Maven home is populated with
+# the real bin/mvn, and uname(1), cygpath(1) and java(1) are stubbed out so that
+# the Windows-only code paths can be exercised and the resulting JVM command
+# line can be asserted upon.
+#
+# The launcher is always run with a PATH holding nothing but those stubs. That
+# keeps the outcome identical on every platform, and it is what makes the
+# missing-cygpath case meaningful: were the real PATH kept, the genuine
+# cygpath(1) of an actual Cygwin/MSYS2 installation would be picked up and the
+# fallback would never be exercised. Consequently the stubs may only rely on
+# shell built-ins, no external command is reachable from them.
+#
+# Usage: sh apache-maven/src/test/scripts/test-mvn-path-conversion.sh
+#
+# Exits with 0 when all assertions pass, 1 otherwise, so it can be wired into a
+# CI job or a git hook as-is.
+# -----------------------------------------------------------------------------
+
+set -e
+
+script_dir=`cd "\`dirname "$0"\`" && pwd`
+mvn_script="$script_dir/../../bin/mvn"
+
+if [ ! -f "$mvn_script" ]; then
+ echo "Cannot locate the mvn script at $mvn_script" >&2
+ exit 1
+fi
+
+# Absolute path to the shell: it has to be invoked while PATH holds the stubs only.
+sh_bin=`unset -f command; command -v sh`
+
+work_dir=`mktemp -d "${TMPDIR:-/tmp}/mvn-path-conversion.XXXXXX"`
+# Normalize the path: on macOS $TMPDIR ends with '/', producing a double
+# slash that pwd(1) inside the launcher will collapse. Resolving through
+# cd/pwd keeps the test's reference paths in sync with the launcher output.
+work_dir=`cd "$work_dir" && pwd`
+trap 'rm -rf "$work_dir"' EXIT INT TERM
+
+stub_dir="$work_dir/stubs"
+maven_home="$work_dir/maven-home"
+project_dir="$work_dir/project"
+
+mkdir -p "$stub_dir" "$maven_home/bin" "$maven_home/boot" "$maven_home/lib/jline-native" \
+ "$project_dir/.mvn" "$project_dir/module"
+
+cp "$mvn_script" "$maven_home/bin/mvn"
+touch "$maven_home/bin/m2.conf" "$maven_home/boot/plexus-classworlds-9.9.9.jar"
+
+# uname(1) stub: the emulated OS is taken from the FAKE_UNAME variable.
+cat > "$stub_dir/uname" <<'STUB'
+#!/bin/sh
+echo "${FAKE_UNAME:-Linux}"
+STUB
+
+# dirname(1) stub: bin/mvn needs it to locate the Maven home.
+cat > "$stub_dir/dirname" <<'STUB'
+#!/bin/sh
+path="$1"
+case "$path" in
+ */*)
+ path="${path%/*}"
+ [ -n "$path" ] || path="/"
+ ;;
+ *) path="." ;;
+esac
+printf '%s\n' "$path"
+STUB
+
+# tr(1) stub: concat_lines uses tr -s '\r\n' ' ' to flatten jvm.config.
+cat > "$stub_dir/tr" <<'STUB'
+#!/bin/sh
+# Minimal tr that only handles the concat_lines usage: tr -s '\r\n' ' '
+# Reads stdin and replaces \r and \n with spaces, squeezing repeats.
+while IFS= read -r line || [ -n "$line" ]; do
+ printf '%s ' "$line"
+done
+STUB
+
+# cygpath(1) stub: --windows maps /foo/bar to C:\foo\bar, --unix does the reverse.
+cat > "$stub_dir/cygpath" <<'STUB'
+#!/bin/sh
+for arg in "$@"; do path="$arg"; done
+
+replace() {
+ text="$1"; needle="$2"; value="$3"; out=""
+ while :; do
+ case "$text" in
+ *"$needle"*)
+ out="$out${text%%"$needle"*}$value"
+ text="${text#*"$needle"}"
+ ;;
+ *) break ;;
+ esac
+ done
+ printf '%s' "$out$text"
+}
+
+case " $* " in
+ *" --windows "*) printf 'C:%s\n' "`replace "$path" '/' '\'`" ;;
+ *" --unix "*) printf '%s\n' "`replace "${path#C:}" '\' '/'`" ;;
+ *" --path "*)
+ # --path with --windows
+ case " $* " in
+ *" --windows "*) printf 'C:%s\n' "`replace "$path" '/' '\'`" ;;
+ *" --unix "*) printf '%s\n' "`replace "${path#C:}" '\' '/'`" ;;
+ *) printf '%s\n' "$path" ;;
+ esac ;;
+ *) printf '%s\n' "$path" ;;
+esac
+STUB
+
+# java(1) stub: answers version probes and otherwise echoes the arguments it was
+# invoked with, one bracketed token per argument.
+cat > "$stub_dir/java" <<'STUB'
+#!/bin/sh
+case " $* " in
+ *" -version "*) exit 0 ;;
+esac
+
+for arg in "$@"; do printf '[%s]' "$arg"; done
+printf '\n'
+STUB
+
+chmod +x "$stub_dir/uname" "$stub_dir/dirname" "$stub_dir/cygpath" "$stub_dir/java" \
+ "$stub_dir/tr" "$maven_home/bin/mvn"
+
+# The same stubs, without cygpath, to exercise the fallback.
+nocygpath_dir="$work_dir/stubs-without-cygpath"
+mkdir -p "$nocygpath_dir"
+for stub in uname dirname java tr; do
+ cp "$stub_dir/$stub" "$nocygpath_dir/$stub"
+ chmod +x "$nocygpath_dir/$stub"
+done
+
+failures=0
+
+# run_mvn <uname-output> <stub-dir>
+run_mvn() {
+ ( cd "$project_dir/module" &&
+ FAKE_UNAME="$1" PATH="$2" JAVA_HOME= MAVEN_SKIP_RC=1 \
+ "$sh_bin" "$maven_home/bin/mvn" verify 2>/dev/null )
+}
+
+# to_windows <posix-path>
+to_windows() {
+ text="$1"; out=""
+ while :; do
+ case "$text" in
+ */*)
+ out="$out${text%%/*}\\"
+ text="${text#*/}"
+ ;;
+ *) break ;;
+ esac
+ done
+ printf 'C:%s' "$out$text"
+}
+
+# contains <haystack> <needle>
+# Plain substring check. A case/glob comparison cannot be used, the haystack
+# holds Windows paths and backslashes are escape characters in glob patterns.
+contains() {
+ case "$1" in
+ *"$2"*) return 0 ;;
+ *) return 1 ;;
+ esac
+}
+
+# assert_contains <description> <haystack> <needle>
+assert_contains() {
+ if contains "$2" "$3"; then
+ printf 'ok - %s\n' "$1"
+ else
+ printf 'FAILED - %s\n' "$1"
+ printf ' expected to contain: %s\n' "$3"
+ printf ' actual: %s\n' "$2"
+ failures=`expr $failures + 1`
+ fi
+}
+
+# assert_not_contains <description> <haystack> <needle>
+assert_not_contains() {
+ if contains "$2" "$3"; then
+ printf 'FAILED - %s\n' "$1"
+ printf ' expected NOT to contain: %s\n' "$3"
+ printf ' actual: %s\n' "$2"
+ failures=`expr $failures + 1`
+ else
+ printf 'ok - %s\n' "$1"
+ fi
+}
+
+# POSIX platforms must be left untouched.
+output=`run_mvn Linux "$stub_dir"`
+assert_contains "POSIX: multiModuleProjectDirectory stays a POSIX path" "$output" \
+ "[-Dmaven.multiModuleProjectDirectory=$project_dir]"
+assert_contains "POSIX: maven.home stays a POSIX path" "$output" \
+ "[-Dmaven.home=$maven_home]"
+
+# Every Windows POSIX emulation layer must receive native Windows paths.
+for os in CYGWIN_NT-10.0 MINGW32_NT-6.2 MINGW64_NT-10.0 MSYS_NT-10.0; do
+ output=`run_mvn "$os" "$stub_dir"`
+
+ assert_contains "$os: multiModuleProjectDirectory is a native Windows path" "$output" \
+ "[-Dmaven.multiModuleProjectDirectory=`to_windows "$project_dir"`]"
+ assert_contains "$os: maven.home is a native Windows path" "$output" \
+ "[-Dmaven.home=`to_windows "$maven_home"`]"
+ assert_contains "$os: library.jline.path is a native Windows path" "$output" \
+ "[-Dlibrary.jline.path=`to_windows "$maven_home/lib/jline-native"`]"
+ assert_not_contains "$os: no path mixes both separators" "$output" "\\/"
+done
+
+# Without cygpath the launcher must still work, using unconverted paths, rather
+# than passing empty paths to the JVM.
+output=`run_mvn MINGW64_NT-10.0 "$nocygpath_dir"`
+assert_contains "missing cygpath: multiModuleProjectDirectory falls back to a POSIX path" "$output" \
+ "[-Dmaven.multiModuleProjectDirectory=$project_dir]"
+assert_contains "missing cygpath: maven.home falls back to a POSIX path" "$output" \
+ "[-Dmaven.home=$maven_home]"
+assert_not_contains "missing cygpath: no empty maven.home is passed" "$output" \
+ "[-Dmaven.home=]"
+
+if [ "$failures" -ne 0 ]; then
+ printf '%s assertion(s) failed\n' "$failures"
+ exit 1
+fi
+
+echo "All assertions passed"