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.
*
* @param commandName 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.
* @param commandName
* 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]) {
/**
* Forks the configured process, waits for it to complete, and returns the exit code.
* The command executed is the `commandName` defined for this Fork instance.
* 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.
* Forks the configured process, waits for it to complete, and returns the exit code. The command
* executed is the `commandName` defined for this Fork instance. 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.
*/
def apply(config: ForkOptions, arguments: Seq[String]): Int = {
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.
* The command executed is the `commandName` defined for this Fork instance.
* 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.
* Forks the configured process and returns a `Process` that can be used to wait for completion or
* to terminate the forked process. The command executed is the `commandName` defined for this
* Fork instance. 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.
*/
def fork(config: ForkOptions, arguments: Seq[String]): Process = {
import config.{ envVars => env, _ }
@ -108,7 +111,10 @@ object Fork {
private[this] def isClasspathOption(s: String) =
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 def fitClasspath(options: Seq[String]): (Option[String], Seq[String]) =

View File

@ -10,21 +10,21 @@ package sbt
import sbt.util.Logger
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
object OutputStrategy {
/**
* Configures the forked standard output to go to standard output of this process and
* for the forked standard error to go to the standard error of this process.
* Configures the forked standard output to go to standard output of this process and for the
* forked standard error to go to the standard error of this process.
*/
case object StdoutOutput extends OutputStrategy
/**
* Logs the forked standard output at the `info` level and the forked standard error at
* the `error` level. The output is buffered until the process completes, at which point
* the logger flushes it (to the screen, for example).
* Logs the forked standard output at the `info` level and the forked standard error at the
* `error` level. The output is buffered until the process completes, at which point the logger
* flushes it (to the screen, for example).
*/
final class BufferedOutput private (val logger: Logger) extends OutputStrategy with Serializable {
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
* the `error` level.
* Logs the forked standard output at the `info` level and the forked standard error at the
* `error` level.
*/
final class LoggedOutput private (val logger: Logger) extends OutputStrategy with Serializable {
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
* to be sent to the standard error of this process.
* Configures the forked standard output to be sent to `output` and the forked standard error to
* be sent to the standard error of this process.
*/
final class CustomOutput private (val output: OutputStream)
extends OutputStrategy

View File

@ -37,12 +37,13 @@ class ForkRun(config: ForkOptions) extends ScalaRun {
log.info(s"running (fork) $mainClass ${Run.runOptionsStr(options)}")
val c = configLogged(log)
val scalaOpts = scalaOptions(mainClass, classpath, options)
val exitCode = try Fork.java(c, scalaOpts)
catch {
case _: InterruptedException =>
log.warn("Run canceled.")
1
}
val exitCode =
try Fork.java(c, scalaOpts)
catch {
case _: InterruptedException =>
log.warn("Run canceled.")
1
}
processExitCode(exitCode, "runner")
}
@ -110,18 +111,17 @@ class Run(private[sbt] val newLoader: Seq[File] => ClassLoader, trapExit: Boolea
}
}
def directExecute(): Try[Unit] =
Try(execute()) recover {
case NonFatal(e) =>
// bgStop should not print out stack trace
// log.trace(e)
throw e
Try(execute()) recover { case NonFatal(e) =>
// bgStop should not print out stack trace
// log.trace(e)
throw e
}
if (trapExit) Run.executeSuccess(execute())
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] = {
val loader = newLoader(classpath)
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 {
def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger)(
implicit runner: ScalaRun
def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger)(implicit
runner: ScalaRun
) =
runner.run(mainClass, classpath, options, log)

View File

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

View File

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

View File

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

View File

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