Merge pull request #3517 from dwijnand/run-cp-dirs

Fix run's support of directories in the classpath
This commit is contained in:
Dale Wijnand 2017-09-13 16:07:36 +01:00 committed by GitHub
commit da40288d6f
6 changed files with 115 additions and 51 deletions

View File

@ -500,15 +500,8 @@ object Defaults extends BuildCommon {
run := foregroundRunTask.evaluated, run := foregroundRunTask.evaluated,
copyResources := copyResourcesTask.value, copyResources := copyResourcesTask.value,
// note that we use the same runner and mainClass as plain run // note that we use the same runner and mainClass as plain run
bgRunMain := bgRunMainTask(exportedProductJars, mainBgRunMainTaskForConfig(This),
fullClasspathAsJars, mainBgRunTaskForConfig(This)
bgCopyClasspath in bgRunMain,
runner in run).evaluated,
bgRun := bgRunTask(exportedProductJars,
fullClasspathAsJars,
mainClass in run,
bgCopyClasspath in bgRun,
runner in run).evaluated
) ++ inTask(run)(runnerSettings) ) ++ inTask(run)(runnerSettings)
private[this] lazy val configGlobal = globalDefaults( private[this] lazy val configGlobal = globalDefaults(
@ -1119,10 +1112,12 @@ object Defaults extends BuildCommon {
toClean toClean
} }
def bgRunMainTask(products: Initialize[Task[Classpath]], def bgRunMainTask(
classpath: Initialize[Task[Classpath]], products: Initialize[Task[Classpath]],
copyClasspath: Initialize[Boolean], classpath: Initialize[Task[Classpath]],
scalaRun: Initialize[Task[ScalaRun]]): Initialize[InputTask[JobHandle]] = { copyClasspath: Initialize[Boolean],
scalaRun: Initialize[Task[ScalaRun]]
): Initialize[InputTask[JobHandle]] = {
val parser = Defaults.loadForParser(discoveredMainClasses)((s, names) => val parser = Defaults.loadForParser(discoveredMainClasses)((s, names) =>
Defaults.runMainParser(s, names getOrElse Nil)) Defaults.runMainParser(s, names getOrElse Nil))
Def.inputTask { Def.inputTask {
@ -1137,11 +1132,14 @@ object Defaults extends BuildCommon {
} }
} }
} }
def bgRunTask(products: Initialize[Task[Classpath]],
classpath: Initialize[Task[Classpath]], def bgRunTask(
mainClassTask: Initialize[Task[Option[String]]], products: Initialize[Task[Classpath]],
copyClasspath: Initialize[Boolean], classpath: Initialize[Task[Classpath]],
scalaRun: Initialize[Task[ScalaRun]]): Initialize[InputTask[JobHandle]] = { mainClassTask: Initialize[Task[Option[String]]],
copyClasspath: Initialize[Boolean],
scalaRun: Initialize[Task[ScalaRun]]
): Initialize[InputTask[JobHandle]] = {
import Def.parserToInput import Def.parserToInput
val parser = Def.spaceDelimited() val parser = Def.spaceDelimited()
Def.inputTask { Def.inputTask {
@ -1156,6 +1154,7 @@ object Defaults extends BuildCommon {
} }
} }
} }
// runMain calls bgRunMain in the background and waits for the result. // runMain calls bgRunMain in the background and waits for the result.
def foregroundRunMainTask: Initialize[InputTask[Unit]] = def foregroundRunMainTask: Initialize[InputTask[Unit]] =
Def.inputTask { Def.inputTask {
@ -1163,6 +1162,7 @@ object Defaults extends BuildCommon {
val service = bgJobService.value val service = bgJobService.value
service.waitForTry(handle).get service.waitForTry(handle).get
} }
// run calls bgRun in the background and waits for the result. // run calls bgRun in the background and waits for the result.
def foregroundRunTask: Initialize[InputTask[Unit]] = def foregroundRunTask: Initialize[InputTask[Unit]] =
Def.inputTask { Def.inputTask {
@ -1170,8 +1170,11 @@ object Defaults extends BuildCommon {
val service = bgJobService.value val service = bgJobService.value
service.waitForTry(handle).get 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 = val parser =
loadForParser(discoveredMainClasses)((s, names) => runMainParser(s, names getOrElse Nil)) loadForParser(discoveredMainClasses)((s, names) => runMainParser(s, names getOrElse Nil))
Def.inputTask { Def.inputTask {
@ -1179,9 +1182,12 @@ object Defaults extends BuildCommon {
scalaRun.value.run(mainClass, data(classpath.value), args, streams.value.log).get scalaRun.value.run(mainClass, data(classpath.value), args, streams.value.log).get
} }
} }
def runTask(classpath: Initialize[Task[Classpath]],
mainClassTask: Initialize[Task[Option[String]]], def runTask(
scalaRun: Initialize[Task[ScalaRun]]): Initialize[InputTask[Unit]] = { classpath: Initialize[Task[Classpath]],
mainClassTask: Initialize[Task[Option[String]]],
scalaRun: Initialize[Task[ScalaRun]]
): Initialize[InputTask[Unit]] = {
import Def.parserToInput import Def.parserToInput
val parser = Def.spaceDelimited() val parser = Def.spaceDelimited()
Def.inputTask { Def.inputTask {
@ -1189,7 +1195,9 @@ object Defaults extends BuildCommon {
scalaRun.value.run(mainClass, data(classpath.value), parser.parsed, streams.value.log).get scalaRun.value.run(mainClass, data(classpath.value), parser.parsed, streams.value.log).get
} }
} }
def runnerTask: Setting[Task[ScalaRun]] = runner := runnerInit.value def runnerTask: Setting[Task[ScalaRun]] = runner := runnerInit.value
def runnerInit: Initialize[Task[ScalaRun]] = Def.task { def runnerInit: Initialize[Task[ScalaRun]] = Def.task {
val tmp = taskTemporaryDirectory.value val tmp = taskTemporaryDirectory.value
val resolvedScope = resolvedScoped.value.scope val resolvedScope = resolvedScoped.value.scope
@ -1217,7 +1225,8 @@ object Defaults extends BuildCommon {
} }
private def foreachJobTask( 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 parser: Initialize[State => Parser[Seq[JobHandle]]] = Def.setting { (s: State) =>
val extracted = Project.extract(s) val extracted = Project.extract(s)
val service = extracted.get(bgJobService) val service = extracted.get(bgJobService)
@ -1232,6 +1241,7 @@ object Defaults extends BuildCommon {
} }
} }
} }
def psTask: Initialize[Task[Seq[JobHandle]]] = def psTask: Initialize[Task[Seq[JobHandle]]] =
Def.task { Def.task {
val xs = bgList.value val xs = bgList.value
@ -1241,9 +1251,11 @@ object Defaults extends BuildCommon {
} }
xs xs
} }
def bgStopTask: Initialize[InputTask[Unit]] = foreachJobTask { (manager, handle) => def bgStopTask: Initialize[InputTask[Unit]] = foreachJobTask { (manager, handle) =>
manager.stop(handle) manager.stop(handle)
} }
def bgWaitForTask: Initialize[InputTask[Unit]] = foreachJobTask { (manager, handle) => def bgWaitForTask: Initialize[InputTask[Unit]] = foreachJobTask { (manager, handle) =>
manager.waitFor(handle) manager.waitFor(handle)
} }
@ -1294,17 +1306,25 @@ object Defaults extends BuildCommon {
} }
)) ))
def mainBgRunTask = def mainBgRunTask = mainBgRunTaskForConfig(Select(Runtime))
bgRun := bgRunTask(exportedProductJars, def mainBgRunMainTask = mainBgRunMainTaskForConfig(Select(Runtime))
fullClasspathAsJars in Runtime,
mainClass in run, private[this] def mainBgRunTaskForConfig(c: ScopeAxis[ConfigKey]) =
bgCopyClasspath in bgRun, bgRun := bgRunTask(
runner in run).evaluated exportedProductJars,
def mainBgRunMainTask = fullClasspathAsJars in (This, c, This),
bgRunMain := bgRunMainTask(exportedProductJars, mainClass in run,
fullClasspathAsJars in Runtime, bgCopyClasspath in bgRun,
bgCopyClasspath in bgRunMain, runner in run
runner in run).evaluated ).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 { def discoverMainClasses(analysis: CompileAnalysis): Seq[String] = analysis match {
case analysis: Analysis => case analysis: Analysis =>
@ -1316,6 +1336,7 @@ object Defaults extends BuildCommon {
ConsoleProject(state.value, (initialCommands in consoleProject).value)(streams.value.log) ConsoleProject(state.value, (initialCommands in consoleProject).value)(streams.value.log)
println() println()
} }
def consoleTask: Initialize[Task[Unit]] = consoleTask(fullClasspath, console) def consoleTask: Initialize[Task[Unit]] = consoleTask(fullClasspath, console)
def consoleQuickTask = consoleTask(externalDependencyClasspath, consoleQuick) def consoleQuickTask = consoleTask(externalDependencyClasspath, consoleQuick)
def consoleTask(classpath: TaskKey[Classpath], task: TaskKey[_]): Initialize[Task[Unit]] = 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 = private[this] def exported(w: PrintWriter, command: String): Seq[String] => Unit =
args => w.println((command +: args).mkString(" ")) args => w.println((command +: args).mkString(" "))
private[this] def exported(s: TaskStreams, command: String): Seq[String] => Unit = args => { private[this] def exported(s: TaskStreams, command: String): Seq[String] => Unit = args => {
val w = s.text(ExportStream) val w = s.text(ExportStream)
try exported(w, command) try exported(w, command)
@ -1530,8 +1552,10 @@ object Defaults extends BuildCommon {
lazy val runnerSettings: Seq[Setting[_]] = Seq(runnerTask, forkOptions := forkOptionsTask.value) lazy val runnerSettings: Seq[Setting[_]] = Seq(runnerTask, forkOptions := forkOptionsTask.value)
lazy val baseTasks: Seq[Setting[_]] = projectTasks ++ packageBase 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[_]] = lazy val compileSettings: Seq[Setting[_]] =
configSettings ++ configSettings ++
@ -1541,8 +1565,8 @@ object Defaults extends BuildCommon {
lazy val testSettings: Seq[Setting[_]] = configSettings ++ testTasks lazy val testSettings: Seq[Setting[_]] = configSettings ++ testTasks
lazy val itSettings: Seq[Setting[_]] = inConfig(IntegrationTest)(testSettings) lazy val itSettings: Seq[Setting[_]] = inConfig(IntegrationTest)(testSettings)
lazy val defaultConfigs: Seq[Setting[_]] = inConfig(Compile)(compileSettings) ++ inConfig(Test)( lazy val defaultConfigs: Seq[Setting[_]] = inConfig(Compile)(compileSettings) ++
testSettings) ++ inConfig(Runtime)(Classpaths.configSettings) inConfig(Test)(testSettings) ++ inConfig(Runtime)(Classpaths.configSettings)
// These are project level settings that MUST be on every project. // These are project level settings that MUST be on every project.
lazy val coreDefaultSettings: Seq[Setting[_]] = lazy val coreDefaultSettings: Seq[Setting[_]] =

View File

@ -2,15 +2,17 @@ package sbt
package internal package internal
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
import java.io.Closeable import java.io.{ Closeable, File, FileInputStream, IOException }
import Def.{ ScopedKey, Setting, Classpath } 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.concurrent.ExecutionContext
import scala.util.Try import scala.util.Try
import Scope.GlobalScope import Scope.GlobalScope
import java.io.File import sbt.io.{ Hash, IO }
import sbt.io.{ IO, Hash }
import sbt.io.syntax._ import sbt.io.syntax._
import sbt.util.{ Logger, LogExchange } import sbt.util.{ LogExchange, Logger }
import sbt.internal.util.{ Attributed, ManagedLogger } 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})" 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. * 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. * 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. * Meanwhile, the rest of the dependencies are cached for the duration of this service.
*/ */
override def copyClasspath(products: Classpath, override def copyClasspath(
full: Classpath, products: Classpath,
workingDirectory: File): Classpath = { full: Classpath,
workingDirectory: File
): Classpath = {
def syncTo(dir: File)(source0: Attributed[File]): Attributed[File] = { def syncTo(dir: File)(source0: Attributed[File]): Attributed[File] = {
val source = source0.data 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 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) Attributed.blank(dest)
} }
val xs = (products.toVector map { syncTo(workingDirectory / "target") }) ++ val xs = (products.toVector map { syncTo(workingDirectory / "target") }) ++
@ -188,6 +195,27 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe
Thread.sleep(100) Thread.sleep(100)
xs 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 { private[sbt] object BackgroundThreadPool {

View File

@ -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()))))
}
}

View File

@ -0,0 +1 @@
externalDependencyClasspath in Runtime += file("conf")

View File

@ -0,0 +1 @@
foo

View File

@ -0,0 +1 @@
> run