[1.x] fix: Handle JVM parameters with spaces in dot files (#8730) (#8738)

**Problem**

The sbt launcher script used naive word splitting when parsing `.sbtopts` and `.jvmopts`, so arguments with spaces were split incorrectly. For example, `-J--add-modules jdk.incubator.concurrent` in `.sbtopts` and `-Dtest.key="value with spaces"` in `.jvmopts` were not passed to the JVM as intended.

Co-authored-by: PandaMan <joewartson757@gmail.com>
This commit is contained in:
eugene yokota 2026-02-14 02:00:45 -08:00 committed by GitHub
parent c8b2b03f8a
commit 44187ea00b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 84 additions and 5 deletions

View File

@ -227,6 +227,30 @@ object RunnerScriptTest extends verify.BasicTestSuite with ShellScriptUtil:
s"sbtopts options should appear before CLI memory settings. g1Index=$g1Index, xmxCliIndex=$xmxCliIndex" s"sbtopts options should appear before CLI memory settings. g1Index=$g1Index, xmxCliIndex=$xmxCliIndex"
) )
// Test for issue #7333: JVM parameters with spaces in .sbtopts
testOutput(
"sbt with -J--add-modules jdk.incubator.concurrent in .sbtopts (args with spaces)",
sbtOptsFileContents = "-J--add-modules jdk.incubator.concurrent",
windowsSupport = false,
)("-v"): (out: List[String]) =>
assert(out.contains[String]("--add-modules"))
assert(out.contains[String]("jdk.incubator.concurrent"))
// Test for issue #7333: -D with spaces in .jvmopts
testOutput(
"sbt with -Dkey=\"value with spaces\" in .jvmopts",
jvmoptsFileContents = """-Dtest.7333="value with spaces"""",
windowsSupport = false,
)("-v"): (out: List[String]) =>
assert(
out.exists(_.contains("test.7333")),
s"Expected -Dtest.7333= in output, got: ${out.filter(_.contains("test.7333")).mkString(", ")}"
)
assert(
out.exists(_.contains("value with spaces")),
"Expected 'value with spaces' in -D value"
)
// Test for issue #7289: Special characters in .jvmopts should not cause shell expansion // Test for issue #7289: Special characters in .jvmopts should not cause shell expansion
testOutput( testOutput(
"sbt with special characters in .jvmopts (pipes, wildcards, ampersands)", "sbt with special characters in .jvmopts (pipes, wildcards, ampersands)",

65
sbt
View File

@ -763,6 +763,60 @@ process_args () {
} }
} }
# Parse a line into words respecting double and single quotes.
# Outputs one word per line. Used for .sbtopts and .jvmopts to handle args with spaces (#7333).
parseLineIntoWords() {
local line="$1"
local word=""
local i=0
local len=${#line}
local in_dq=0 in_sq=0
while (( i < len )); do
local c="${line:$i:1}"
if (( in_dq )); then
word+="$c"
[[ "$c" == '"' ]] && in_dq=0
elif (( in_sq )); then
word+="$c"
[[ "$c" == "'" ]] && in_sq=0
else
case "$c" in
'"') in_dq=1; word+="$c" ;;
"'") in_sq=1; word+="$c" ;;
' '|$'\t')
[[ -n "$word" ]] && printf '%s\n' "$word"
word=""
;;
*) word+="$c" ;;
esac
fi
((i++))
done
[[ -n "$word" ]] && printf '%s\n' "$word"
}
# Output config file tokens one per line. For -J lines, each token is prefixed with -J.
# No eval; caller appends via: while IFS= read -r t; do [[ -n "$t" ]] && arr+=("$t"); done < <(outputConfigFileTokens "$file")
# Fixes #7333; Bash 3.x compatible.
outputConfigFileTokens() {
local file="$1"
[[ ! -f "$file" ]] && return
while IFS= read -r line || [[ -n "$line" ]]; do
line=$(printf '%s' "$line" | sed $'/^\#/d;s/\r$//')
[[ -z "$line" ]] && continue
if [[ "$line" == -J* ]]; then
local rest="${line#-J}"
while IFS= read -r token; do
[[ -n "$token" ]] && printf '%s\n' "-J$token"
done < <(parseLineIntoWords "$rest")
else
while IFS= read -r token; do
[[ -n "$token" ]] && printf '%s\n' "$token"
done < <(parseLineIntoWords "$line")
fi
done < <(cat "$file")
}
loadConfigFile() { loadConfigFile() {
# Make sure the last line is read even if it doesn't have a terminating \n # Make sure the last line is read even if it doesn't have a terminating \n
# Output lines literally without shell expansion to handle special characters safely # Output lines literally without shell expansion to handle special characters safely
@ -858,14 +912,14 @@ sbt_file_opts=()
# Pull in the machine-wide settings configuration. # Pull in the machine-wide settings configuration.
if [[ -f "$machine_sbt_opts_file" ]]; then if [[ -f "$machine_sbt_opts_file" ]]; then
sbt_file_opts+=($(loadConfigFile "$machine_sbt_opts_file")) while IFS= read -r t; do [[ -n "$t" ]] && sbt_file_opts+=("$t"); done < <(outputConfigFileTokens "$machine_sbt_opts_file")
else else
# Otherwise pull in the default settings configuration. # Otherwise pull in the default settings configuration.
[[ -f "$dist_sbt_opts_file" ]] && sbt_file_opts+=($(loadConfigFile "$dist_sbt_opts_file")) [[ -f "$dist_sbt_opts_file" ]] && while IFS= read -r t; do [[ -n "$t" ]] && sbt_file_opts+=("$t"); done < <(outputConfigFileTokens "$dist_sbt_opts_file")
fi fi
# Pull in the project-level config file, if it exists (highest priority, overrides machine/dist). # Pull in the project-level config file, if it exists (highest priority, overrides machine/dist).
[[ -f "$sbt_opts_file" ]] && sbt_file_opts+=($(loadConfigFile "$sbt_opts_file")) [[ -f "$sbt_opts_file" ]] && while IFS= read -r t; do [[ -n "$t" ]] && sbt_file_opts+=("$t"); done < <(outputConfigFileTokens "$sbt_opts_file")
# Prepend sbtopts so command line args appear last and win for duplicate properties. # Prepend sbtopts so command line args appear last and win for duplicate properties.
if (( ${#sbt_file_opts[@]} > 0 )); then if (( ${#sbt_file_opts[@]} > 0 )); then
@ -873,14 +927,15 @@ if (( ${#sbt_file_opts[@]} > 0 )); then
fi fi
# Pull in the project-level java config, if it exists. # Pull in the project-level java config, if it exists.
[[ -f ".jvmopts" ]] && export JAVA_OPTS="$JAVA_OPTS $(loadConfigFile .jvmopts)" jvmopts_args=()
[[ -f ".jvmopts" ]] && while IFS= read -r t; do [[ -n "$t" ]] && jvmopts_args+=("$t"); done < <(outputConfigFileTokens ".jvmopts")
# Pull in default JAVA_OPTS # Pull in default JAVA_OPTS
[[ -z "${JAVA_OPTS// }" ]] && export JAVA_OPTS="$default_java_opts" [[ -z "${JAVA_OPTS// }" ]] && export JAVA_OPTS="$default_java_opts"
[[ -f "$build_props_file" ]] && loadPropFile "$build_props_file" [[ -f "$build_props_file" ]] && loadPropFile "$build_props_file"
java_args=($JAVA_OPTS) java_args=($JAVA_OPTS "${jvmopts_args[@]}")
sbt_options0=(${SBT_OPTS:-$default_sbt_opts}) sbt_options0=(${SBT_OPTS:-$default_sbt_opts})
java_tool_options=($JAVA_TOOL_OPTIONS) java_tool_options=($JAVA_TOOL_OPTIONS)
jdk_java_options=($JDK_JAVA_OPTIONS) jdk_java_options=($JDK_JAVA_OPTIONS)