Skip to content

Commit ca45370

Browse files
committed
Add sbt.argsfile option
There are a number of scenarios, including long classpaths, that can cause linux and other systems to fail to fork a jvm. We can work around this on JDK9 and above by using an "arguments file" [1]. This patch adds an "sbt.argsfile" property and SBT_ARGSFILE environment variable. This option defaults to true. When active on Java 9+, long command lines are translated to an arguments file by adding all command line arguments to the arguments file format at [1] and java is invoked using the arguments file instead of passing the arguments on the command line. 1. https://docs.oracle.com/javase/9/tools/java.htm# Fixes: sbt#2974
1 parent 5bd4405 commit ca45370

File tree

1 file changed

+60
-1
lines changed

1 file changed

+60
-1
lines changed

run/src/main/scala/sbt/Fork.scala

+60-1
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
package sbt
99

1010
import java.io.File
11+
import java.io.PrintWriter
1112
import java.lang.ProcessBuilder.Redirect
1213
import scala.sys.process.Process
1314
import OutputStrategy._
1415
import sbt.internal.util.{ RunningProcesses, Util }
1516
import Util.{ AnyOps, none }
1617

1718
import java.lang.{ ProcessBuilder => JProcessBuilder }
19+
import java.util.Locale
1820

1921
/**
2022
* Represents a command that can be forked.
@@ -57,7 +59,11 @@ final class Fork(val commandName: String, val runnerClass: Option[String]) {
5759
(classpathEnv map { value =>
5860
Fork.ClasspathEnvKey -> value
5961
})
60-
val jpb = new JProcessBuilder(command.toArray: _*)
62+
val jpb =
63+
if (Fork.shouldUseArgumentsFile(options))
64+
new JProcessBuilder(executable, Fork.createArgumentsFile(options))
65+
else
66+
new JProcessBuilder(command.toArray: _*)
6167
workingDirectory foreach (jpb directory _)
6268
environment foreach { case (k, v) => jpb.environment.put(k, v) }
6369
if (connectInput) {
@@ -125,4 +131,57 @@ object Fork {
125131
val home = javaHome.getOrElse(new File(System.getProperty("java.home")))
126132
new File(new File(home, "bin"), name)
127133
}
134+
135+
/* copied from SysProp.scala for consistency while avoiding
136+
* introducing a circular dependency
137+
*/
138+
private def parseBoolean(value: String): Option[Boolean] =
139+
value.toLowerCase(Locale.ENGLISH) match {
140+
case "1" | "always" | "true" => Some(true)
141+
case "0" | "never" | "false" => Some(false)
142+
case "auto" => None
143+
case _ => None
144+
}
145+
private def booleanOpt(name: String): Option[Boolean] =
146+
sys.props.get(name) match {
147+
case Some(x) => parseBoolean(x)
148+
case _ =>
149+
sys.env.get(name.toUpperCase(Locale.ENGLISH).replace('.', '_')) match {
150+
case Some(x) => parseBoolean(x)
151+
case _ => None
152+
}
153+
}
154+
155+
/** Use an arguments file if:
156+
* - we are on jdk >= 9
157+
* - sbt.argfile is unset or not falsy
158+
* - the command line length would exceed MaxConcatenatedOptionLength
159+
*/
160+
private def shouldUseArgumentsFile(options: Seq[String]): Boolean =
161+
(sys.props.getOrElse("java.vm.specification.version", "1").toFloat >= 9.0) &&
162+
booleanOpt("sbt.argsfile").getOrElse(true) &&
163+
(options.mkString.length > MaxConcatenatedOptionLength)
164+
165+
/**
166+
* Create an arguments file from a sequence of command line arguments
167+
* by quoting each argument to a line with escaped backslashes
168+
*
169+
* @param options command line options to write to the args file
170+
* @return
171+
*/
172+
private def createArgumentsFile(options: Seq[String]): String = {
173+
val file = File.createTempFile(s"sbt-args", ".tmp")
174+
file.deleteOnExit()
175+
176+
val pw = new PrintWriter(file)
177+
options.foreach { option =>
178+
pw.write("\"")
179+
pw.write(option.replace("\\", "\\\\"))
180+
pw.write("\"")
181+
pw.write(System.lineSeparator())
182+
}
183+
pw.flush()
184+
pw.close()
185+
s"@${file.getAbsolutePath}"
186+
}
128187
}

0 commit comments

Comments
 (0)