This commit is contained in:
Eugene Yokota 2022-01-17 02:31:37 -05:00
parent 4b928f16c2
commit 320b025993
7 changed files with 64 additions and 59 deletions

View File

@ -21,16 +21,18 @@ import java.util.Locale
/** /**
* Represents a command that can be forked. * Represents a command that can be forked.
* *
* @param commandName The java-like binary to fork. This is expected to exist in bin/ of the Java home directory. * @param commandName
* @param runnerClass If Some, this will be prepended to the `arguments` passed to the `apply` or `fork` methods. * The java-like binary to fork. This is expected to exist in bin/ of the Java home directory.
* @param runnerClass
* If Some, this will be prepended to the `arguments` passed to the `apply` or `fork` methods.
*/ */
final class Fork(val commandName: String, val runnerClass: Option[String]) { final class Fork(val commandName: String, val runnerClass: Option[String]) {
/** /**
* Forks the configured process, waits for it to complete, and returns the exit code. * Forks the configured process, waits for it to complete, and returns the exit code. The command
* The command executed is the `commandName` defined for this Fork instance. * executed is the `commandName` defined for this Fork instance. It is configured according to
* It is configured according to `config`. * `config`. If `runnerClass` is defined for this Fork instance, it is prepended to `arguments` to
* If `runnerClass` is defined for this Fork instance, it is prepended to `arguments` to define the arguments passed to the forked command. * define the arguments passed to the forked command.
*/ */
def apply(config: ForkOptions, arguments: Seq[String]): Int = { def apply(config: ForkOptions, arguments: Seq[String]): Int = {
val p = fork(config, arguments) val p = fork(config, arguments)
@ -43,10 +45,11 @@ final class Fork(val commandName: String, val runnerClass: Option[String]) {
} }
/** /**
* 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
* The command executed is the `commandName` defined for this Fork instance. * to terminate the forked process. The command executed is the `commandName` defined for this
* It is configured according to `config`. * Fork instance. It is configured according to `config`. If `runnerClass` is defined for this
* If `runnerClass` is defined for this Fork instance, it is prepended to `arguments` to define the arguments passed to the forked command. * 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 = {
import config.{ envVars => env, _ } import config.{ envVars => env, _ }
@ -108,7 +111,10 @@ object Fork {
private[this] def isClasspathOption(s: String) = private[this] def isClasspathOption(s: String) =
s == ClasspathOptionLong || s == ClasspathOptionShort s == ClasspathOptionLong || s == ClasspathOptionShort
/** Maximum length of classpath string before passing the classpath in an environment variable instead of an option. */ /**
* Maximum length of classpath string before passing the classpath in an environment variable
* instead of an option.
*/
private[this] val MaxConcatenatedOptionLength = 5000 private[this] val MaxConcatenatedOptionLength = 5000
private def fitClasspath(options: Seq[String]): (Option[String], Seq[String]) = private def fitClasspath(options: Seq[String]): (Option[String], Seq[String]) =

View File

@ -10,21 +10,21 @@ package sbt
import sbt.util.Logger import sbt.util.Logger
import java.io.OutputStream import java.io.OutputStream
/** Configures where the standard output and error streams from a forked process go.*/ /** Configures where the standard output and error streams from a forked process go. */
sealed abstract class OutputStrategy sealed abstract class OutputStrategy
object OutputStrategy { object OutputStrategy {
/** /**
* Configures the forked standard output to go to standard output of this process and * Configures the forked standard output to go to standard output of this process and for the
* for the forked standard error to go to the standard error of this process. * forked standard error to go to the standard error of this process.
*/ */
case object StdoutOutput extends OutputStrategy case object StdoutOutput extends OutputStrategy
/** /**
* Logs the forked standard output at the `info` level and the forked standard error at * Logs the forked standard output at the `info` level and the forked standard error at the
* the `error` level. The output is buffered until the process completes, at which point * `error` level. The output is buffered until the process completes, at which point the logger
* the logger flushes it (to the screen, for example). * flushes it (to the screen, for example).
*/ */
final class BufferedOutput private (val logger: Logger) extends OutputStrategy with Serializable { final class BufferedOutput private (val logger: Logger) extends OutputStrategy with Serializable {
override def equals(o: Any): Boolean = o match { override def equals(o: Any): Boolean = o match {
@ -49,8 +49,8 @@ object OutputStrategy {
} }
/** /**
* Logs the forked standard output at the `info` level and the forked standard error at * Logs the forked standard output at the `info` level and the forked standard error at the
* the `error` level. * `error` level.
*/ */
final class LoggedOutput private (val logger: Logger) extends OutputStrategy with Serializable { final class LoggedOutput private (val logger: Logger) extends OutputStrategy with Serializable {
override def equals(o: Any): Boolean = o match { override def equals(o: Any): Boolean = o match {
@ -75,8 +75,8 @@ object OutputStrategy {
} }
/** /**
* Configures the forked standard output to be sent to `output` and the forked standard error * Configures the forked standard output to be sent to `output` and the forked standard error to
* to be sent to the standard error of this process. * be sent to the standard error of this process.
*/ */
final class CustomOutput private (val output: OutputStream) final class CustomOutput private (val output: OutputStream)
extends OutputStrategy extends OutputStrategy

View File

@ -37,7 +37,8 @@ class ForkRun(config: ForkOptions) extends ScalaRun {
log.info(s"running (fork) $mainClass ${Run.runOptionsStr(options)}") log.info(s"running (fork) $mainClass ${Run.runOptionsStr(options)}")
val c = configLogged(log) val c = configLogged(log)
val scalaOpts = scalaOptions(mainClass, classpath, options) val scalaOpts = scalaOptions(mainClass, classpath, options)
val exitCode = try Fork.java(c, scalaOpts) val exitCode =
try Fork.java(c, scalaOpts)
catch { catch {
case _: InterruptedException => case _: InterruptedException =>
log.warn("Run canceled.") log.warn("Run canceled.")
@ -110,8 +111,7 @@ class Run(private[sbt] val newLoader: Seq[File] => ClassLoader, trapExit: Boolea
} }
} }
def directExecute(): Try[Unit] = def directExecute(): Try[Unit] =
Try(execute()) recover { Try(execute()) recover { case NonFatal(e) =>
case NonFatal(e) =>
// bgStop should not print out stack trace // bgStop should not print out stack trace
// log.trace(e) // log.trace(e)
throw e throw e
@ -121,7 +121,7 @@ class Run(private[sbt] val newLoader: Seq[File] => ClassLoader, trapExit: Boolea
else directExecute() else directExecute()
} }
/** Runs the class 'mainClass' using the given classpath and options using the scala runner.*/ /** Runs the class 'mainClass' using the given classpath and options using the scala runner. */
def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Try[Unit] = { def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Try[Unit] = {
val loader = newLoader(classpath) val loader = newLoader(classpath)
try runWithLoader(loader, classpath, mainClass, options, log) try runWithLoader(loader, classpath, mainClass, options, log)
@ -171,10 +171,10 @@ class Run(private[sbt] val newLoader: Seq[File] => ClassLoader, trapExit: Boolea
} }
} }
/** This module is an interface to starting the scala interpreter or runner.*/ /** This module is an interface to starting the scala interpreter or runner. */
object Run { object Run {
def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger)( def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger)(implicit
implicit runner: ScalaRun runner: ScalaRun
) = ) =
runner.run(mainClass, classpath, options, log) runner.run(mainClass, classpath, options, log)

View File

@ -45,8 +45,7 @@ object SelectMainClass {
private def toInt(s: String, size: Int): Option[Int] = private def toInt(s: String, size: Int): Option[Int] =
try { try {
val i = s.toInt val i = s.toInt
if (i > 0 && i <= size) if (i > 0 && i <= size) (i - 1).some
(i - 1).some
else { else {
println("Number out of range: was " + i + ", expected number between 1 and " + size) println("Number out of range: was " + i + ", expected number between 1 and " + size)
none none

View File

@ -10,29 +10,29 @@ package sbt
import sbt.util.Logger import sbt.util.Logger
/** /**
* Provides an approximation to isolated execution within a single JVM. * Provides an approximation to isolated execution within a single JVM. System.exit calls are
* System.exit calls are trapped to prevent the JVM from terminating. This is useful for executing * trapped to prevent the JVM from terminating. This is useful for executing user code that may call
* user code that may call System.exit, but actually exiting is undesirable. * System.exit, but actually exiting is undesirable.
* *
* Exit is simulated by disposing all top-level windows and interrupting user-started threads. * Exit is simulated by disposing all top-level windows and interrupting user-started threads.
* Threads are not stopped and shutdown hooks are not called. It is * Threads are not stopped and shutdown hooks are not called. It is therefore inappropriate to use
* therefore inappropriate to use this with code that requires shutdown hooks, creates threads that * this with code that requires shutdown hooks, creates threads that do not terminate, or if
* do not terminate, or if concurrent AWT applications are run. * concurrent AWT applications are run. This category of code should only be called by forking a new
* This category of code should only be called by forking a new JVM. * JVM.
*/ */
object TrapExit { object TrapExit {
/** /**
* Run `execute` in a managed context, using `log` for debugging messages. * Run `execute` in a managed context, using `log` for debugging messages. `installManager` must
* `installManager` must be called before calling this method. * be called before calling this method.
*/ */
@deprecated("TrapExit feature is removed; just call the function instead", "1.6.0") @deprecated("TrapExit feature is removed; just call the function instead", "1.6.0")
def apply(execute: => Unit, log: Logger): Int = def apply(execute: => Unit, log: Logger): Int =
runUnmanaged(execute, log) runUnmanaged(execute, log)
/** /**
* Installs the SecurityManager that implements the isolation and returns the previously installed SecurityManager, which may be null. * Installs the SecurityManager that implements the isolation and returns the previously installed
* This method must be called before using `apply`. * SecurityManager, which may be null. This method must be called before using `apply`.
*/ */
@deprecated("TrapExit feature is removed; just call the function instead", "1.6.0") @deprecated("TrapExit feature is removed; just call the function instead", "1.6.0")
def installManager(): Nothing = def installManager(): Nothing =

View File

@ -8,9 +8,9 @@
package sbt package sbt
/** /**
* A custom SecurityException that tries not to be caught. Closely based on a similar class in Nailgun. * A custom SecurityException that tries not to be caught. Closely based on a similar class in
* The main goal of this exception is that once thrown, it propagates all of the way up the call stack, * Nailgun. The main goal of this exception is that once thrown, it propagates all of the way up the
* terminating the thread's execution. * call stack, terminating the thread's execution.
*/ */
private final class TrapExitSecurityException(val exitCode: Int) extends SecurityException { private final class TrapExitSecurityException(val exitCode: Int) extends SecurityException {
override def printStackTrace = throw this override def printStackTrace = throw this

View File

@ -20,9 +20,9 @@ import sbt.internal.util.Util._
object ForkTest extends Properties("Fork") { object ForkTest extends Properties("Fork") {
/** /**
* Heuristic for limiting the length of the classpath string. * Heuristic for limiting the length of the classpath string. Longer than this will hit hard
* Longer than this will hit hard limits in the total space * limits in the total space allowed for process initialization, which includes environment
* allowed for process initialization, which includes environment variables, at least on linux. * variables, at least on linux.
*/ */
final val MaximumClasspathLength = 100000 final val MaximumClasspathLength = 100000
@ -49,7 +49,8 @@ object ForkTest extends Properties("Fork") {
val absClasspath = trimClasspath(Path.makeString(withScala)) val absClasspath = trimClasspath(Path.makeString(withScala))
val args = optionName.map(_ :: absClasspath :: Nil).toList.flatten ++ mainAndArgs val args = optionName.map(_ :: absClasspath :: Nil).toList.flatten ++ mainAndArgs
val config = ForkOptions().withOutputStrategy(LoggedOutput(log)) val config = ForkOptions().withOutputStrategy(LoggedOutput(log))
val exitCode = try Fork.java(config, args) val exitCode =
try Fork.java(config, args)
catch { case e: Exception => e.printStackTrace; 1 } catch { case e: Exception => e.printStackTrace; 1 }
val expectedCode = if (optionName.isEmpty) 1 else 0 val expectedCode = if (optionName.isEmpty) 1 else 0
s"temporary directory: ${dir.getAbsolutePath}" |: s"temporary directory: ${dir.getAbsolutePath}" |:
@ -69,8 +70,7 @@ object ForkTest extends Properties("Fork") {
cp.substring(0, lastEntryI) cp.substring(0, lastEntryI)
else else
cp cp
} else } else cp
cp
} }
// Object used in the tests // Object used in the tests