From 461e12d4970c63f0105755e83e853448f5c0f781 Mon Sep 17 00:00:00 2001 From: PandaMan Date: Fri, 30 Jan 2026 05:51:06 +0800 Subject: [PATCH] [2.x] fix: Handle paths with parentheses in sbt.bat on Windows (#8656) Windows CMD interprets parentheses as special syntax for command grouping. When the project directory path contains parentheses (e.g., in username), the batch script fails with ') was unexpected at this time.' error. This fix stores the current directory in a variable using delayed expansion (!CURRENT_DIR!) instead of using %CD% directly, which properly handles paths containing parentheses and other special characters. Fixes #8644 --- .../src/test/scala/ExtendedRunnerTest.scala | 50 +++++++++++++++++++ launcher-package/src/universal/bin/sbt.bat | 5 +- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/launcher-package/integration-test/src/test/scala/ExtendedRunnerTest.scala b/launcher-package/integration-test/src/test/scala/ExtendedRunnerTest.scala index 7ce9aedd4..69e312126 100755 --- a/launcher-package/integration-test/src/test/scala/ExtendedRunnerTest.scala +++ b/launcher-package/integration-test/src/test/scala/ExtendedRunnerTest.scala @@ -181,4 +181,54 @@ object ExtendedRunnerTest extends BasicTestSuite: } () } + + // Test for issue #8644: sbt.bat fails when project path contains parentheses + // https://github.com/sbt/sbt/issues/8644 + test("sbt.bat handles paths with parentheses") { + if (!isWindows) { + // This test is Windows-specific, skip on other platforms + () + } else { + IO.withTemporaryDirectory { baseDir => + // Create a temporary directory with parentheses in the name + val testDir = new File(baseDir, "test(parentheses)") + + // Create the directory structure + IO.createDirectory(testDir) + val projectDir = new File(testDir, "project") + IO.createDirectory(projectDir) + + // Create a minimal build.properties to make it a valid sbt project + val buildProps = new File(projectDir, "build.properties") + IO.write(buildProps, "sbt.version=1.12.1\n") + + // Test 1: Run sbt from directory with parentheses - should work without parsing errors + val out1 = sbtProcessInDir(testDir)("--script-version").!!.trim + val expectedVersion = "^" + versionRegEx + "$" + assert(out1.matches(expectedVersion), s"Expected version format, got: $out1") + + // Test 2: Test error message when no build.sbt exists (this is where the fix is most visible) + // Create a directory with parentheses but no build.sbt + val emptyDir = new File(baseDir, "empty(parentheses)") + IO.createDirectory(emptyDir) + + // Run sbt from empty directory - should fail gracefully with proper error message + // Use ProcessLogger to capture stderr without throwing on non-zero exit + import scala.sys.process.ProcessLogger + val errorBuffer = new StringBuilder + val logger = ProcessLogger( + _ => (), // ignore stdout + line => errorBuffer.append(line).append("\n") // capture stderr + ) + val exitCode = sbtProcessInDir(emptyDir)("compile").!(logger) + assert(exitCode == 1, "Expected sbt to fail when no build.sbt exists") + + // Verify the error output doesn't contain ") was unexpected" parsing error + val errorOutput = errorBuffer.toString + val hasParsingError = errorOutput.contains(") was unexpected") + assert(!hasParsingError, s"Error message should not contain parsing error when path has parentheses. Error output: $errorOutput") + } + } + () + } end ExtendedRunnerTest diff --git a/launcher-package/src/universal/bin/sbt.bat b/launcher-package/src/universal/bin/sbt.bat index 64091f484..867df3308 100755 --- a/launcher-package/src/universal/bin/sbt.bat +++ b/launcher-package/src/universal/bin/sbt.bat @@ -582,13 +582,16 @@ if exist project\build.properties ( set is_this_dir_sbt=1 ) +rem Store current directory in a variable for delayed expansion to handle paths with parentheses +set "CURRENT_DIR=%CD%" + rem Confirm a user's intent if the current directory does not look like an sbt rem top-level directory and the "new" command was not given. if not defined sbt_args_allow_empty if not defined sbt_args_print_version if not defined sbt_args_print_sbt_version if not defined sbt_args_print_sbt_script_version if not defined shutdownall ( if not !is_this_dir_sbt! equ 1 ( if not defined sbt_new ( - >&2 echo [error] Neither build.sbt nor a 'project' directory in the current directory: "%CD%" + >&2 echo [error] Neither build.sbt nor a 'project' directory in the current directory: "!CURRENT_DIR!" >&2 echo [error] run 'sbt new', touch build.sbt, or run 'sbt --allow-empty'. goto error )