diff --git a/build.sbt b/build.sbt index 20ee54543..1d36d038d 100644 --- a/build.sbt +++ b/build.sbt @@ -167,9 +167,13 @@ lazy val stdTaskProj = (project in file("tasks-standard")) // Embedded Scala code runner lazy val runProj = (project in file("run")) + .enablePlugins(ContrabandPlugin) .settings( testedBaseSettings, - name := "Run" + name := "Run", + managedSourceDirectories in Compile += + baseDirectory.value / "src" / "main" / "contraband-scala", + sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala" ) .configure(addSbtIO, addSbtUtilLogging, addSbtCompilerClasspath) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 58e33dfc3..711f08118 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -682,13 +682,13 @@ object Defaults extends BuildCommon { def forkOptionsTask: Initialize[Task[ForkOptions]] = Def.task { ForkOptions( - // bootJars is empty by default because only jars on the user's classpath should be on the boot classpath - bootJars = Nil, javaHome = javaHome.value, - connectInput = connectInput.value, outputStrategy = outputStrategy.value, - runJVMOptions = javaOptions.value, + // bootJars is empty by default because only jars on the user's classpath should be on the boot classpath + bootJars = Vector(), workingDirectory = Some(baseDirectory.value), + runJVMOptions = javaOptions.value.toVector, + connectInput = connectInput.value, envVars = envVars.value ) } diff --git a/project/plugins.sbt b/project/plugins.sbt index 53033e12b..44b25400d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -8,4 +8,4 @@ scalacOptions ++= Seq("-feature", "-language:postfixOps") addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "0.7.0-RC1") // addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.2.0") addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.4.0") -addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.0-M4") +addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.0-M5") diff --git a/run/src/main/contraband-scala/sbt/ForkOptions.scala b/run/src/main/contraband-scala/sbt/ForkOptions.scala new file mode 100644 index 000000000..4b87a122e --- /dev/null +++ b/run/src/main/contraband-scala/sbt/ForkOptions.scala @@ -0,0 +1,85 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt +/** Configures forking. */ +final class ForkOptions private ( + /** The Java installation to use. If not defined, the Java home for the current process is used. */ + val javaHome: Option[java.io.File], + /** + * Configures the forked standard output and error streams. + * If not defined, StdoutOutput is used, which maps the forked output to the output of + * this process and the forked error to the error stream of the forking process. + */ + val outputStrategy: Option[sbt.OutputStrategy], + /** The Vector of jars to put on the forked boot classpath. By default, this is empty. */ + val bootJars: Vector[java.io.File], + /** + * The directory to use as the working directory for the forked process. + * By default, this is the working directory of the forking process. + */ + val workingDirectory: Option[java.io.File], + /** The options to prepend to all user-specified arguments. By default, this is empty. */ + val runJVMOptions: Vector[String], + /** + * If true, the standard input of the forked process is connected to the standard input of this process. Otherwise, it is connected to an empty input stream. + * Connecting input streams can be problematic, especially on versions before Java 7. + */ + val connectInput: Boolean, + /** The environment variables to provide to the forked process. By default, none are provided. */ + val envVars: scala.collection.immutable.Map[String, String]) extends Serializable { + + private def this() = this(None, None, Vector(), None, Vector(), false, Map()) + + override def equals(o: Any): Boolean = o match { + case x: ForkOptions => (this.javaHome == x.javaHome) && (this.outputStrategy == x.outputStrategy) && (this.bootJars == x.bootJars) && (this.workingDirectory == x.workingDirectory) && (this.runJVMOptions == x.runJVMOptions) && (this.connectInput == x.connectInput) && (this.envVars == x.envVars) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + javaHome.##) + outputStrategy.##) + bootJars.##) + workingDirectory.##) + runJVMOptions.##) + connectInput.##) + envVars.##) + } + override def toString: String = { + "ForkOptions(" + javaHome + ", " + outputStrategy + ", " + bootJars + ", " + workingDirectory + ", " + runJVMOptions + ", " + connectInput + ", " + envVars + ")" + } + protected[this] def copy(javaHome: Option[java.io.File] = javaHome, outputStrategy: Option[sbt.OutputStrategy] = outputStrategy, bootJars: Vector[java.io.File] = bootJars, workingDirectory: Option[java.io.File] = workingDirectory, runJVMOptions: Vector[String] = runJVMOptions, connectInput: Boolean = connectInput, envVars: scala.collection.immutable.Map[String, String] = envVars): ForkOptions = { + new ForkOptions(javaHome, outputStrategy, bootJars, workingDirectory, runJVMOptions, connectInput, envVars) + } + def withJavaHome(javaHome: Option[java.io.File]): ForkOptions = { + copy(javaHome = javaHome) + } + def withJavaHome(javaHome: java.io.File): ForkOptions = { + copy(javaHome = Option(javaHome)) + } + def withOutputStrategy(outputStrategy: Option[sbt.OutputStrategy]): ForkOptions = { + copy(outputStrategy = outputStrategy) + } + def withOutputStrategy(outputStrategy: sbt.OutputStrategy): ForkOptions = { + copy(outputStrategy = Option(outputStrategy)) + } + def withBootJars(bootJars: Vector[java.io.File]): ForkOptions = { + copy(bootJars = bootJars) + } + def withWorkingDirectory(workingDirectory: Option[java.io.File]): ForkOptions = { + copy(workingDirectory = workingDirectory) + } + def withWorkingDirectory(workingDirectory: java.io.File): ForkOptions = { + copy(workingDirectory = Option(workingDirectory)) + } + def withRunJVMOptions(runJVMOptions: Vector[String]): ForkOptions = { + copy(runJVMOptions = runJVMOptions) + } + def withConnectInput(connectInput: Boolean): ForkOptions = { + copy(connectInput = connectInput) + } + def withEnvVars(envVars: scala.collection.immutable.Map[String, String]): ForkOptions = { + copy(envVars = envVars) + } +} +object ForkOptions { + + def apply(): ForkOptions = new ForkOptions(None, None, Vector(), None, Vector(), false, Map()) + def apply(javaHome: Option[java.io.File], outputStrategy: Option[sbt.OutputStrategy], bootJars: Vector[java.io.File], workingDirectory: Option[java.io.File], runJVMOptions: Vector[String], connectInput: Boolean, envVars: scala.collection.immutable.Map[String, String]): ForkOptions = new ForkOptions(javaHome, outputStrategy, bootJars, workingDirectory, runJVMOptions, connectInput, envVars) + def apply(javaHome: java.io.File, outputStrategy: sbt.OutputStrategy, bootJars: Vector[java.io.File], workingDirectory: java.io.File, runJVMOptions: Vector[String], connectInput: Boolean, envVars: scala.collection.immutable.Map[String, String]): ForkOptions = new ForkOptions(Option(javaHome), Option(outputStrategy), bootJars, Option(workingDirectory), runJVMOptions, connectInput, envVars) +} diff --git a/run/src/main/contraband/run.contra b/run/src/main/contraband/run.contra new file mode 100644 index 000000000..0cb61c7c3 --- /dev/null +++ b/run/src/main/contraband/run.contra @@ -0,0 +1,30 @@ +package sbt +@target(Scala) + +## Configures forking. +type ForkOptions { + ## The Java installation to use. If not defined, the Java home for the current process is used. + javaHome: java.io.File @since("0.1.0") + + ## Configures the forked standard output and error streams. + ## If not defined, StdoutOutput is used, which maps the forked output to the output of + ## this process and the forked error to the error stream of the forking process. + outputStrategy: sbt.OutputStrategy @since("0.1.0") + + ## The Vector of jars to put on the forked boot classpath. By default, this is empty. + bootJars: [java.io.File] @since("0.1.0") + + ## The directory to use as the working directory for the forked process. + ## By default, this is the working directory of the forking process. + workingDirectory: java.io.File @since("0.1.0") + + ## The options to prepend to all user-specified arguments. By default, this is empty. + runJVMOptions: [String] @since("0.1.0") + + ## If true, the standard input of the forked process is connected to the standard input of this process. Otherwise, it is connected to an empty input stream. + ## Connecting input streams can be problematic, especially on versions before Java 7. + connectInput: Boolean! = false @since("0.1.0") + + ## The environment variables to provide to the forked process. By default, none are provided. + envVars: StringStringMap! = raw"Map()" @since("0.1.0") +} diff --git a/run/src/main/scala/sbt/Fork.scala b/run/src/main/scala/sbt/Fork.scala index 8f481bf65..4ce6cfffc 100644 --- a/run/src/main/scala/sbt/Fork.scala +++ b/run/src/main/scala/sbt/Fork.scala @@ -3,56 +3,10 @@ */ package sbt -import java.io.{ File, OutputStream } +import java.io.File import java.util.Locale - -import sbt.util.Logger import scala.sys.process.Process - -/** - * Configures forking. - * - * @param javaHome The Java installation to use. If not defined, the Java home for the current process is used. - * @param outputStrategy Configures the forked standard output and error streams. If not defined, StdoutOutput is used, which maps the forked output to the output of this process and the forked error to the error stream of the forking process. - * @param bootJars The list of jars to put on the forked boot classpath. By default, this is empty. - * @param workingDirectory The directory to use as the working directory for the forked process. By default, this is the working directory of the forking process. - * @param runJVMOptions The options to prepend to all user-specified arguments. By default, this is empty. - * @param connectInput If true, the standard input of the forked process is connected to the standard input of this process. Otherwise, it is connected to an empty input stream. Connecting input streams can be problematic, especially on versions before Java 7. - * @param envVars The environment variables to provide to the forked process. By default, none are provided. - */ -final case class ForkOptions( - javaHome: Option[File] = None, - outputStrategy: Option[OutputStrategy] = None, - bootJars: Seq[File] = Nil, - workingDirectory: Option[File] = None, - runJVMOptions: Seq[String] = Nil, - connectInput: Boolean = false, - envVars: Map[String, String] = Map.empty -) - -/** Configures where the standard output and error streams from a forked process go.*/ -sealed abstract class 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. - */ -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). - */ -case class BufferedOutput(logger: Logger) extends OutputStrategy - -/** Logs the forked standard output at the `info` level and the forked standard error at the `error` level. */ -case class LoggedOutput(logger: Logger) extends 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. - */ -case class CustomOutput(output: OutputStream) extends OutputStrategy +import OutputStrategy._ /** * Represents a command that can be forked. @@ -83,14 +37,17 @@ final class Fork(val commandName: String, val runnerClass: Option[String]) { val (classpathEnv, options) = Fork.fitClasspath(preOptions) val command = executable +: options - val environment = env ++ classpathEnv.map(value => Fork.ClasspathEnvKey -> value) + val environment: List[(String, String)] = env.toList ++ + (classpathEnv map { value => + Fork.ClasspathEnvKey -> value + }) val process = Process(command, workingDirectory, environment.toList: _*) outputStrategy.getOrElse(StdoutOutput) match { - case StdoutOutput => process.run(connectInput) - case BufferedOutput(logger) => logger.buffer { process.run(logger, connectInput) } - case LoggedOutput(logger) => process.run(logger, connectInput) - case CustomOutput(output) => (process #> output).run(connectInput) + case StdoutOutput => process.run(connectInput) + case out: BufferedOutput => out.logger.buffer { process.run(out.logger, connectInput) } + case out: LoggedOutput => process.run(out.logger, connectInput) + case out: CustomOutput => (process #> out.output).run(connectInput) } } private[this] def makeOptions(jvmOptions: Seq[String], diff --git a/run/src/main/scala/sbt/OutputStrategy.scala b/run/src/main/scala/sbt/OutputStrategy.scala new file mode 100644 index 000000000..73168d15b --- /dev/null +++ b/run/src/main/scala/sbt/OutputStrategy.scala @@ -0,0 +1,99 @@ +package sbt + +import sbt.util.Logger +import java.io.OutputStream + +/** 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. + */ + 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). + */ + final class BufferedOutput private (val logger: Logger) + extends OutputStrategy + with Serializable { + override def equals(o: Any): Boolean = o match { + case x: BufferedOutput => (this.logger == x.logger) + case _ => false + } + override def hashCode: Int = { + 37 * (17 + logger.##) + "BufferedOutput".## + } + override def toString: String = { + "BufferedOutput(" + logger + ")" + } + protected[this] def copy(logger: Logger = logger): BufferedOutput = { + new BufferedOutput(logger) + } + def withLogger(logger: Logger): BufferedOutput = { + copy(logger = logger) + } + } + object BufferedOutput { + def apply(logger: Logger): BufferedOutput = new BufferedOutput(logger) + } + + /** + * 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 { + case x: LoggedOutput => (this.logger == x.logger) + case _ => false + } + override def hashCode: Int = { + 37 * (17 + logger.##) + "LoggedOutput".## + } + override def toString: String = { + "LoggedOutput(" + logger + ")" + } + protected[this] def copy(logger: Logger = logger): LoggedOutput = { + new LoggedOutput(logger) + } + def withLogger(logger: Logger): LoggedOutput = { + copy(logger = logger) + } + } + object LoggedOutput { + def apply(logger: Logger): LoggedOutput = new LoggedOutput(logger) + } + + /** + * 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 + with Serializable { + override def equals(o: Any): Boolean = o match { + case x: CustomOutput => (this.output == x.output) + case _ => false + } + override def hashCode: Int = { + 37 * (17 + output.##) + "CustomOutput".## + } + override def toString: String = { + "CustomOutput(" + output + ")" + } + protected[this] def copy(output: OutputStream = output): CustomOutput = { + new CustomOutput(output) + } + def withOutput(output: OutputStream): CustomOutput = { + copy(output = output) + } + } + object CustomOutput { + def apply(output: OutputStream): CustomOutput = new CustomOutput(output) + } +} diff --git a/run/src/main/scala/sbt/Run.scala b/run/src/main/scala/sbt/Run.scala index a3d4e6b5e..121448a31 100644 --- a/run/src/main/scala/sbt/Run.scala +++ b/run/src/main/scala/sbt/Run.scala @@ -44,7 +44,7 @@ class ForkRun(config: ForkOptions) extends ScalaRun { val scalaOptions = classpathOption(classpath) ::: mainClass :: options.toList val configLogged = if (config.outputStrategy.isDefined) config - else config.copy(outputStrategy = Some(LoggedOutput(log))) + else config.withOutputStrategy(OutputStrategy.LoggedOutput(log)) // fork with Java because Scala introduces an extra class loader (#702) Fork.java.fork(configLogged, scalaOptions) } diff --git a/run/src/test/scala/sbt/ForkTest.scala b/run/src/test/scala/sbt/ForkTest.scala index 2e13a9db2..6b715a7b1 100644 --- a/run/src/test/scala/sbt/ForkTest.scala +++ b/run/src/test/scala/sbt/ForkTest.scala @@ -7,6 +7,7 @@ import java.io.File import sbt.internal.TestLogger import sbt.io.{ IO, Path } +import OutputStrategy._ object ForkTest extends Properties("Fork") { @@ -39,7 +40,7 @@ object ForkTest extends Properties("Fork") { val withScala = requiredEntries ::: relCP.map(rel => new File(dir, rel)) val absClasspath = trimClasspath(Path.makeString(withScala)) val args = optionName.map(_ :: absClasspath :: Nil).toList.flatten ++ mainAndArgs - val config = ForkOptions(outputStrategy = Some(LoggedOutput(log))) + val config = ForkOptions().withOutputStrategy(LoggedOutput(log)) val exitCode = try Fork.java(config, args) catch { case e: Exception => e.printStackTrace; 1 } val expectedCode = if (optionName.isEmpty) 1 else 0 diff --git a/sbt/src/main/scala/Import.scala b/sbt/src/main/scala/Import.scala index 2f8a9c976..ca8575c69 100644 --- a/sbt/src/main/scala/Import.scala +++ b/sbt/src/main/scala/Import.scala @@ -8,6 +8,15 @@ trait Import { type URI = java.net.URI type URL = java.net.URL + // sbt + val StdoutOutput = sbt.OutputStrategy.StdoutOutput + type BufferedOutput = sbt.OutputStrategy.BufferedOutput + val BufferedOutput = sbt.OutputStrategy.BufferedOutput + type LoggedOutput = sbt.OutputStrategy.LoggedOutput + val LoggedOutput = sbt.OutputStrategy.LoggedOutput + type CustomOutput = sbt.OutputStrategy.CustomOutput + val CustomOutput = sbt.OutputStrategy.CustomOutput + // sbt.testing type TestResult = sbt.protocol.testing.TestResult val TestResult = sbt.protocol.testing.TestResult diff --git a/sbt/src/sbt-test/tests/empty/build.sbt b/sbt/src/sbt-test/tests/empty/build.sbt index d003ce2f9..a4448f16f 100644 --- a/sbt/src/sbt-test/tests/empty/build.sbt +++ b/sbt/src/sbt-test/tests/empty/build.sbt @@ -4,7 +4,7 @@ testGrouping := { new Tests.Group( name = test.name, tests = Seq(test), - runPolicy = Tests.SubProcess(ForkOptions(runJVMOptions = Seq.empty[String])) + runPolicy = Tests.SubProcess(ForkOptions().withRunJVMOptions(Vector())) ) } } diff --git a/sbt/src/sbt-test/tests/fork-test-group-parallel/build.sbt b/sbt/src/sbt-test/tests/fork-test-group-parallel/build.sbt index 549691d2c..4c8f89643 100644 --- a/sbt/src/sbt-test/tests/fork-test-group-parallel/build.sbt +++ b/sbt/src/sbt-test/tests/fork-test-group-parallel/build.sbt @@ -4,13 +4,13 @@ libraryDependencies += "org.specs2" %% "specs2-core" % "3.8.4" % Test inConfig(Test)(Seq( testGrouping := definedTests.value.map { test => new Tests.Group(test.name, Seq(test), Tests.SubProcess( ForkOptions( - javaHome.value, - outputStrategy.value, - Nil, - Some(baseDirectory.value), - javaOptions.value, - connectInput.value, - envVars.value + javaHome = javaHome.value, + outputStrategy = outputStrategy.value, + bootJars = Vector(), + workingDirectory = Some(baseDirectory.value), + runJVMOptions = javaOptions.value.toVector, + connectInput = connectInput.value, + envVars = envVars.value ) ))}, TaskKey[Unit]("test-failure") := test.failure.value diff --git a/sbt/src/sbt-test/tests/fork/build.sbt b/sbt/src/sbt-test/tests/fork/build.sbt index 2629c28b9..cb68da8a6 100644 --- a/sbt/src/sbt-test/tests/fork/build.sbt +++ b/sbt/src/sbt-test/tests/fork/build.sbt @@ -19,7 +19,7 @@ lazy val root = (project in file(".")). new Group( groupId(idx), tests, - SubProcess(ForkOptions(runJVMOptions = Seq("-Dgroup.prefix=" + groupPrefix(idx)))) + SubProcess(ForkOptions().withRunJVMOptions(Vector("-Dgroup.prefix=" + groupPrefix(idx)))) ) }, check := {