diff --git a/run/src/main/scala/sbt/Fork.scala b/run/src/main/scala/sbt/Fork.scala index 2c4c102af..935f3fc91 100644 --- a/run/src/main/scala/sbt/Fork.scala +++ b/run/src/main/scala/sbt/Fork.scala @@ -166,7 +166,7 @@ object Fork { /** * Create an arguments file from a sequence of command line arguments - * by quoting each argument to a line with escaped backslashes + * by quoting each argument to a line with escaped backslashes and double quotes * * @param options command line options to write to the args file * @return @@ -178,7 +178,7 @@ object Fork { val pw = new PrintWriter(file) options.foreach { option => pw.write("\"") - pw.write(option.replace("\\", "\\\\")) + pw.write(option.replace("\\", "\\\\").replace("\"", "\\\"")) pw.write("\"") pw.write(System.lineSeparator()) } diff --git a/run/src/test/scala/sbt/ForkTest.scala b/run/src/test/scala/sbt/ForkTest.scala index 4faa668fa..68fc24aa4 100644 --- a/run/src/test/scala/sbt/ForkTest.scala +++ b/run/src/test/scala/sbt/ForkTest.scala @@ -64,6 +64,25 @@ object ForkTest extends Properties("Fork") { } } + property("Arguments with double quotes preserved in arguments file mode.") = { + val baos = new java.io.ByteArrayOutputStream() + val jsonArg = """{"a":1}""" + // Pad JVM options to exceed MaxConcatenatedOptionLength (5000) and trigger argsfile mode + val padding = "-Dproperty=" + ("X" * 5000) + val absClasspath = Path.makeString(requiredEntries) + val args = List("-cp", absClasspath, padding, "sbt.echoArgs", jsonArg) + val config = ForkOptions() + .withOutputStrategy(CustomOutput(baos)) + .withCanUseArgumentsFile(true) + val exitCode = + try Fork.java(config, args) + catch { case e: Exception => e.printStackTrace(); 1 } + val output = baos.toString("UTF-8").trim + s"exitCode: $exitCode" |: + s"output: '$output', expected: '$jsonArg'" |: + (exitCode == 0) && (output == jsonArg) + } + private def trimClasspath(cp: String): String = if (cp.length > MaximumClasspathLength) { val lastEntryI = cp.lastIndexOf(File.pathSeparatorChar.toInt, MaximumClasspathLength) @@ -80,3 +99,10 @@ object exit { System.exit(java.lang.Integer.parseInt(args(0))) } } + +// Echoes each argument on its own line, used to verify argument passing +object echoArgs { + def main(args: Array[String]): Unit = { + args.foreach(println) + } +} diff --git a/sbt-app/src/sbt-test/run/fork-argsfile-quotes/build.sbt b/sbt-app/src/sbt-test/run/fork-argsfile-quotes/build.sbt new file mode 100644 index 000000000..7c326238c --- /dev/null +++ b/sbt-app/src/sbt-test/run/fork-argsfile-quotes/build.sbt @@ -0,0 +1,6 @@ +scalaVersion := "3.6.4" +run / fork := true +// Force arguments file mode by exceeding MaxConcatenatedOptionLength (5000) +run / javaOptions += ("-Dsome.long.property=" + ("X" * 5000)) +// Pass JSON with double quotes as a system property — this goes through the argsfile +run / javaOptions += """-Djson={"a":1}""" diff --git a/sbt-app/src/sbt-test/run/fork-argsfile-quotes/src/main/scala/Main.scala b/sbt-app/src/sbt-test/run/fork-argsfile-quotes/src/main/scala/Main.scala new file mode 100644 index 000000000..2507ba7a8 --- /dev/null +++ b/sbt-app/src/sbt-test/run/fork-argsfile-quotes/src/main/scala/Main.scala @@ -0,0 +1,6 @@ +object Main { + def main(args: Array[String]): Unit = { + val json = System.getProperty("json") + assert(json == """{"a":1}""", s"""Expected '{"a":1}' but got '$json'""") + } +} diff --git a/sbt-app/src/sbt-test/run/fork-argsfile-quotes/test b/sbt-app/src/sbt-test/run/fork-argsfile-quotes/test new file mode 100644 index 000000000..97e62c271 --- /dev/null +++ b/sbt-app/src/sbt-test/run/fork-argsfile-quotes/test @@ -0,0 +1,2 @@ +# Verify that double quotes in JVM options survive the argsfile round-trip (sbt/sbt#7129) +> run