diff --git a/apache-maven/src/assembly/maven/bin/mvn b/apache-maven/src/assembly/maven/bin/mvn
index 8559d47af557..d11120d72c89 100755
--- a/apache-maven/src/assembly/maven/bin/mvn
+++ b/apache-maven/src/assembly/maven/bin/mvn
@@ -168,23 +168,40 @@ find_file_argument_basedir() {
# concatenates all lines of a file and replaces variables
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' ' '
+ result=""
+ # Read the file line by line
+ # Do not use `xargs -0` as this is not POSIX-compliant
+ while IFS= read -r line || [ -n "$line" ]; do
+ # Convert CR to LF
+ line=$(echo "$line" | tr '\r' '\n')
+ # Remove comments
+ line=$(echo "$line" | sed 's/#.*$//')
+ # Skip empty lines
+ [ -z "$(echo "$line" | tr -d ' \t')" ] && continue
+
+ # Process each argument in the line using eval to handle quotes
+ eval "set -- $line"
+ for arg in "$@"; do
+ # Replace variables
+ arg=$(echo "$arg" | sed \
+ -e "s@\${MAVEN_PROJECTBASEDIR}@$MAVEN_PROJECTBASEDIR@g" \
+ -e "s@\$MAVEN_PROJECTBASEDIR@$MAVEN_PROJECTBASEDIR@g")
+
+ # Quote the argument if it contains spaces or special shell characters
+ case "$arg" in
+ *[\ \|\&\;\<\>\(\)\$\`\\\"\'\~\*\?\[\]\#\~\=]*)
+ arg="\"$arg\""
+ ;;
+ esac
+
+ if [ -n "$result" ]; then
+ result="$result $arg"
+ else
+ result="$arg"
+ fi
+ done
+ done < "$1"
+ echo "$result"
fi
}
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11485AtSignInJvmConfigTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11485AtSignInJvmConfigTest.java
new file mode 100644
index 000000000000..63e3dc98bd02
--- /dev/null
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11485AtSignInJvmConfigTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.io.File;
+import java.util.Properties;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * This is a test set for GH-11485:
+ * Verify that @ character in .mvn/jvm.config values is handled correctly.
+ * This is important for Jenkins workspaces like workspace/project_PR-350@2
+ */
+public class MavenITgh11485AtSignInJvmConfigTest extends AbstractMavenIntegrationTestCase {
+
+ @Test
+ public void testAtSignInJvmConfig() throws Exception {
+ File testDir = extractResources("/gh-11485-at-sign");
+
+ Verifier verifier = newVerifier(testDir.getAbsolutePath());
+ verifier.addCliArgument(
+ "-Dexpression.outputFile=" + new File(testDir, "target/pom.properties").getAbsolutePath());
+ verifier.setForkJvm(true); // custom .mvn/jvm.config
+ verifier.addCliArgument("validate");
+ verifier.execute();
+ verifier.verifyErrorFreeLog();
+
+ Properties props = verifier.loadProperties("target/pom.properties");
+ String expectedPath = testDir.getAbsolutePath().replace('\\', '/');
+ assertEquals(
+ expectedPath + "/workspace@2/test",
+ props.getProperty("project.properties.pathWithAtProp").replace('\\', '/'),
+ "Path with @ character should be preserved");
+ assertEquals(
+ "value@test",
+ props.getProperty("project.properties.propWithAtProp"),
+ "Property value with @ character should be preserved");
+ }
+
+ @Test
+ public void testAtSignInCommandLineProperty() throws Exception {
+ File testDir = extractResources("/gh-11485-at-sign");
+
+ Verifier verifier = newVerifier(testDir.getAbsolutePath());
+ verifier.addCliArgument(
+ "-Dexpression.outputFile=" + new File(testDir, "target/pom.properties").getAbsolutePath());
+ verifier.setForkJvm(true); // custom .mvn/jvm.config
+ // Pass a path with @ character via command line (simulating Jenkins workspace)
+ String jenkinsPath = testDir.getAbsolutePath().replace('\\', '/') + "/jenkins.workspace/proj@2";
+ verifier.addCliArgument("-Dcmdline.path=" + jenkinsPath);
+ verifier.addCliArgument("-Dcmdline.value=test@value");
+ verifier.addCliArgument("validate");
+ verifier.execute();
+ verifier.verifyErrorFreeLog();
+
+ Properties props = verifier.loadProperties("target/pom.properties");
+ assertEquals(
+ jenkinsPath,
+ props.getProperty("project.properties.cmdlinePath").replace('\\', '/'),
+ "Command-line path with @ character should be preserved");
+ assertEquals(
+ "test@value",
+ props.getProperty("project.properties.cmdlineValue"),
+ "Command-line value with @ character should be preserved");
+ }
+}
+
diff --git a/its/core-it-suite/src/test/resources/gh-11485-at-sign/.mvn/jvm.config b/its/core-it-suite/src/test/resources/gh-11485-at-sign/.mvn/jvm.config
new file mode 100644
index 000000000000..6b5301311118
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11485-at-sign/.mvn/jvm.config
@@ -0,0 +1,2 @@
+-Dpath.with.at=${MAVEN_PROJECTBASEDIR}/workspace@2/test
+-Dprop.with.at=value@test
\ No newline at end of file
diff --git a/its/core-it-suite/src/test/resources/gh-11485-at-sign/pom.xml b/its/core-it-suite/src/test/resources/gh-11485-at-sign/pom.xml
new file mode 100644
index 000000000000..9fdbc2444b6e
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11485-at-sign/pom.xml
@@ -0,0 +1,70 @@
+
+
+
+ 4.0.0
+
+ org.apache.maven.its.gh11485
+ test
+ 1.0
+ pom
+
+ Test @ character in jvm.config
+
+ Verify that @ character in jvm.config values is handled correctly.
+ This is important for Jenkins workspaces like workspace/project_PR-350@2
+
+
+
+ ${path.with.at}
+ ${prop.with.at}
+ ${cmdline.path}
+ ${cmdline.value}
+
+
+
+
+
+ org.apache.maven.its.plugins
+ maven-it-plugin-expression
+ 2.1-SNAPSHOT
+
+
+ validate
+
+ eval
+
+
+ target/pom.properties
+
+ project/properties/pathWithAtProp
+ project/properties/propWithAtProp
+ project/properties/cmdlinePath
+ project/properties/cmdlineValue
+
+
+
+
+
+
+
+
+