diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index 11400ea98..22de59306 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -1194,6 +1194,77 @@ object NetworkClient { private[client] val noTab = "--no-tab" private[client] val noStdErr = "--no-stderr" private[client] val sbtBase = "--sbt-base-directory" + // Launcher flags that take a value argument (flag + next arg should be skipped) + private[client] val launcherValueFlags: Set[String] = Set( + "-mem", + "--mem", + "-jvm-debug", + "--jvm-debug", + "-sbt-jar", + "--sbt-jar", + "-sbt-cache", + "--sbt-cache", + "-sbt-version", + "--sbt-version", + "-java-home", + "--java-home", + "-ivy", + "--ivy", + "-sbt-boot", + "--sbt-boot", + "-sbt-dir", + "--sbt-dir", + ) + // Launcher flags that take no value (flag itself should be skipped) + private[client] val launcherNoValueFlags: Set[String] = Set( + "-client", + "--client", + "--server", + "--jvm-client", + "-h", + "-help", + "--help", + "-v", + "-verbose", + "--verbose", + "-V", + "-version", + "--version", + "--numeric-version", + "--script-version", + "-d", + "-debug", + "--debug", + "-debug-inc", + "--debug-inc", + "-batch", + "--batch", + "--no-hide-jdk-warnings", + "-no-colors", + "--no-colors", + "-timings", + "--timings", + "-traces", + "--traces", + "-no-server", + "--no-server", + "-no-share", + "--no-share", + "-no-global", + "--no-global", + "-allow-empty", + "--allow-empty", + "-sbt-create", + "--sbt-create", + "shutdownall", + ) + // Prefixes for launcher flags using = syntax + private[client] val launcherEqPrefixes: Seq[String] = Seq( + "--supershell=", + "-supershell=", + "--color=", + "-color=", + ) private[client] def parseArgs(args: Array[String]): Arguments = { val defaultSbtScript = if (Properties.isWin) "sbt.bat" else "sbt" var sbtScript = Properties.propOrNone("sbt.script") @@ -1230,8 +1301,13 @@ object NetworkClient { case "--sbt-launch-jar" if i + 1 < sanitized.length => i += 1 launchJar = Option(sanitized(i).replace("%20", " ")) - case "-bsp" | "--bsp" => bsp = true - case a if !a.startsWith("-") => commandArgs += a + case "-bsp" | "--bsp" => bsp = true + case a if launcherValueFlags.contains(a) => + if (i + 1 < sanitized.length) i += 1 + case a if launcherNoValueFlags.contains(a) => () + case a if launcherEqPrefixes.exists(p => a.startsWith(p)) => () + case a if a.startsWith("-J") => () + case a if !a.startsWith("-") => commandArgs += a case a @ SysProp(key, value) => System.setProperty(key, value) sbtArguments += a diff --git a/main-command/src/test/scala/sbt/internal/client/NetworkClientParseArgsTest.scala b/main-command/src/test/scala/sbt/internal/client/NetworkClientParseArgsTest.scala new file mode 100644 index 000000000..40f174e21 --- /dev/null +++ b/main-command/src/test/scala/sbt/internal/client/NetworkClientParseArgsTest.scala @@ -0,0 +1,146 @@ +/* + * sbt + * Copyright 2023, Scala center + * Copyright 2011 - 2022, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt.internal.client + +import verify.BasicTestSuite + +object NetworkClientParseArgsTest extends BasicTestSuite: + + private def parse(args: String*): NetworkClient.Arguments = + NetworkClient.parseArgs(args.toArray) + + // -- Value-taking launcher flags -- + + test("-mem 10000 compile drops -mem and its value"): + val result = parse("-mem", "10000", "compile") + assert(!result.sbtArguments.contains("-mem")) + assert(!result.sbtArguments.contains("10000")) + assert(!result.commandArguments.contains("-mem")) + assert(!result.commandArguments.contains("10000")) + assert(result.commandArguments.contains("compile")) + + test("--mem 10000 compile drops --mem and its value"): + val result = parse("--mem", "10000", "compile") + assert(!result.sbtArguments.contains("--mem")) + assert(!result.sbtArguments.contains("10000")) + assert(result.commandArguments.contains("compile")) + + test("-jvm-debug 5005 compile drops both flag and port"): + val result = parse("-jvm-debug", "5005", "compile") + assert(!result.sbtArguments.contains("-jvm-debug")) + assert(!result.sbtArguments.contains("5005")) + assert(!result.commandArguments.contains("-jvm-debug")) + assert(!result.commandArguments.contains("5005")) + assert(result.commandArguments.contains("compile")) + + test("-java-home /path/to/jdk compile drops both"): + val result = parse("-java-home", "/path/to/jdk", "compile") + assert(!result.sbtArguments.contains("-java-home")) + assert(!result.sbtArguments.contains("/path/to/jdk")) + assert(!result.commandArguments.contains("-java-home")) + assert(!result.commandArguments.contains("/path/to/jdk")) + assert(result.commandArguments.contains("compile")) + + test("-mem at end of args with no value does not crash"): + val result = parse("-mem") + assert(!result.sbtArguments.contains("-mem")) + assert(result.commandArguments.isEmpty) + + // -- No-value launcher flags -- + + test("--client compile drops --client"): + val result = parse("--client", "compile") + assert(!result.sbtArguments.contains("--client")) + assert(!result.commandArguments.contains("--client")) + assert(result.commandArguments.contains("compile")) + + test("-client compile drops -client"): + val result = parse("-client", "compile") + assert(!result.sbtArguments.contains("-client")) + assert(!result.commandArguments.contains("-client")) + assert(result.commandArguments.contains("compile")) + + test("-debug is dropped"): + val result = parse("-debug", "compile") + assert(!result.sbtArguments.contains("-debug")) + assert(!result.commandArguments.contains("-debug")) + assert(result.commandArguments.contains("compile")) + + test("-batch is dropped"): + val result = parse("-batch", "compile") + assert(!result.sbtArguments.contains("-batch")) + assert(result.commandArguments.contains("compile")) + + test("-allow-empty is dropped"): + val result = parse("-allow-empty", "compile") + assert(!result.sbtArguments.contains("-allow-empty")) + assert(!result.commandArguments.contains("-allow-empty")) + assert(result.commandArguments.contains("compile")) + + // -- Eq-syntax flags and -J* -- + + test("--supershell=false is dropped"): + val result = parse("--supershell=false", "compile") + assert(!result.sbtArguments.exists(_.contains("supershell"))) + assert(result.commandArguments.contains("compile")) + + test("--color=never is dropped"): + val result = parse("--color=never", "compile") + assert(!result.sbtArguments.exists(_.contains("color"))) + assert(result.commandArguments.contains("compile")) + + test("-J-Xss4m is dropped"): + val result = parse("-J-Xss4m", "compile") + assert(!result.sbtArguments.contains("-J-Xss4m")) + assert(result.commandArguments.contains("compile")) + + // -- Flags that should be preserved -- + + test("-Dfoo=bar compile forwards -D property to sbtArguments"): + val result = parse("-Dfoo=bar", "compile") + assert(result.sbtArguments.exists(_.contains("-Dfoo=bar"))) + assert(result.commandArguments.contains("compile")) + + test("-bsp is still recognized"): + val result = parse("-bsp") + assert(result.bsp) + + test("--sbt-launch-jar is preserved"): + val result = parse("--sbt-launch-jar", "/path/to/sbt-launch.jar", "compile") + assert(result.sbtLaunchJar.contains("/path/to/sbt-launch.jar")) + assert(result.commandArguments.contains("compile")) + + test("--sbt-script is preserved"): + val result = parse("--sbt-script", "/usr/bin/sbt", "compile") + assert(result.sbtScript == "/usr/bin/sbt") + assert(result.commandArguments.contains("compile")) + + // -- Combined / integration -- + + test("combined: -mem 10000 -Dfoo=bar compile test"): + val result = parse("-mem", "10000", "-Dfoo=bar", "compile", "test") + assert(!result.sbtArguments.contains("-mem")) + assert(!result.sbtArguments.contains("10000")) + assert(result.sbtArguments.exists(_.contains("-Dfoo=bar"))) + assert(result.commandArguments.contains("compile")) + assert(result.commandArguments.contains("test")) + + test("combined: --client -batch -java-home /jdk --color=never -Dfoo=bar compile"): + val result = + parse("--client", "-batch", "-java-home", "/jdk", "--color=never", "-Dfoo=bar", "compile") + assert(!result.sbtArguments.contains("--client")) + assert(!result.sbtArguments.contains("-batch")) + assert(!result.sbtArguments.contains("-java-home")) + assert(!result.sbtArguments.contains("/jdk")) + assert(!result.sbtArguments.exists(_.contains("color=never"))) + assert(result.sbtArguments.exists(_.contains("-Dfoo=bar"))) + assert(result.commandArguments.contains("compile")) + assert(result.commandArguments.size == 1) + +end NetworkClientParseArgsTest