You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by rm...@apache.org on 2026/09/06 19:10:38 UTC
(maven) branch master updated: Render mvn -v from the shell fast path (#13056)
This is an automated email from the ASF dual-hosted git repository.
rmannibucau pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push:
new 74b1317cfc Render mvn -v from the shell fast path (#13056)
74b1317cfc is described below
commit 74b1317cfcf44b962151f79b603b5aa4f3285aaf
Author: Romain Manni-Bucau <rm...@gmail.com>
AuthorDate: Sun Sep 6 21:10:32 2026 +0200
Render mvn -v from the shell fast path (#13056)
Piggyback on the Java 17 gate to probe JVM settings once and render the
version banner from maven.version.properties, so version-only invocations
answer without booting the full Maven JVM. Both the Unix and Windows
launchers parse the CLI flags (including compact -qv / -qV tokens) and
fall back to a full launch for -X, --enc/--shell/--up, and anything
ambiguous. -e/--errors stays on the fast path since it does not change
the main class.
---
apache-maven/pom.xml | 37 ++++
apache-maven/src/assembly/component.xml | 8 +
apache-maven/src/assembly/maven/bin/mvn | 206 +++++++++++++++++-
apache-maven/src/assembly/maven/bin/mvn.cmd | 214 ++++++++++++++++++-
.../apache/maven/messages/maven.version.properties | 21 ++
.../src/test/scripts/test-mvn-fast-version.sh | 231 +++++++++++++++++++++
.../apache/maven/cling/invoker/LookupInvoker.java | 2 +-
7 files changed, 701 insertions(+), 18 deletions(-)
diff --git a/apache-maven/pom.xml b/apache-maven/pom.xml
index 6b06ed42ef..e02421bfdc 100644
--- a/apache-maven/pom.xml
+++ b/apache-maven/pom.xml
@@ -164,7 +164,30 @@ under the License.
</pluginRepositories>
<build>
+ <resources>
+ <resource>
+ <filtering>true</filtering>
+ <directory>src/main/resources</directory>
+ </resource>
+ </resources>
<plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>buildnumber-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>create-buildnumber</id>
+ <goals>
+ <goal>create</goal>
+ </goals>
+ <configuration>
+ <doCheck>false</doCheck>
+ <doUpdate>false</doUpdate>
+ <revisionOnScmFailure>NON_CANONICAL</revisionOnScmFailure>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
@@ -487,6 +510,20 @@ under the License.
</arguments>
</configuration>
</execution>
+ <execution>
+ <id>test-mvn-fast-version</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-fast-version.sh</argument>
+ </arguments>
+ </configuration>
+ </execution>
</executions>
</plugin>
</plugins>
diff --git a/apache-maven/src/assembly/component.xml b/apache-maven/src/assembly/component.xml
index 5f55a310c8..f8250afe42 100644
--- a/apache-maven/src/assembly/component.xml
+++ b/apache-maven/src/assembly/component.xml
@@ -105,5 +105,13 @@ under the License.
<directory>src/assembly/maven/lib</directory>
<outputDirectory>lib</outputDirectory>
</fileSet>
+ <fileSet>
+ <directory>target/classes/org/apache/maven/messages</directory>
+ <outputDirectory>bin</outputDirectory>
+ <includes>
+ <include>maven.version.properties</include>
+ </includes>
+ <fileMode>0444</fileMode>
+ </fileSet>
</fileSets>
</component>
diff --git a/apache-maven/src/assembly/maven/bin/mvn b/apache-maven/src/assembly/maven/bin/mvn
index 0adc4eabeb..96046c701e 100755
--- a/apache-maven/src/assembly/maven/bin/mvn
+++ b/apache-maven/src/assembly/maven/bin/mvn
@@ -130,11 +130,196 @@ else
fi
fi
-if ! "$JAVACMD" --enable-native-access=ALL-UNNAMED -version >/dev/null 2>&1; then
- echo "Error: Apache Maven 4.x requires Java 17 or newer to run." >&2
- "$JAVACMD" -version >&2
- echo "Please upgrade your Java installation or set JAVA_HOME to point to a compatible JDK." >&2
- exit 1
+# Early scan of the CLI arguments so that version-only invocations can be
+# answered straight from this script, without booting the full Maven JVM. The
+# banner is assembled to match org.apache.maven.cling.utils.CLIReportingUtils
+# #showVersion (see print_fast_version below). Compact single-dash tokens
+# (e.g. -qv, -qV) are honoured, but only when composed exclusively of the safe
+# chars q/v/V/X/e, mirroring the relevant Commons CLI short options.
+VERSION_AND_EXIT=false
+SHOW_VERSION=false
+QUIET=false
+VERBOSE=false
+MAIN_CLASS_OVERRIDE=false
+FAST_VERSION_PRINTED=false
+for arg in "$@"; do
+ if [ "$arg" = "--" ] ; then
+ break
+ fi
+ case $arg in
+ -v|--version) VERSION_AND_EXIT=true ;;
+ -V|--show-version) SHOW_VERSION=true ;;
+ -q|--quiet) QUIET=true ;;
+ -X|--debug) VERBOSE=true ;;
+ --enc|--shell|--up) MAIN_CLASS_OVERRIDE=true ;;
+ -[qvVXe]*)
+ case $arg in
+ *v*) VERSION_AND_EXIT=true ;;
+ *V*) SHOW_VERSION=true ;;
+ esac
+ case $arg in
+ *q*) QUIET=true ;;
+ *X*) VERBOSE=true ;;
+ esac
+ ;;
+ *) ;;
+ esac
+done
+
+# Read a single `key=value` from a properties file. Unresolved `${...}`
+# placeholders are treated as absent, mirroring the reduce() logic used by the
+# JVM version reporting.
+read_property() {
+ _file=$1
+ _key=$2
+ if [ -f "$_file" ] ; then
+ _value=`sed -n "s/^$_key=//p" "$_file"`
+ case $_value in
+ \$\{*|"") ;;
+ *) printf '%s\n' "$_value" ;;
+ esac
+ fi
+}
+
+# Extract a JVM system property value from a `java -XshowSettings:properties`
+# dump (the properties are emitted on stderr as ` java.version = 17.0.10`).
+show_settings_property() {
+ _settings=$1
+ _key=$2
+ if [ -n "$_settings" ] ; then
+ printf '%s\n' "$_settings" | sed -n "s/^ *$_key *= *//p" | head -n 1
+ fi
+}
+
+# Render the version banner. When quiet, only the bare version is printed,
+# matching CLIReportingUtils#showVersionMinimal.
+print_fast_version() {
+ _settings=$1
+ _quiet=$2
+
+ _version_file="$MAVEN_HOME/bin/maven.version.properties"
+
+ _mvn_name=`read_property "$_version_file" distributionName`
+ _mvn_short=`read_property "$_version_file" distributionShortName`
+ _mvn_version=`read_property "$_version_file" version`
+ _mvn_build=`read_property "$_version_file" buildNumber`
+
+ if [ -z "$_mvn_version" ] ; then
+ # Fallback to the (versioned) core jar filename when the version file is
+ # not present, e.g. when running straight from a source checkout.
+ for _jar in "$MAVEN_HOME"/lib/maven-core-*.jar ; do
+ if [ -f "$_jar" ] ; then
+ _mvn_version=`printf '%s' "$_jar" | sed -e 's#.*maven-core-##' -e 's#\.jar$##'`
+ break
+ fi
+ done
+ fi
+
+ if [ -z "$_mvn_name" ] ; then
+ _mvn_name="Apache Maven"
+ fi
+ if [ -z "$_mvn_short" ] ; then
+ _mvn_short="Maven"
+ fi
+ if [ -z "$_mvn_version" ] ; then
+ _mvn_version="<version unknown>"
+ fi
+
+ if $_quiet ; then
+ printf '%s\n' "$_mvn_version"
+ return 0
+ fi
+
+ printf '%s %s' "$_mvn_name" "$_mvn_version"
+ if [ -n "$_mvn_build" ] ; then
+ printf ' (%s)' "$_mvn_build"
+ fi
+ printf '\n'
+
+ printf '%s home: %s\n' "$_mvn_short" "$MAVEN_HOME"
+
+ _java_version=`show_settings_property "$_settings" java.version`
+ _java_vendor=`show_settings_property "$_settings" java.vendor`
+ _java_home=`show_settings_property "$_settings" java.home`
+ printf 'Java version: %s, vendor: %s, runtime: %s\n' \
+ "${_java_version:-<unknown Java version>}" "${_java_vendor:-<unknown vendor>}" "${_java_home:-<unknown runtime>}"
+
+ _lang=`show_settings_property "$_settings" user.language`
+ _country=`show_settings_property "$_settings" user.country`
+ _encoding=`show_settings_property "$_settings" file.encoding`
+ _timezone=`show_settings_property "$_settings" user.timezone`
+ if [ -z "$_timezone" ] ; then
+ # The settings dump does not carry the time zone; derive it the same way
+ # the JVM does (TZ wins, then /etc/localtime) when possible.
+ if [ -n "$TZ" ] && [ -r "/usr/share/zoneinfo/$TZ" ] ; then
+ _timezone="$TZ"
+ elif [ -h /etc/localtime ] ; then
+ _timezone=`readlink /etc/localtime`
+ _timezone=`printf '%s' "$_timezone" | sed 's#^.*zoneinfo/##'`
+ elif [ -r /etc/timezone ] ; then
+ _timezone=`cat /etc/timezone`
+ fi
+ fi
+ _locale="$_lang"
+ if [ -n "$_country" ] ; then
+ _locale="$_lang"_"$_country"
+ fi
+ printf 'Default locale: %s, platform encoding: %s, time zone: %s\n' \
+ "$_locale" "${_encoding:-<unknown encoding>}" "${_timezone:-<unknown>}"
+
+ _os_name=`show_settings_property "$_settings" os.name`
+ _os_version=`show_settings_property "$_settings" os.version`
+ _os_arch=`show_settings_property "$_settings" os.arch`
+ _os_name=`printf '%s' "$_os_name" | tr '[:upper:]' '[:lower:]'`
+ _os_version=`printf '%s' "$_os_version" | tr '[:upper:]' '[:lower:]'`
+ _os_arch=`printf '%s' "$_os_arch" | tr '[:upper:]' '[:lower:]'`
+
+ _os_family="unknown"
+ case "$_os_name" in
+ *windows*) _os_family="windows" ;;
+ *mac*|darwin) _os_family="mac" ;;
+ *os/2*) _os_family="os/2" ;;
+ *dos*) _os_family="dos" ;;
+ *)
+ if [ -n "$_os_name" ] ; then
+ _os_family="unix"
+ fi
+ ;;
+ esac
+ printf 'OS name: "%s", version: "%s", arch: "%s", family: "%s"\n' \
+ "$_os_name" "$_os_version" "$_os_arch" "$_os_family"
+}
+
+# Combined Java 17 gate and JVM settings probe. Version invocations reuse the
+# settings dump to render the banner without starting Maven itself; every other
+# invocation only relies on the exit status, exactly like the former probe.
+HAS_SETTINGS=false
+VERSION_SETTINGS=
+if $VERSION_AND_EXIT || $SHOW_VERSION ; then
+ if ! $VERBOSE && ! $MAIN_CLASS_OVERRIDE ; then
+ if VERSION_SETTINGS=`"$JAVACMD" --enable-native-access=ALL-UNNAMED -XshowSettings:properties -version 2>&1` ; then
+ HAS_SETTINGS=true
+ fi
+ fi
+fi
+if ! $HAS_SETTINGS ; then
+ if ! "$JAVACMD" --enable-native-access=ALL-UNNAMED -version >/dev/null 2>&1; then
+ echo "Error: Apache Maven 4.x requires Java 17 or newer to run." >&2
+ "$JAVACMD" -version >&2
+ echo "Please upgrade your Java installation or set JAVA_HOME to point to a compatible JDK." >&2
+ exit 1
+ fi
+fi
+
+if $HAS_SETTINGS ; then
+ if $VERSION_AND_EXIT ; then
+ print_fast_version "$VERSION_SETTINGS" $QUIET
+ exit 0
+ fi
+ # -V/--show-version: print the banner now and let the JVM run the build
+ # without printing it a second time.
+ print_fast_version "$VERSION_SETTINGS" $QUIET
+ FAST_VERSION_PRINTED=true
fi
# traverses directory structure from process work directory to filesystem root
@@ -323,8 +508,15 @@ cmd="\"$JAVACMD\" \
\"-Dmaven.home=$MAVEN_HOME\" \
\"-Dmaven.mainClass=$MAVEN_MAIN_CLASS\" \
\"-Dlibrary.jline.path=$JLINE_NATIVE_PATH\" \
- \"-Dmaven.multiModuleProjectDirectory=$MAVEN_PROJECTBASEDIR\" \
- $LAUNCHER_CLASS"
+ \"-Dmaven.multiModuleProjectDirectory=$MAVEN_PROJECTBASEDIR\""
+
+# The JVM option must precede the main class on the command line, otherwise it
+# would be treated as a plain Maven argument rather than a system property.
+if [ "$FAST_VERSION_PRINTED" = "true" ] ; then
+ cmd="$cmd \"-Dmaven.version.printed=true\""
+fi
+
+cmd="$cmd $LAUNCHER_CLASS"
if [ -n "$MAVEN_DEBUG_SCRIPT" ]; then
echo "[DEBUG] Launching JVM with command:" >&2
diff --git a/apache-maven/src/assembly/maven/bin/mvn.cmd b/apache-maven/src/assembly/maven/bin/mvn.cmd
index 74d4a5a984..c1b52fa87f 100644
--- a/apache-maven/src/assembly/maven/bin/mvn.cmd
+++ b/apache-maven/src/assembly/maven/bin/mvn.cmd
@@ -74,14 +74,75 @@ if not exist "%JAVACMD%" (
goto error
)
-@REM Check Java version by testing the Java 17+ flag
-"%JAVACMD%" --enable-native-access=ALL-UNNAMED -version >nul 2>&1
-if ERRORLEVEL 1 (
- echo Error: Apache Maven 4.x requires Java 17 or newer to run. >&2
- "%JAVACMD%" -version >&2
- echo Please upgrade your Java installation or set JAVA_HOME to point to a compatible JDK. >&2
- goto error
-)
+@REM Scan the arguments for version/quiet flags so that version-only
+@REM invocations can be answered without starting Maven itself; the Java-17
+@REM gate below then doubles as the settings probe used to render the banner.
+@REM Arguments are inspected via :getFlagArg instead of "for %%a in (%*)" so
+@REM that quoted argument boundaries are preserved: both the "--" and the
+@REM "for" tokenizers split values like -Dfoo=-v into "-Dfoo" and "-v", which
+@REM would false-trigger the version fast path. The scan leaves the positional
+@REM parameters intact so the regular argument handling below (-f/--file and
+@REM Maven goal args) is unaffected.
+set "IS_VERSION_AND_EXIT="
+set "IS_SHOW_VERSION="
+set "IS_QUIET="
+set "IS_VERBOSE="
+set "IS_MAIN_OVERRIDE="
+set "_ARG_IDX=0"
+:parseFlags
+set /a _ARG_IDX+=1
+call :getFlagArg %_ARG_IDX% %*
+if "%_FLAG_ARG%"=="" goto parseFlagsDone
+if "%_FLAG_ARG%"=="--" goto parseFlagsDone
+if "%_FLAG_ARG%"=="-v" set "IS_VERSION_AND_EXIT=1"
+if "%_FLAG_ARG%"=="--version" set "IS_VERSION_AND_EXIT=1"
+if "%_FLAG_ARG%"=="-V" set "IS_SHOW_VERSION=1"
+if "%_FLAG_ARG%"=="--show-version" set "IS_SHOW_VERSION=1"
+if "%_FLAG_ARG%"=="-q" set "IS_QUIET=1"
+if "%_FLAG_ARG%"=="--quiet" set "IS_QUIET=1"
+if "%_FLAG_ARG%"=="-X" set "IS_VERBOSE=1"
+if "%_FLAG_ARG%"=="--debug" set "IS_VERBOSE=1"
+if "%_FLAG_ARG%"=="--enc" set "IS_MAIN_OVERRIDE=1"
+if "%_FLAG_ARG%"=="--shell" set "IS_MAIN_OVERRIDE=1"
+if "%_FLAG_ARG%"=="--up" set "IS_MAIN_OVERRIDE=1"
+@REM Compact single-dash tokens (e.g. -qv, -vX) mirror the Unix script's
+@REM -[qvVXe]* handling, but only when the part after '-' is made exclusively
+@REM of the safe chars v V q X e; otherwise (e.g. -f, -D...) the token is
+@REM skipped so property values like -Dfoo=-v never trigger the fast path.
+@REM Each set is a separate top-level line so %var% is expanded after the
+@REM previous assignment, avoiding the need for delayed expansion.
+if not "%_FLAG_ARG:~0,1%"=="-" goto parseFlags
+set "_FLAG_REST=%_FLAG_ARG:~1%"
+set "_FLAG_CHECK=%_FLAG_REST:v=%"
+set "_FLAG_CHECK=%_FLAG_CHECK:V=%"
+set "_FLAG_CHECK=%_FLAG_CHECK:q=%"
+set "_FLAG_CHECK=%_FLAG_CHECK:X=%"
+set "_FLAG_CHECK=%_FLAG_CHECK:e=%"
+if not "%_FLAG_CHECK%"=="" goto parseFlags
+@REM CMD %var:str=% substitution is case-insensitive, so the strings v/V, q/Q
+@REM and x/X are not distinguishable with it. The compact tokens must match the
+@REM Unix script's exact flags (-q is quiet, -v exit-after-version, -V show),
+@REM so use case-sensitive findstr matching instead. The pipeline echoes a
+@REM string that has already been restricted to the safe chars v V q X e, so no
+@REM shell-special characters can leak through. -e/--errors is deliberately not
+@REM mapped here: it only controls error stack traces and does not change the
+@REM main class, mirroring the Unix script which treats e as a safe char without
+@REM setting any bypass flag.
+echo %_FLAG_REST%| findstr /c:"v" >nul 2>&1 && set "IS_VERSION_AND_EXIT=1"
+echo %_FLAG_REST%| findstr /c:"V" >nul 2>&1 && set "IS_SHOW_VERSION=1"
+echo %_FLAG_REST%| findstr /c:"q" >nul 2>&1 && set "IS_QUIET=1"
+echo %_FLAG_REST%| findstr /c:"X" >nul 2>&1 && set "IS_VERBOSE=1"
+goto parseFlags
+:parseFlagsDone
+goto endFlagScan
+:getFlagArg
+@REM Shift the subroutine's own positional parameters so that %1 becomes the
+@REM argument at index %1 (the caller's _ARG_IDX); the caller's parameters are
+@REM never touched.
+for /l %%i in (1,1,%1) do shift
+set "_FLAG_ARG=%~1"
+exit /b
+:endFlagScan
:chkMHome
set "MAVEN_HOME=%~dp0"
@@ -91,6 +152,46 @@ if "%MAVEN_HOME%"=="" goto error
:checkMCmd
if not exist "%MAVEN_HOME%\bin\mvn.cmd" goto error
+@REM ==== FAST VERSION PATH ====
+@REM When only version info is requested, render the banner from a single
+@REM lightweight JVM (-XshowSettings) without starting Maven itself.
+set "FAST_VERSION=0"
+set "VERSION_SETTINGS_TEMP="
+if defined IS_VERSION_AND_EXIT goto tryFastVersion
+if defined IS_SHOW_VERSION goto tryFastVersion
+goto javaGate
+
+:tryFastVersion
+if defined IS_VERBOSE goto javaGate
+if defined IS_MAIN_OVERRIDE goto javaGate
+set "VERSION_SETTINGS_TEMP=%TEMP%\mvn-version-%RANDOM%-%RANDOM%.txt"
+"%JAVACMD%" --enable-native-access=ALL-UNNAMED -XshowSettings:properties -version 2> "%VERSION_SETTINGS_TEMP%"
+if ERRORLEVEL 1 (
+ del "%VERSION_SETTINGS_TEMP%" 2>nul
+ set "VERSION_SETTINGS_TEMP="
+ goto javaGate
+)
+set "FAST_VERSION=1"
+goto versionPrint
+
+:javaGate
+"%JAVACMD%" --enable-native-access=ALL-UNNAMED -version >nul 2>&1
+if ERRORLEVEL 1 (
+ echo Error: Apache Maven 4.x requires Java 17 or newer to run. >&2
+ "%JAVACMD%" -version >&2
+ echo Please upgrade your Java installation or set JAVA_HOME to point to a compatible JDK. >&2
+ goto error
+)
+if "%FAST_VERSION%"=="1" goto versionPrint
+goto fastVersionDone
+
+:versionPrint
+call :printFastVersion "%VERSION_SETTINGS_TEMP%"
+if defined VERSION_SETTINGS_TEMP del "%VERSION_SETTINGS_TEMP%" 2>nul
+if defined IS_VERSION_AND_EXIT goto end
+set "MAVEN_VERSION_PRINTED=-Dmaven.version.printed=true"
+:fastVersionDone
+
@REM ==== END VALIDATION ====
:init
@@ -297,7 +398,7 @@ if not "%MAVEN_MAIN_CLASS%"=="org.apache.maven.cling.MavenCling" set "MAVEN_ARGS
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% %*
+ 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%" %MAVEN_VERSION_PRINTED% %LAUNCHER_CLASS% %MAVEN_ARGS% %*
)
"%JAVACMD%" ^
@@ -312,6 +413,7 @@ if defined MAVEN_DEBUG_SCRIPT (
"-Dmaven.mainClass=%MAVEN_MAIN_CLASS%" ^
"-Dlibrary.jline.path=%MAVEN_HOME%\lib\jline-native" ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %MAVEN_VERSION_PRINTED% ^
%LAUNCHER_CLASS% ^
%MAVEN_ARGS% ^
%*
@@ -335,4 +437,96 @@ 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%
\ No newline at end of file
+exit /b %ERROR_CODE%
+
+:printFastVersion
+@REM Renders the Maven version banner without starting Maven itself.
+@REM %1 = path to the java -XshowSettings dump file (may be empty when absent)
+set "_SETTINGS=%~1"
+set "_MVN_NAME="
+set "_MVN_SHORT="
+set "_MVN_VERSION="
+set "_MVN_BUILD="
+set "_VFILE=%MAVEN_HOME%\bin\maven.version.properties"
+if exist "%_VFILE%" (
+ for /f "tokens=1,* delims==" %%a in ('findstr /b /c:"distributionName=" "%_VFILE%"') do set "_MVN_NAME=%%b"
+ for /f "tokens=1,* delims==" %%a in ('findstr /b /c:"distributionShortName=" "%_VFILE%"') do set "_MVN_SHORT=%%b"
+ for /f "tokens=1,* delims==" %%a in ('findstr /b /c:"version=" "%_VFILE%"') do set "_MVN_VERSION=%%b"
+ for /f "tokens=1,* delims==" %%a in ('findstr /b /c:"buildNumber=" "%_VFILE%"') do set "_MVN_BUILD=%%b"
+)
+if not defined _MVN_NAME set "_MVN_NAME=Apache Maven"
+if not defined _MVN_SHORT set "_MVN_SHORT=Maven"
+if not defined _MVN_VERSION set "_MVN_VERSION=<version unknown>"
+
+set "_JAVA_VERSION="
+set "_JAVA_VENDOR="
+set "_JAVA_HOME="
+set "_LANG="
+set "_COUNTRY="
+set "_ENCODING="
+set "_TIMEZONE="
+set "_OS_NAME="
+set "_OS_VERSION="
+set "_OS_ARCH="
+
+if not defined _SETTINGS goto printFastVersionOutput
+for /f "tokens=1,* delims==" %%a in ('findstr /c:"java.version =" "%_SETTINGS%"') do set "_JAVA_VERSION=%%b"
+for /f "tokens=1,* delims==" %%a in ('findstr /c:"java.vendor =" "%_SETTINGS%"') do set "_JAVA_VENDOR=%%b"
+for /f "tokens=1,* delims==" %%a in ('findstr /c:"java.home =" "%_SETTINGS%"') do set "_JAVA_HOME=%%b"
+for /f "tokens=1,* delims==" %%a in ('findstr /c:"user.language =" "%_SETTINGS%"') do set "_LANG=%%b"
+for /f "tokens=1,* delims==" %%a in ('findstr /c:"user.country =" "%_SETTINGS%"') do set "_COUNTRY=%%b"
+for /f "tokens=1,* delims==" %%a in ('findstr /c:"file.encoding =" "%_SETTINGS%"') do set "_ENCODING=%%b"
+for /f "tokens=1,* delims==" %%a in ('findstr /c:"user.timezone =" "%_SETTINGS%"') do set "_TIMEZONE=%%b"
+for /f "tokens=1,* delims==" %%a in ('findstr /c:"os.name =" "%_SETTINGS%"') do set "_OS_NAME=%%b"
+for /f "tokens=1,* delims==" %%a in ('findstr /c:"os.version =" "%_SETTINGS%"') do set "_OS_VERSION=%%b"
+for /f "tokens=1,* delims==" %%a in ('findstr /c:"os.arch =" "%_SETTINGS%"') do set "_OS_ARCH=%%b"
+
+@REM The "key = value" format adds one leading space after '='; strip it.
+if defined _JAVA_VERSION set "_JAVA_VERSION=%_JAVA_VERSION:~1%"
+if defined _JAVA_VENDOR set "_JAVA_VENDOR=%_JAVA_VENDOR:~1%"
+if defined _JAVA_HOME set "_JAVA_HOME=%_JAVA_HOME:~1%"
+if defined _LANG set "_LANG=%_LANG:~1%"
+if defined _COUNTRY set "_COUNTRY=%_COUNTRY:~1%"
+if defined _ENCODING set "_ENCODING=%_ENCODING:~1%"
+if defined _TIMEZONE set "_TIMEZONE=%_TIMEZONE:~1%"
+if defined _OS_NAME set "_OS_NAME=%_OS_NAME:~1%"
+if defined _OS_VERSION set "_OS_VERSION=%_OS_VERSION:~1%"
+if defined _OS_ARCH set "_OS_ARCH=%_OS_ARCH:~1%"
+
+if not defined _JAVA_VERSION set "_JAVA_VERSION=<unknown Java version>"
+if not defined _JAVA_VENDOR set "_JAVA_VENDOR=<unknown vendor>"
+if not defined _JAVA_HOME set "_JAVA_HOME=<unknown runtime>"
+if not defined _ENCODING set "_ENCODING=<unknown encoding>"
+if defined _COUNTRY (
+ set "_LOCALE=%_LANG%_%_COUNTRY%"
+) else (
+ set "_LOCALE=%_LANG%"
+)
+if not defined _LOCALE set "_LOCALE=<unknown>"
+
+@REM Time zone: not in -XshowSettings; try TZ env, else Windows registry not portable.
+if not defined _TIMEZONE (
+ if defined TZ (set "_TIMEZONE=%TZ%") else (set "_TIMEZONE=unknown")
+)
+
+@REM OS family: derived from os.name (case-insensitive substring).
+set "_OS_FAMILY="
+echo %_OS_NAME% | findstr /i "windows" >nul 2>&1 && set "_OS_FAMILY=windows"
+echo %_OS_NAME% | findstr /i "mac" >nul 2>&1 && set "_OS_FAMILY=mac"
+if not defined _OS_FAMILY if defined _OS_NAME set "_OS_FAMILY=unix"
+
+:printFastVersionOutput
+if defined IS_QUIET (
+ echo %_MVN_VERSION%
+ exit /b 0
+)
+if defined _MVN_BUILD (
+ echo %_MVN_NAME% %_MVN_VERSION% (%_MVN_BUILD%)
+) else (
+ echo %_MVN_NAME% %_MVN_VERSION%
+)
+echo %_MVN_SHORT% home: %MAVEN_HOME%
+echo Java version: %_JAVA_VERSION%, vendor: %_JAVA_VENDOR%, runtime: %_JAVA_HOME%
+echo Default locale: %_LOCALE%, platform encoding: %_ENCODING%, time zone: %_TIMEZONE%
+echo OS name: "%_OS_NAME%", version: "%_OS_VERSION%", arch: "%_OS_ARCH%", family: "%_OS_FAMILY%"
+exit /b 0
diff --git a/apache-maven/src/main/resources/org/apache/maven/messages/maven.version.properties b/apache-maven/src/main/resources/org/apache/maven/messages/maven.version.properties
new file mode 100644
index 0000000000..2e325d78d9
--- /dev/null
+++ b/apache-maven/src/main/resources/org/apache/maven/messages/maven.version.properties
@@ -0,0 +1,21 @@
+# 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.
+
+buildNumber=${buildNumber}
+version=${project.version}
+distributionShortName=${distributionShortName}
+distributionName=${distributionName}
diff --git a/apache-maven/src/test/scripts/test-mvn-fast-version.sh b/apache-maven/src/test/scripts/test-mvn-fast-version.sh
new file mode 100644
index 0000000000..9c191cf861
--- /dev/null
+++ b/apache-maven/src/test/scripts/test-mvn-fast-version.sh
@@ -0,0 +1,231 @@
+#!/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 fast version path in bin/mvn that renders the version banner
+# without starting Maven itself.
+#
+# The test runs on any POSIX platform: a throw-away Maven home is populated with
+# a fake java stub that responds to -XshowSettings with synthetic properties and
+# a maven.version.properties file. The script then asserts that -v/--version
+# prints the expected banner, -q/--quiet prints only the version, -V prints the
+# banner but also launches Maven (via the -Dmaven.version.printed flag), and
+# that -X or --enc bypasses the fast path.
+#
+# Usage: sh apache-maven/src/test/scripts/test-mvn-fast-version.sh
+#
+# Exits with 0 when all assertions pass, 1 otherwise.
+# -----------------------------------------------------------------------------
+
+set -e
+
+script_dir=`cd "\`dirname "$0"\`" && pwd`
+mvn_script="$script_dir/../../assembly/maven/bin/mvn"
+
+if [ ! -f "$mvn_script" ]; then
+ echo "Cannot locate the mvn script at $mvn_script" >&2
+ exit 1
+fi
+
+sh_bin=`unset -f command; command -v sh`
+
+work_dir=`mktemp -d "${TMPDIR:-/tmp}/mvn-fast-version.XXXXXX"`
+work_dir=`cd "$work_dir" && pwd`
+trap 'rm -rf "$work_dir"' EXIT INT TERM
+
+maven_home="$work_dir/maven-home"
+mkdir -p "$maven_home/bin" "$maven_home/boot" "$maven_home/lib"
+
+cp "$mvn_script" "$maven_home/bin/mvn"
+touch "$maven_home/boot/plexus-classworlds-9.9.9.jar"
+# Minimal jar for -v --enc fallback (not used in fast path, but needed for
+# the non-fast-path case where MAVEN_MAIN_CLASS is set).
+touch "$maven_home/lib/maven-core-9.9.9.jar"
+
+# Create the precomputed version properties file.
+cat > "$maven_home/bin/maven.version.properties" <<'EOF'
+buildNumber=abc123def
+version=4.1.0-SNAPSHOT
+distributionShortName=Maven
+distributionName=Apache Maven
+EOF
+
+# java stub: responds to -XshowSettings:properties -version by writing synthetic
+# JVM properties to stderr (matching the real format), and to -version by
+# exiting 0. When invoked for a full Maven launch it prints the arguments so
+# assertions can verify what was passed.
+cat > "$maven_home/bin/java" <<'STUB'
+#!/bin/sh
+# Full settings dump on stderr (real format: " key = value").
+case " $* " in
+ *" -XshowSettings:properties -version "*)
+ printf ' java.version = 17.0.20.1\n' >&2
+ printf ' java.vendor = Azul Systems, Inc.\n' >&2
+ printf ' java.home = /usr/lib/jvm/zulu17\n' >&2
+ printf ' user.language = en\n' >&2
+ printf ' user.country = US\n' >&2
+ printf ' file.encoding = UTF-8\n' >&2
+ printf ' os.name = Linux\n' >&2
+ printf ' os.version = 6.8.0-45-generic\n' >&2
+ printf ' os.arch = amd64\n' >&2
+ exit 0 ;;
+ *" -version "*) exit 0 ;;
+esac
+
+# Fallback: echo arguments for full-Maven launch assertions.
+for arg in "$@"; do printf '[%s]' "$arg"; done
+printf '\n'
+STUB
+
+chmod +x "$maven_home/bin/java" "$maven_home/bin/mvn"
+
+failures=0
+
+# run_mvn <extra args...>
+run_mvn() {
+ ( cd "$work_dir" &&
+ PATH="$maven_home/bin:$PATH" JAVA_HOME= MAVEN_SKIP_RC=1 \
+ "$sh_bin" "$maven_home/bin/mvn" "$@" 2>/dev/null )
+}
+
+# run_mvn_stderr <extra args...>
+# Same, but captures stderr (the version banner goes to stdout via the script).
+run_mvn_stderr() {
+ ( cd "$work_dir" &&
+ PATH="$maven_home/bin:$PATH" JAVA_HOME= MAVEN_SKIP_RC=1 \
+ "$sh_bin" "$maven_home/bin/mvn" "$@" )
+}
+
+# assert_eq <description> <expected> <actual>
+assert_eq() {
+ if [ "$2" = "$3" ]; then
+ printf 'ok - %s\n' "$1"
+ else
+ printf 'FAILED - %s\n' "$1"
+ printf ' expected: %s\n' "$2"
+ printf ' actual: %s\n' "$3"
+ failures=`expr $failures + 1`
+ fi
+}
+
+# assert_contains <description> <haystack> <needle>
+assert_contains() {
+ case "$2" in
+ *"$3"*) printf 'ok - %s\n' "$1" ;;
+ *)
+ printf 'FAILED - %s\n' "$1"
+ printf ' expected to contain: %s\n' "$3"
+ printf ' actual: %s\n' "$2"
+ failures=`expr $failures + 1`
+ ;;
+ esac
+}
+
+# assert_not_contains <description> <haystack> <needle>
+assert_not_contains() {
+ case "$2" in
+ *"$3"*)
+ printf 'FAILED - %s\n' "$1"
+ printf ' expected NOT to contain: %s\n' "$3"
+ printf ' actual: %s\n' "$2"
+ failures=`expr $failures + 1`
+ ;;
+ *) printf 'ok - %s\n' "$1" ;;
+ esac
+}
+
+# ---------------------------------------------------------------------------
+# Test: -v prints the full banner
+# ---------------------------------------------------------------------------
+output=`run_mvn -v`
+
+assert_contains "-v: prints Maven name and version" "$output" "Apache Maven 4.1.0-SNAPSHOT"
+assert_contains "-v: prints buildNumber" "$output" "abc123def"
+assert_contains "-v: prints Maven home" "$output" "Maven home: $maven_home"
+assert_contains "-v: prints Java version" "$output" "Java version: 17.0.20.1"
+assert_contains "-v: prints Java vendor" "$output" "vendor: Azul Systems, Inc."
+assert_contains "-v: prints Java runtime" "$output" "runtime: /usr/lib/jvm/zulu17"
+assert_contains "-v: prints locale" "$output" "Default locale: en_US"
+assert_contains "-v: prints encoding" "$output" "platform encoding: UTF-8"
+assert_contains "-v: prints OS name" "$output" "OS name: \"linux\""
+assert_contains "-v: prints OS arch" "$output" "arch: \"amd64\""
+assert_contains "-v: prints OS family" "$output" "family: \"unix\""
+
+# --version should produce identical output.
+output_long=`run_mvn --version`
+assert_eq "--version produces same output as -v" "$output" "$output_long"
+
+# ---------------------------------------------------------------------------
+# Test: -q/--quiet prints only the version string
+# ---------------------------------------------------------------------------
+output_q=`run_mvn -q -v`
+assert_eq "-q -v: prints only the version string" "4.1.0-SNAPSHOT" "$output_q"
+
+output_q_long=`run_mvn --quiet --version`
+assert_eq "--quiet --version: prints only the version string" "4.1.0-SNAPSHOT" "$output_q_long"
+
+# ---------------------------------------------------------------------------
+# Test: -V/--show-version prints banner AND launches Maven (not exit)
+# ---------------------------------------------------------------------------
+output_sv=`run_mvn -V`
+assert_contains "-V: prints banner" "$output_sv" "Apache Maven 4.1.0-SNAPSHOT"
+assert_contains "-V: passes -Dmaven.version.printed=true" "$output_sv" "[-Dmaven.version.printed=true]"
+
+output_sv_long=`run_mvn --show-version`
+assert_contains "--show-version: prints banner" "$output_sv_long" "Apache Maven 4.1.0-SNAPSHOT"
+assert_contains "--show-version: passes -Dmaven.version.printed=true" "$output_sv_long" "[-Dmaven.version.printed=true]"
+
+# ---------------------------------------------------------------------------
+# Test: -X/--debug bypasses the fast path (verbose = full JVM launch)
+# ---------------------------------------------------------------------------
+output_dbg=`run_mvn -v -X`
+assert_not_contains "-v -X: does NOT print banner from script" "$output_dbg" "Apache Maven 4.1.0-SNAPSHOT"
+assert_contains "-v -X: launches Maven with standard args" "$output_dbg" "[-Dmaven.home=$maven_home]"
+
+# ---------------------------------------------------------------------------
+# Test: --enc bypasses the fast path (MAIN_CLASS_OVERRIDE)
+# ---------------------------------------------------------------------------
+output_enc=`run_mvn -v --enc`
+assert_not_contains "-v --enc: does NOT print banner from script" "$output_enc" "Apache Maven 4.1.0-SNAPSHOT"
+assert_contains "-v --enc: uses MavenEncCling" "$output_enc" "MavenEncCling"
+
+# ---------------------------------------------------------------------------
+# Test: missing version file falls back to jar filename parsing
+# ---------------------------------------------------------------------------
+mv "$maven_home/bin/maven.version.properties" "$maven_home/bin/maven.version.properties.bak"
+output_novf=`run_mvn -v`
+assert_contains "missing version file: falls back to jar filename" "$output_novf" "Apache Maven 9.9.9"
+assert_contains "missing version file: prints home" "$output_novf" "Maven home: $maven_home"
+assert_not_contains "missing version file: no buildNumber" "$output_novf" "abc123def"
+mv "$maven_home/bin/maven.version.properties.bak" "$maven_home/bin/maven.version.properties"
+
+# ---------------------------------------------------------------------------
+# Test: -v in quiet mode from the missing-version fallback
+# ---------------------------------------------------------------------------
+mv "$maven_home/bin/maven.version.properties" "$maven_home/bin/maven.version.properties.bak"
+output_novf_q=`run_mvn -q -v`
+assert_eq "missing version file + -q: prints jar-parsed version" "9.9.9" "$output_novf_q"
+mv "$maven_home/bin/maven.version.properties.bak" "$maven_home/bin/maven.version.properties"
+
+if [ "$failures" -ne 0 ]; then
+ printf '%s assertion(s) failed\n' "$failures"
+ exit 1
+fi
+
+echo "All fast-version assertions passed"
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java
index d594307815..3e027ef4bb 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java
@@ -527,7 +527,7 @@ protected String describe(Terminal terminal) {
protected void preCommands(C context) throws Exception {
boolean verbose = context.invokerRequest.effectiveVerbose();
- boolean version = context.options().showVersion().orElse(false);
+ boolean version = context.options().showVersion().orElse(false) && !Boolean.getBoolean("maven.version.printed");
if (verbose || version) {
showVersion(context);
}