diff --git a/main/src/main/scala/sbt/BackgroundJobService.scala b/main/src/main/scala/sbt/BackgroundJobService.scala index 58d3f25e1..7b7b30955 100644 --- a/main/src/main/scala/sbt/BackgroundJobService.scala +++ b/main/src/main/scala/sbt/BackgroundJobService.scala @@ -5,6 +5,7 @@ import sbt.util.Logger import Def.{ ScopedKey, Classpath } import sbt.internal.util.complete._ import java.io.File +import scala.util.Try abstract class BackgroundJobService extends Closeable { @@ -24,6 +25,12 @@ abstract class BackgroundJobService extends Closeable { def shutdown(): Unit def jobs: Vector[JobHandle] def stop(job: JobHandle): Unit + + def waitForTry(job: JobHandle): Try[Unit] = { + // This implementation is provided only for backward compatibility. + Try(waitFor(job)) + } + def waitFor(job: JobHandle): Unit /** Copies classpath to temporary directories. */ diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index d2143f31f..b45ce316b 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -503,8 +503,8 @@ object Defaults extends BuildCommon { selectMainClass := mainClass.value orElse askForMainClass(discoveredMainClasses.value), mainClass in run := (selectMainClass in run).value, mainClass := pickMainClassOrWarn(discoveredMainClasses.value, streams.value.log), - runMain := runMainTask(fullClasspath, runner in run).evaluated, - run := runTask(fullClasspath, mainClass in run, runner in run).evaluated, + runMain := foregroundRunMainTask.evaluated, + run := foregroundRunTask.evaluated, copyResources := copyResourcesTask.value, // note that we use the same runner and mainClass as plain run bgRunMain := bgRunMainTask(exportedProductJars, @@ -1168,14 +1168,14 @@ object Defaults extends BuildCommon { Def.inputTask { val handle = bgRunMain.evaluated val service = bgJobService.value - service.waitFor(handle) + service.waitForTry(handle).get } // run calls bgRun in the background and waits for the result. def foregroundRunTask: Initialize[InputTask[Unit]] = Def.inputTask { val handle = bgRun.evaluated val service = bgJobService.value - service.waitFor(handle) + service.waitForTry(handle).get } def runMainTask(classpath: Initialize[Task[Classpath]], scalaRun: Initialize[Task[ScalaRun]]): Initialize[InputTask[Unit]] = { @@ -1540,8 +1540,11 @@ object Defaults extends BuildCommon { lazy val configSettings : Seq[Setting[_]] = Classpaths.configSettings ++ configTasks ++ configPaths ++ packageConfig ++ Classpaths.compilerPluginConfig ++ deprecationSettings - lazy val compileSettings - : Seq[Setting[_]] = configSettings ++ (mainBgRunMainTask +: mainBgRunTask +: addBaseSources) ++ Classpaths.addUnmanagedLibrary + lazy val compileSettings: Seq[Setting[_]] = + configSettings ++ + (mainBgRunMainTask +: mainBgRunTask +: addBaseSources) ++ + Classpaths.addUnmanagedLibrary + lazy val testSettings: Seq[Setting[_]] = configSettings ++ testTasks lazy val itSettings: Seq[Setting[_]] = inConfig(IntegrationTest)(testSettings) diff --git a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala index b2680a729..ea08836a9 100644 --- a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala +++ b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala @@ -5,6 +5,7 @@ import java.util.concurrent.atomic.AtomicLong import java.io.Closeable import Def.{ ScopedKey, Setting, Classpath } import scala.concurrent.ExecutionContext +import scala.util.Try import Scope.GlobalScope import java.io.File import sbt.io.{ IO, Hash } @@ -18,6 +19,13 @@ import sbt.internal.util.{ Attributed, ManagedLogger } private[sbt] abstract class BackgroundJob { def humanReadableName: String def awaitTermination(): Unit + + /** This waits till the job ends, and returns inner error via `Try`. */ + def awaitTerminationTry(): Try[Unit] = { + // This implementation is provided only for backward compatibility. + Try(awaitTermination()) + } + def shutdown(): Unit // this should be true on construction and stay true until // the job is complete @@ -132,15 +140,27 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe private def withHandle(job: JobHandle)(f: ThreadJobHandle => Unit): Unit = job match { case handle: ThreadJobHandle @unchecked => f(handle) - case dead: DeadHandle @unchecked => () // nothing to stop or wait for + case _: DeadHandle @unchecked => () // nothing to stop or wait for case other => sys.error( s"BackgroundJobHandle does not originate with the current BackgroundJobService: $other") } + private def withHandleTry(job: JobHandle)(f: ThreadJobHandle => Try[Unit]): Try[Unit] = + job match { + case handle: ThreadJobHandle @unchecked => f(handle) + case _: DeadHandle @unchecked => Try(()) // nothing to stop or wait for + case other => + Try(sys.error( + s"BackgroundJobHandle does not originate with the current BackgroundJobService: $other")) + } + override def stop(job: JobHandle): Unit = withHandle(job)(_.job.shutdown()) + override def waitForTry(job: JobHandle): Try[Unit] = + withHandleTry(job)(_.job.awaitTerminationTry()) + override def waitFor(job: JobHandle): Unit = withHandle(job)(_.job.awaitTermination()) @@ -212,6 +232,9 @@ private[sbt] class BackgroundThreadPool extends java.io.Closeable { @volatile private var status: Status = Waiting + // This is used to capture exceptions that are caught in this background job. + private var exitTry: Option[Try[Unit]] = None + // double-finally for extra paranoia that we will finishedLatch.countDown override def run() = try { @@ -226,7 +249,11 @@ private[sbt] class BackgroundThreadPool extends java.io.Closeable { throw new RuntimeException("Impossible status of bg thread") } } - try { if (go) body() } finally cleanup() + try { + if (go) { + exitTry = Option(Try(body())) + } + } finally cleanup() } finally finishedLatch.countDown() private class StopListener(val callback: () => Unit, val executionContext: ExecutionContext) @@ -269,6 +296,12 @@ private[sbt] class BackgroundThreadPool extends java.io.Closeable { result } override def awaitTermination(): Unit = finishedLatch.await() + + override def awaitTerminationTry(): Try[Unit] = { + awaitTermination() + exitTry.getOrElse(Try(())) + } + override def humanReadableName: String = taskName override def isRunning(): Boolean = status match { diff --git a/sbt/src/sbt-test/compiler-project/run-test/build.sbt b/sbt/src/sbt-test/compiler-project/run-test/build.sbt index 52cd6d1f6..1e2352020 100644 --- a/sbt/src/sbt-test/compiler-project/run-test/build.sbt +++ b/sbt/src/sbt-test/compiler-project/run-test/build.sbt @@ -1,6 +1,9 @@ +scalaVersion in ThisBuild := "2.12.3" + libraryDependencies ++= Seq( - "com.novocode" % "junit-interface" % "0.5" % "test", - "junit" % "junit" % "4.8" % "test" + "com.novocode" % "junit-interface" % "0.5" % Test, + "junit" % "junit" % "4.8" % Test, + "commons-io" % "commons-io" % "2.5" % Runtime, ) libraryDependencies += scalaVersion("org.scala-lang" % "scala-compiler" % _ ).value diff --git a/sbt/src/sbt-test/compiler-project/run-test/src/main/scala/Foo.scala b/sbt/src/sbt-test/compiler-project/run-test/src/main/scala/Foo.scala index 872ab523e..4f45a02cc 100644 --- a/sbt/src/sbt-test/compiler-project/run-test/src/main/scala/Foo.scala +++ b/sbt/src/sbt-test/compiler-project/run-test/src/main/scala/Foo.scala @@ -28,10 +28,11 @@ class Foo { catch { case _: URISyntaxException => new File(url.getPath) } } -object Test -{ - def main(args: Array[String]) - { +object Test { + def main(args: Array[String]): Unit = { + // test that Runtime configuration is included + Class.forName("org.apache.commons.io.ByteOrderMark") + val foo = new Foo args.foreach { arg => foo.eval(arg) == arg.toInt } } diff --git a/sbt/src/sbt-test/project/flatten/build.sbt b/sbt/src/sbt-test/project/flatten/build.sbt index 819170f8e..bbe93fbcc 100644 --- a/sbt/src/sbt-test/project/flatten/build.sbt +++ b/sbt/src/sbt-test/project/flatten/build.sbt @@ -17,7 +17,10 @@ def unpackageSettings(name: String) = Seq( unmanagedSourceDirectories := (baseDirectory.value / name) :: Nil, excludeFilter in unmanagedResources := (includeFilter in unmanagedSources).value, unmanagedResourceDirectories := unmanagedSourceDirectories.value, - unpackage := IO.unzip(artifactPath in packageSrc value, baseDirectory.value / name) + unpackage := { + IO.unzip(artifactPath in packageSrc value, baseDirectory.value / name) + IO.delete(baseDirectory.value / name / "META-INF") + } ) val unpackage = TaskKey[Unit]("unpackage")