mirror of https://github.com/sbt/sbt.git
[1.x] Use JProcess for interactive forking (#8678)
**Problem/Solution** For forking that require both input and output, use a Java process instead of the Scala process.
This commit is contained in:
parent
6e974bb283
commit
2380082bcc
|
|
@ -180,7 +180,8 @@ def mimaSettingsSince(versions: Seq[String]): Seq[Def.Setting[_]] = Def settings
|
||||||
exclude[DirectMissingMethodProblem]("sbt.PluginData.this"),
|
exclude[DirectMissingMethodProblem]("sbt.PluginData.this"),
|
||||||
exclude[IncompatibleResultTypeProblem]("sbt.EvaluateTask.executeProgress"),
|
exclude[IncompatibleResultTypeProblem]("sbt.EvaluateTask.executeProgress"),
|
||||||
exclude[DirectMissingMethodProblem]("sbt.Keys.currentTaskProgress"),
|
exclude[DirectMissingMethodProblem]("sbt.Keys.currentTaskProgress"),
|
||||||
exclude[IncompatibleResultTypeProblem]("sbt.PluginData.copy$default$10")
|
exclude[IncompatibleResultTypeProblem]("sbt.PluginData.copy$default$10"),
|
||||||
|
exclude[IncompatibleMethTypeProblem]("sbt.internal.util.*"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
package sbt.internal.util
|
package sbt.internal.util
|
||||||
|
|
||||||
|
import java.lang.{ Process => JProcess }
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import scala.sys.process.Process
|
import scala.sys.process.Process
|
||||||
|
|
||||||
|
|
@ -18,17 +19,21 @@ import scala.sys.process.Process
|
||||||
* processes when the user inputs ctrl+c.
|
* processes when the user inputs ctrl+c.
|
||||||
*/
|
*/
|
||||||
private[sbt] object RunningProcesses {
|
private[sbt] object RunningProcesses {
|
||||||
val active = ConcurrentHashMap.newKeySet[Process]
|
val active = ConcurrentHashMap.newKeySet[AnyRef]
|
||||||
def add(process: Process): Unit = active.synchronized {
|
def add(process: AnyRef): Unit = active.synchronized {
|
||||||
active.add(process)
|
active.add(process)
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
def remove(process: Process): Unit = active.synchronized {
|
def remove(process: AnyRef): Unit = active.synchronized {
|
||||||
active.remove(process)
|
active.remove(process)
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
def killAll(): Unit = active.synchronized {
|
def killAll(): Unit = active.synchronized {
|
||||||
active.forEach(_.destroy())
|
active.forEach {
|
||||||
|
case p: Process => p.destroy()
|
||||||
|
case p: JProcess => p.destroy()
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
active.clear()
|
active.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import OutputStrategy._
|
||||||
import sbt.internal.util.{ RunningProcesses, Util }
|
import sbt.internal.util.{ RunningProcesses, Util }
|
||||||
import Util.{ AnyOps, none }
|
import Util.{ AnyOps, none }
|
||||||
|
|
||||||
import java.lang.{ ProcessBuilder => JProcessBuilder }
|
import java.lang.{ ProcessBuilder => JProcessBuilder, Process => JProcess }
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -33,8 +33,18 @@ final class Fork(val commandName: String, val runnerClass: Option[String]) {
|
||||||
* It is configured according to `config`.
|
* It is configured according to `config`.
|
||||||
* If `runnerClass` is defined for this Fork instance, it is prepended to `arguments` to define the arguments passed to the forked command.
|
* If `runnerClass` is defined for this Fork instance, it is prepended to `arguments` to define the arguments passed to the forked command.
|
||||||
*/
|
*/
|
||||||
def apply(config: ForkOptions, arguments: Seq[String]): Int =
|
def apply(config: ForkOptions, arguments: Seq[String]): Int = {
|
||||||
Fork.blockForExitCode(fork(config, arguments))
|
import config._
|
||||||
|
val outStrategy = outputStrategy.getOrElse(StdoutOutput)
|
||||||
|
if (connectInput && outStrategy == StdoutOutput)
|
||||||
|
Fork.blockJForExitCode(interactiveFork(config, arguments))
|
||||||
|
else Fork.blockForExitCode(fork(config, arguments))
|
||||||
|
}
|
||||||
|
|
||||||
|
private def interactiveFork(config: ForkOptions, arguments: Seq[String]): JProcess = {
|
||||||
|
val (extraEnv, jpb) = prep(config, arguments)
|
||||||
|
Fork.forkInternalInteractive(config, extraEnv, jpb)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forks the configured process and returns a `Process` that can be used to wait for completion or to terminate the forked process.
|
* Forks the configured process and returns a `Process` that can be used to wait for completion or to terminate the forked process.
|
||||||
|
|
@ -43,6 +53,14 @@ final class Fork(val commandName: String, val runnerClass: Option[String]) {
|
||||||
* If `runnerClass` is defined for this Fork instance, it is prepended to `arguments` to define the arguments passed to the forked command.
|
* If `runnerClass` is defined for this Fork instance, it is prepended to `arguments` to define the arguments passed to the forked command.
|
||||||
*/
|
*/
|
||||||
def fork(config: ForkOptions, arguments: Seq[String]): Process = {
|
def fork(config: ForkOptions, arguments: Seq[String]): Process = {
|
||||||
|
val (extraEnv, jpb) = prep(config, arguments)
|
||||||
|
Fork.forkInternal(config, extraEnv, jpb)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def prep(
|
||||||
|
config: ForkOptions,
|
||||||
|
arguments: Seq[String]
|
||||||
|
): (List[(String, String)], JProcessBuilder) = {
|
||||||
import config._
|
import config._
|
||||||
val executable = Fork.javaCommand(javaHome, commandName).getAbsolutePath
|
val executable = Fork.javaCommand(javaHome, commandName).getAbsolutePath
|
||||||
val preOptions = makeOptions(runJVMOptions, bootJars, arguments)
|
val preOptions = makeOptions(runJVMOptions, bootJars, arguments)
|
||||||
|
|
@ -58,7 +76,7 @@ final class Fork(val commandName: String, val runnerClass: Option[String]) {
|
||||||
val extraEnv = classpathEnv.toList.map { value =>
|
val extraEnv = classpathEnv.toList.map { value =>
|
||||||
Fork.ClasspathEnvKey -> value
|
Fork.ClasspathEnvKey -> value
|
||||||
}
|
}
|
||||||
Fork.forkInternal(config, extraEnv, jpb)
|
(extraEnv, jpb)
|
||||||
}
|
}
|
||||||
|
|
||||||
private[this] def makeOptions(
|
private[this] def makeOptions(
|
||||||
|
|
@ -187,6 +205,30 @@ object Fork {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private[sbt] def forkInternalInteractive(
|
||||||
|
config: ForkOptions,
|
||||||
|
extraEnv: List[(String, String)],
|
||||||
|
jpb: JProcessBuilder
|
||||||
|
): JProcess = {
|
||||||
|
import config.{ envVars => env, _ }
|
||||||
|
val environment: List[(String, String)] = env.toList ++ extraEnv
|
||||||
|
workingDirectory.foreach(jpb.directory(_))
|
||||||
|
environment.foreach { case (k, v) => jpb.environment.put(k, v) }
|
||||||
|
jpb.inheritIO()
|
||||||
|
jpb.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private[sbt] def blockJForExitCode(p: JProcess): Int = {
|
||||||
|
RunningProcesses.add(p)
|
||||||
|
try {
|
||||||
|
p.waitFor()
|
||||||
|
p.exitValue()
|
||||||
|
} finally {
|
||||||
|
if (p.isAlive()) p.destroy()
|
||||||
|
RunningProcesses.remove(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private[sbt] def blockForExitCode(p: Process): Int = {
|
private[sbt] def blockForExitCode(p: Process): Int = {
|
||||||
RunningProcesses.add(p)
|
RunningProcesses.add(p)
|
||||||
try p.exitValue()
|
try p.exitValue()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue