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 09:58:33 UTC
(maven) branch backport/12546-to-3.9.x created (now 86e737ae90)
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a change to branch backport/12546-to-3.9.x
in repository https://gitbox.apache.org/repos/asf/maven.git
at 86e737ae90 [3.9.x] Convert paths for MSYS2 and jvm.config placeholders in bin/mvn
This branch includes the following new commits:
new 86e737ae90 [3.9.x] Convert paths for MSYS2 and jvm.config placeholders in bin/mvn
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(maven) 01/01: [3.9.x] Convert paths for MSYS2 and jvm.config placeholders in bin/mvn
Posted by gn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch backport/12546-to-3.9.x
in repository https://gitbox.apache.org/repos/asf/maven.git
commit 86e737ae904b82191fe72a59214285b3f1e29810
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Tue Jul 28 11:58:20 2026 +0200
[3.9.x] Convert paths for MSYS2 and jvm.config placeholders in bin/mvn
Backport of #12546 to the 3.9.x branch. Adapted for the different script
location (src/bin/mvn vs src/assembly/maven/bin/mvn), the simpler
jvm.config handling (no JvmConfigParser on this branch), and jansi-native
instead of jline-native.
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,
jansi-native) for both Cygwin and MinGW/MSYS2, not just Cygwin
- Convert jansi-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 31ad5fe484..467bc67a35 100644
--- a/apache-maven/pom.xml
+++ b/apache-maven/pom.xml
@@ -354,5 +354,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 47dc7d7f4e..c017a3e9ce 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)
+JANSI_NATIVE_PATH="$MAVEN_HOME/lib/jansi-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/jansi-native
+ JANSI_NATIVE_PATH=`cygpath --windows "$JANSI_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,6 +238,6 @@ exec "$JAVACMD" \
-classpath "${CLASSWORLDS_JAR}" \
"-Dclassworlds.conf=${MAVEN_HOME}/bin/m2.conf" \
"-Dmaven.home=${MAVEN_HOME}" \
- "-Dlibrary.jansi.path=${MAVEN_HOME}/lib/jansi-native" \
+ "-Dlibrary.jansi.path=$JANSI_NATIVE_PATH" \
"-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..21626aa01e
--- /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/jansi-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.jansi.path is a native Windows path" "$output" \
+ "[-Dlibrary.jansi.path=`to_windows "$maven_home/lib/jansi-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"