From 53e41a0045a38823d693c2b4792c876b71398db7 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 11 Sep 2017 17:54:13 +0100 Subject: [PATCH 1/2] Dedup defining bgRun/bgRunMain --- main/src/main/scala/sbt/Defaults.scala | 102 +++++++++++++++---------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index bed43f59e..927f984bd 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -500,15 +500,8 @@ object Defaults extends BuildCommon { run := foregroundRunTask.evaluated, copyResources := copyResourcesTask.value, // note that we use the same runner and mainClass as plain run - bgRunMain := bgRunMainTask(exportedProductJars, - fullClasspathAsJars, - bgCopyClasspath in bgRunMain, - runner in run).evaluated, - bgRun := bgRunTask(exportedProductJars, - fullClasspathAsJars, - mainClass in run, - bgCopyClasspath in bgRun, - runner in run).evaluated + mainBgRunMainTaskForConfig(This), + mainBgRunTaskForConfig(This) ) ++ inTask(run)(runnerSettings) private[this] lazy val configGlobal = globalDefaults( @@ -1119,10 +1112,12 @@ object Defaults extends BuildCommon { toClean } - def bgRunMainTask(products: Initialize[Task[Classpath]], - classpath: Initialize[Task[Classpath]], - copyClasspath: Initialize[Boolean], - scalaRun: Initialize[Task[ScalaRun]]): Initialize[InputTask[JobHandle]] = { + def bgRunMainTask( + products: Initialize[Task[Classpath]], + classpath: Initialize[Task[Classpath]], + copyClasspath: Initialize[Boolean], + scalaRun: Initialize[Task[ScalaRun]] + ): Initialize[InputTask[JobHandle]] = { val parser = Defaults.loadForParser(discoveredMainClasses)((s, names) => Defaults.runMainParser(s, names getOrElse Nil)) Def.inputTask { @@ -1137,11 +1132,14 @@ object Defaults extends BuildCommon { } } } - def bgRunTask(products: Initialize[Task[Classpath]], - classpath: Initialize[Task[Classpath]], - mainClassTask: Initialize[Task[Option[String]]], - copyClasspath: Initialize[Boolean], - scalaRun: Initialize[Task[ScalaRun]]): Initialize[InputTask[JobHandle]] = { + + def bgRunTask( + products: Initialize[Task[Classpath]], + classpath: Initialize[Task[Classpath]], + mainClassTask: Initialize[Task[Option[String]]], + copyClasspath: Initialize[Boolean], + scalaRun: Initialize[Task[ScalaRun]] + ): Initialize[InputTask[JobHandle]] = { import Def.parserToInput val parser = Def.spaceDelimited() Def.inputTask { @@ -1156,6 +1154,7 @@ object Defaults extends BuildCommon { } } } + // runMain calls bgRunMain in the background and waits for the result. def foregroundRunMainTask: Initialize[InputTask[Unit]] = Def.inputTask { @@ -1163,6 +1162,7 @@ object Defaults extends BuildCommon { val service = bgJobService.value service.waitForTry(handle).get } + // run calls bgRun in the background and waits for the result. def foregroundRunTask: Initialize[InputTask[Unit]] = Def.inputTask { @@ -1170,8 +1170,11 @@ object Defaults extends BuildCommon { val service = bgJobService.value service.waitForTry(handle).get } - def runMainTask(classpath: Initialize[Task[Classpath]], - scalaRun: Initialize[Task[ScalaRun]]): Initialize[InputTask[Unit]] = { + + def runMainTask( + classpath: Initialize[Task[Classpath]], + scalaRun: Initialize[Task[ScalaRun]] + ): Initialize[InputTask[Unit]] = { val parser = loadForParser(discoveredMainClasses)((s, names) => runMainParser(s, names getOrElse Nil)) Def.inputTask { @@ -1179,9 +1182,12 @@ object Defaults extends BuildCommon { scalaRun.value.run(mainClass, data(classpath.value), args, streams.value.log).get } } - def runTask(classpath: Initialize[Task[Classpath]], - mainClassTask: Initialize[Task[Option[String]]], - scalaRun: Initialize[Task[ScalaRun]]): Initialize[InputTask[Unit]] = { + + def runTask( + classpath: Initialize[Task[Classpath]], + mainClassTask: Initialize[Task[Option[String]]], + scalaRun: Initialize[Task[ScalaRun]] + ): Initialize[InputTask[Unit]] = { import Def.parserToInput val parser = Def.spaceDelimited() Def.inputTask { @@ -1189,7 +1195,9 @@ object Defaults extends BuildCommon { scalaRun.value.run(mainClass, data(classpath.value), parser.parsed, streams.value.log).get } } + def runnerTask: Setting[Task[ScalaRun]] = runner := runnerInit.value + def runnerInit: Initialize[Task[ScalaRun]] = Def.task { val tmp = taskTemporaryDirectory.value val resolvedScope = resolvedScoped.value.scope @@ -1217,7 +1225,8 @@ object Defaults extends BuildCommon { } private def foreachJobTask( - f: (BackgroundJobService, JobHandle) => Unit): Initialize[InputTask[Unit]] = { + f: (BackgroundJobService, JobHandle) => Unit + ): Initialize[InputTask[Unit]] = { val parser: Initialize[State => Parser[Seq[JobHandle]]] = Def.setting { (s: State) => val extracted = Project.extract(s) val service = extracted.get(bgJobService) @@ -1232,6 +1241,7 @@ object Defaults extends BuildCommon { } } } + def psTask: Initialize[Task[Seq[JobHandle]]] = Def.task { val xs = bgList.value @@ -1241,9 +1251,11 @@ object Defaults extends BuildCommon { } xs } + def bgStopTask: Initialize[InputTask[Unit]] = foreachJobTask { (manager, handle) => manager.stop(handle) } + def bgWaitForTask: Initialize[InputTask[Unit]] = foreachJobTask { (manager, handle) => manager.waitFor(handle) } @@ -1294,17 +1306,25 @@ object Defaults extends BuildCommon { } )) - def mainBgRunTask = - bgRun := bgRunTask(exportedProductJars, - fullClasspathAsJars in Runtime, - mainClass in run, - bgCopyClasspath in bgRun, - runner in run).evaluated - def mainBgRunMainTask = - bgRunMain := bgRunMainTask(exportedProductJars, - fullClasspathAsJars in Runtime, - bgCopyClasspath in bgRunMain, - runner in run).evaluated + def mainBgRunTask = mainBgRunTaskForConfig(Select(Runtime)) + def mainBgRunMainTask = mainBgRunMainTaskForConfig(Select(Runtime)) + + private[this] def mainBgRunTaskForConfig(c: ScopeAxis[ConfigKey]) = + bgRun := bgRunTask( + exportedProductJars, + fullClasspathAsJars in (This, c, This), + mainClass in run, + bgCopyClasspath in bgRun, + runner in run + ).evaluated + + private[this] def mainBgRunMainTaskForConfig(c: ScopeAxis[ConfigKey]) = + bgRunMain := bgRunMainTask( + exportedProductJars, + fullClasspathAsJars in (This, c, This), + bgCopyClasspath in bgRunMain, + runner in run + ).evaluated def discoverMainClasses(analysis: CompileAnalysis): Seq[String] = analysis match { case analysis: Analysis => @@ -1316,6 +1336,7 @@ object Defaults extends BuildCommon { ConsoleProject(state.value, (initialCommands in consoleProject).value)(streams.value.log) println() } + def consoleTask: Initialize[Task[Unit]] = consoleTask(fullClasspath, console) def consoleQuickTask = consoleTask(externalDependencyClasspath, consoleQuick) def consoleTask(classpath: TaskKey[Classpath], task: TaskKey[_]): Initialize[Task[Unit]] = @@ -1340,6 +1361,7 @@ object Defaults extends BuildCommon { private[this] def exported(w: PrintWriter, command: String): Seq[String] => Unit = args => w.println((command +: args).mkString(" ")) + private[this] def exported(s: TaskStreams, command: String): Seq[String] => Unit = args => { val w = s.text(ExportStream) try exported(w, command) @@ -1530,8 +1552,10 @@ object Defaults extends BuildCommon { lazy val runnerSettings: Seq[Setting[_]] = Seq(runnerTask, forkOptions := forkOptionsTask.value) lazy val baseTasks: Seq[Setting[_]] = projectTasks ++ packageBase - lazy val configSettings - : Seq[Setting[_]] = Classpaths.configSettings ++ configTasks ++ configPaths ++ packageConfig ++ Classpaths.compilerPluginConfig ++ deprecationSettings + + lazy val configSettings: Seq[Setting[_]] = + Classpaths.configSettings ++ configTasks ++ configPaths ++ packageConfig ++ + Classpaths.compilerPluginConfig ++ deprecationSettings lazy val compileSettings: Seq[Setting[_]] = configSettings ++ @@ -1541,8 +1565,8 @@ object Defaults extends BuildCommon { lazy val testSettings: Seq[Setting[_]] = configSettings ++ testTasks lazy val itSettings: Seq[Setting[_]] = inConfig(IntegrationTest)(testSettings) - lazy val defaultConfigs: Seq[Setting[_]] = inConfig(Compile)(compileSettings) ++ inConfig(Test)( - testSettings) ++ inConfig(Runtime)(Classpaths.configSettings) + lazy val defaultConfigs: Seq[Setting[_]] = inConfig(Compile)(compileSettings) ++ + inConfig(Test)(testSettings) ++ inConfig(Runtime)(Classpaths.configSettings) // These are project level settings that MUST be on every project. lazy val coreDefaultSettings: Seq[Setting[_]] = From 103e40882f104ae5fc40991d15e6e9ae4e817650 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 12 Sep 2017 14:30:24 +0100 Subject: [PATCH 2/2] Fix run's support of directories in the classpath Fixes #3504 --- .../DefaultBackgroundJobService.scala | 52 ++++++++++++++----- sbt/src/sbt-test/run/classpath/Main.scala | 9 ++++ sbt/src/sbt-test/run/classpath/build.sbt | 1 + sbt/src/sbt-test/run/classpath/conf/a.txt | 1 + sbt/src/sbt-test/run/classpath/test | 1 + 5 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 sbt/src/sbt-test/run/classpath/Main.scala create mode 100644 sbt/src/sbt-test/run/classpath/build.sbt create mode 100644 sbt/src/sbt-test/run/classpath/conf/a.txt create mode 100644 sbt/src/sbt-test/run/classpath/test diff --git a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala index ea08836a9..308ce2047 100644 --- a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala +++ b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala @@ -2,15 +2,17 @@ package sbt package internal import java.util.concurrent.atomic.AtomicLong -import java.io.Closeable -import Def.{ ScopedKey, Setting, Classpath } +import java.io.{ Closeable, File, FileInputStream, IOException } +import java.nio.file.attribute.BasicFileAttributes +import java.nio.file.{ FileVisitResult, Files, Path, SimpleFileVisitor } +import java.security.{ DigestInputStream, MessageDigest } +import Def.{ Classpath, ScopedKey, Setting } import scala.concurrent.ExecutionContext import scala.util.Try import Scope.GlobalScope -import java.io.File -import sbt.io.{ IO, Hash } +import sbt.io.{ Hash, IO } import sbt.io.syntax._ -import sbt.util.{ Logger, LogExchange } +import sbt.util.{ LogExchange, Logger } import sbt.internal.util.{ Attributed, ManagedLogger } /** @@ -167,20 +169,25 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe override def toString(): String = s"BackgroundJobService(jobs=${jobs.map(_.id).mkString})" /** - * Copies products to the workind directory, and the rest to the serviceTempDir of this service, + * Copies products to the working directory, and the rest to the serviceTempDir of this service, * both wrapped in SHA-1 hash of the file contents. - * This is intended to mimize the file copying and accumulation of the unused JAR file. + * This is intended to minimize the file copying and accumulation of the unused JAR file. * Since working directory is wiped out when the background job ends, the product JAR is deleted too. * Meanwhile, the rest of the dependencies are cached for the duration of this service. */ - override def copyClasspath(products: Classpath, - full: Classpath, - workingDirectory: File): Classpath = { + override def copyClasspath( + products: Classpath, + full: Classpath, + workingDirectory: File + ): Classpath = { def syncTo(dir: File)(source0: Attributed[File]): Attributed[File] = { val source = source0.data - val hash8 = Hash.toHex(Hash(source)).take(8) + val hash8 = Hash.toHex(hash(source)).take(8) val dest = dir / hash8 / source.getName - if (!dest.exists) { IO.copyFile(source, dest) } + if (!dest.exists) { + if (source.isDirectory) IO.copyDirectory(source, dest) + else IO.copyFile(source, dest) + } Attributed.blank(dest) } val xs = (products.toVector map { syncTo(workingDirectory / "target") }) ++ @@ -188,6 +195,27 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe Thread.sleep(100) xs } + + /** An alternative to sbt.io.Hash that handles java.io.File being a directory. */ + private def hash(f: File) = { + val digest = MessageDigest.getInstance("SHA") + val buffer = new Array[Byte](8192) + Files.walkFileTree( + f.toPath, + new SimpleFileVisitor[Path]() { + override def visitFile(file: Path, attrs: BasicFileAttributes) = { + val dis = new DigestInputStream(new FileInputStream(file.toFile), digest) + try { + while (dis.read(buffer) >= 0) () + FileVisitResult.CONTINUE + } catch { + case _: IOException => FileVisitResult.TERMINATE + } finally dis.close() + } + } + ) + digest.digest + } } private[sbt] object BackgroundThreadPool { diff --git a/sbt/src/sbt-test/run/classpath/Main.scala b/sbt/src/sbt-test/run/classpath/Main.scala new file mode 100644 index 000000000..196834b7f --- /dev/null +++ b/sbt/src/sbt-test/run/classpath/Main.scala @@ -0,0 +1,9 @@ +package t + +import java.nio._, charset._, file._ + +object Main { + def main(args: Array[String]): Unit = { + println(new String(Files.readAllBytes(Paths.get(getClass().getResource("/a.txt").toURI())))) + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/run/classpath/build.sbt b/sbt/src/sbt-test/run/classpath/build.sbt new file mode 100644 index 000000000..57556d8bb --- /dev/null +++ b/sbt/src/sbt-test/run/classpath/build.sbt @@ -0,0 +1 @@ +externalDependencyClasspath in Runtime += file("conf") \ No newline at end of file diff --git a/sbt/src/sbt-test/run/classpath/conf/a.txt b/sbt/src/sbt-test/run/classpath/conf/a.txt new file mode 100644 index 000000000..257cc5642 --- /dev/null +++ b/sbt/src/sbt-test/run/classpath/conf/a.txt @@ -0,0 +1 @@ +foo diff --git a/sbt/src/sbt-test/run/classpath/test b/sbt/src/sbt-test/run/classpath/test new file mode 100644 index 000000000..62ea636c1 --- /dev/null +++ b/sbt/src/sbt-test/run/classpath/test @@ -0,0 +1 @@ +> run