mirror of https://github.com/sbt/sbt.git
commit
df24c0d8aa
|
|
@ -9,7 +9,7 @@ package sbt
|
||||||
|
|
||||||
import java.io.{ File, IOException }
|
import java.io.{ File, IOException }
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.util.concurrent.{ Executors, ForkJoinPool }
|
import java.util.concurrent.ForkJoinPool
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import java.util.{ Locale, Properties }
|
import java.util.{ Locale, Properties }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,21 +177,14 @@ object MainLoop {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This is the main function State transfer function of the sbt command processing. */
|
/** This is the main function State transfer function of the sbt command processing. */
|
||||||
def processCommand(exec: Exec, state: State): State =
|
def processCommand(exec: Exec, state: State): State = {
|
||||||
processCommand(exec, state, () => Command.process(exec.commandLine, state))
|
|
||||||
|
|
||||||
private[sbt] def processCommand(
|
|
||||||
exec: Exec,
|
|
||||||
state: State,
|
|
||||||
runCommand: () => State
|
|
||||||
): State = {
|
|
||||||
val channelName = exec.source map (_.channelName)
|
val channelName = exec.source map (_.channelName)
|
||||||
StandardMain.exchange publishEventMessage
|
StandardMain.exchange publishEventMessage
|
||||||
ExecStatusEvent("Processing", channelName, exec.execId, Vector())
|
ExecStatusEvent("Processing", channelName, exec.execId, Vector())
|
||||||
|
|
||||||
try {
|
try {
|
||||||
def process(): State = {
|
def process(): State = {
|
||||||
val newState = runCommand()
|
val newState = Command.process(exec.commandLine, state)
|
||||||
val doneEvent = ExecStatusEvent(
|
val doneEvent = ExecStatusEvent(
|
||||||
"Done",
|
"Done",
|
||||||
channelName,
|
channelName,
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
||||||
inputs.foreach(i => repository.register(i.glob))
|
inputs.foreach(i => repository.register(i.glob))
|
||||||
val watchSettings = new WatchSettings(scopedKey)
|
val watchSettings = new WatchSettings(scopedKey)
|
||||||
new Config(
|
new Config(
|
||||||
scopedKey,
|
scopedKey.show,
|
||||||
() => dynamicInputs.toSeq.sorted,
|
() => dynamicInputs.toSeq.sorted,
|
||||||
watchSettings
|
watchSettings
|
||||||
)
|
)
|
||||||
|
|
@ -212,7 +212,7 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
||||||
val onFail = Command.command(failureCommandName)(identity)
|
val onFail = Command.command(failureCommandName)(identity)
|
||||||
// This adds the "SbtContinuousWatchOnFail" onFailure handler which allows us to determine
|
// This adds the "SbtContinuousWatchOnFail" onFailure handler which allows us to determine
|
||||||
// whether or not the last task successfully ran. It is used in the makeTask method below.
|
// whether or not the last task successfully ran. It is used in the makeTask method below.
|
||||||
val s = (FailureWall :: state).copy(
|
val s = state.copy(
|
||||||
onFailure = Some(Exec(failureCommandName, None)),
|
onFailure = Some(Exec(failureCommandName, None)),
|
||||||
definedCommands = state.definedCommands :+ onFail
|
definedCommands = state.definedCommands :+ onFail
|
||||||
)
|
)
|
||||||
|
|
@ -226,22 +226,29 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
||||||
* if they are not visible in the input graph due to the use of Def.taskDyn.
|
* if they are not visible in the input graph due to the use of Def.taskDyn.
|
||||||
*/
|
*/
|
||||||
def makeTask(cmd: String): (String, State, () => Boolean) = {
|
def makeTask(cmd: String): (String, State, () => Boolean) = {
|
||||||
val newState = s.put(DynamicInputs, mutable.Set.empty[DynamicInput])
|
val newState = s
|
||||||
val task = Parser
|
.put(DynamicInputs, mutable.Set.empty[DynamicInput])
|
||||||
.parse(cmd, Command.combine(newState.definedCommands)(newState))
|
.copy(remainingCommands = Exec(cmd, None, None) :: Exec(FailureWall, None, None) :: Nil)
|
||||||
.getOrElse(
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"No longer able to parse command after transforming state"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(
|
(
|
||||||
cmd,
|
cmd,
|
||||||
newState,
|
newState,
|
||||||
() => {
|
() => {
|
||||||
MainLoop
|
@tailrec
|
||||||
.processCommand(Exec(cmd, None), newState, task)
|
def impl(s: State): Boolean = {
|
||||||
.remainingCommands
|
s.remainingCommands match {
|
||||||
.forall(_.commandLine != failureCommandName)
|
case exec :: rest =>
|
||||||
|
val updatedState = MainLoop.processCommand(exec, s.copy(remainingCommands = rest))
|
||||||
|
val remaining =
|
||||||
|
updatedState.remainingCommands.takeWhile(_.commandLine != FailureWall)
|
||||||
|
remaining match {
|
||||||
|
case Nil =>
|
||||||
|
updatedState.remainingCommands.forall(_.commandLine != failureCommandName)
|
||||||
|
case _ => impl(updatedState)
|
||||||
|
}
|
||||||
|
case Nil => true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl(newState)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -256,7 +263,7 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
||||||
|
|
||||||
// Convert the command strings to runnable tasks, which are represented by
|
// Convert the command strings to runnable tasks, which are represented by
|
||||||
// () => Try[Boolean].
|
// () => Try[Boolean].
|
||||||
val taskParser = Command.combine(s.definedCommands)(s)
|
val taskParser = s.combinedParser
|
||||||
// This specified either the task corresponding to a command or the command itself if the
|
// This specified either the task corresponding to a command or the command itself if the
|
||||||
// the command cannot be converted to a task.
|
// the command cannot be converted to a task.
|
||||||
val (invalid, valid) =
|
val (invalid, valid) =
|
||||||
|
|
@ -378,6 +385,9 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
||||||
} finally repo.close()
|
} finally repo.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is defined so we can assign a task key to a command to parse the WatchSettings.
|
||||||
|
private[this] val globalWatchSettingKey =
|
||||||
|
taskKey[Unit]("Internal task key. Not actually used.").withRank(KeyRanks.Invisible)
|
||||||
private def parseCommand(command: String, state: State): Seq[ScopedKey[_]] = {
|
private def parseCommand(command: String, state: State): Seq[ScopedKey[_]] = {
|
||||||
// Collect all of the scoped keys that are used to delegate the multi commands. These are
|
// Collect all of the scoped keys that are used to delegate the multi commands. These are
|
||||||
// necessary to extract all of the transitive globs that we need to monitor during watch.
|
// necessary to extract all of the transitive globs that we need to monitor during watch.
|
||||||
|
|
@ -391,9 +401,13 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
||||||
val aliases = BasicCommands.allAliases(state)
|
val aliases = BasicCommands.allAliases(state)
|
||||||
aliases.collectFirst { case (`command`, aliased) => aliased } match {
|
aliases.collectFirst { case (`command`, aliased) => aliased } match {
|
||||||
case Some(aliased) => impl(aliased)
|
case Some(aliased) => impl(aliased)
|
||||||
case _ =>
|
case None =>
|
||||||
val msg = s"Error attempting to extract scope from $command: $e."
|
Parser.parse(command, state.combinedParser) match {
|
||||||
throw new IllegalStateException(msg)
|
case Right(_) => globalWatchSettingKey.scopedKey :: Nil
|
||||||
|
case _ =>
|
||||||
|
val msg = s"Error attempting to extract scope from $command: $e."
|
||||||
|
throw new IllegalStateException(msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case _ => Nil: Seq[ScopedKey[_]]
|
case _ => Nil: Seq[ScopedKey[_]]
|
||||||
}
|
}
|
||||||
|
|
@ -619,7 +633,7 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
||||||
configs.map { config =>
|
configs.map { config =>
|
||||||
// Create a logger with a scoped key prefix so that we can tell from which task there
|
// Create a logger with a scoped key prefix so that we can tell from which task there
|
||||||
// were inputs that matched the event path.
|
// were inputs that matched the event path.
|
||||||
val configLogger = logger.withPrefix(config.key.show)
|
val configLogger = logger.withPrefix(config.command)
|
||||||
observers.addObserver { e =>
|
observers.addObserver { e =>
|
||||||
if (config.inputs().exists(_.glob.matches(e.path))) {
|
if (config.inputs().exists(_.glob.matches(e.path))) {
|
||||||
configLogger.debug(s"Accepted event for ${e.path}")
|
configLogger.debug(s"Accepted event for ${e.path}")
|
||||||
|
|
@ -910,12 +924,12 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
||||||
* Container class for all of the components we need to setup a watch for a particular task or
|
* Container class for all of the components we need to setup a watch for a particular task or
|
||||||
* input task.
|
* input task.
|
||||||
*
|
*
|
||||||
* @param key the [[ScopedKey]] instance for the task we will watch
|
* @param command the name of the command/task to run with each iteration
|
||||||
* @param inputs the transitive task inputs (see [[SettingsGraph]])
|
* @param inputs the transitive task inputs (see [[SettingsGraph]])
|
||||||
* @param watchSettings the [[WatchSettings]] instance for the task
|
* @param watchSettings the [[WatchSettings]] instance for the task
|
||||||
*/
|
*/
|
||||||
private final class Config private[internal] (
|
private final class Config private[internal] (
|
||||||
val key: ScopedKey[_],
|
val command: String,
|
||||||
val inputs: () => Seq[DynamicInput],
|
val inputs: () => Seq[DynamicInput],
|
||||||
val watchSettings: WatchSettings
|
val watchSettings: WatchSettings
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
import java.nio.file.Files
|
||||||
|
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
|
val foo = taskKey[Unit]("foo")
|
||||||
|
foo := {
|
||||||
|
val fooTxt = baseDirectory.value / "foo.txt"
|
||||||
|
val _ = println(s"foo inputs: ${(foo / allInputFiles).value}")
|
||||||
|
IO.write(fooTxt, "foo")
|
||||||
|
println(s"foo wrote to $foo")
|
||||||
|
}
|
||||||
|
foo / fileInputs += baseDirectory.value.toGlob / "foo.txt"
|
||||||
|
|
||||||
|
Global / watchTriggers += baseDirectory.value.toGlob / "bar.txt"
|
||||||
|
|
||||||
|
commands ++= Seq(
|
||||||
|
Command.command("eval-foo") { s =>
|
||||||
|
Project.extract(s).runTask(foo, s)
|
||||||
|
s
|
||||||
|
},
|
||||||
|
Command.command("write-bar") { s =>
|
||||||
|
val bar = Project.extract(s).get(baseDirectory) / "bar.txt"
|
||||||
|
IO.write(bar, "bar")
|
||||||
|
println(s"write-bar wrote to $bar")
|
||||||
|
s
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watchOnFileInputEvent := { (_, _) => sbt.nio.Watch.CancelWatch }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This test ensures that when watching a cross build that commands are run for multiple scala
|
||||||
|
* versions. It also checks that the fileInputs are automatically detected during task evaluation
|
||||||
|
* even though we can't directly inspect the fileInputs of the cross ('+') command.
|
||||||
|
*/
|
||||||
|
val expectFailure = taskKey[Unit]("expect failure")
|
||||||
|
expectFailure := {
|
||||||
|
val main = baseDirectory.value.toPath / "src" / "main"
|
||||||
|
val crossDir = main / (if (scalaVersion.value.startsWith("2.11")) "scala-2.11" else "scala-2.12")
|
||||||
|
(Compile / compile).result.value.toEither match {
|
||||||
|
case Left(_) =>
|
||||||
|
Files.write(crossDir / "Foo.scala", "class Foo".getBytes)
|
||||||
|
throw new IllegalStateException("Compilation failed.")
|
||||||
|
case Right(_) =>
|
||||||
|
if (!Files.walk(main).iterator.asScala.exists(_.getFileName.toString == "first.scala"))
|
||||||
|
Files.write(crossDir / "first.scala", "class first".getBytes)
|
||||||
|
else Files.write(crossDir / "second.scala", "class second".getBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expectFailure / watchOnFileInputEvent := { (_, e) =>
|
||||||
|
if (e.path.getFileName.toString == "second.scala") sbt.nio.Watch.CancelWatch
|
||||||
|
else sbt.nio.Watch.Trigger
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
crossScalaVersions := Seq("2.11.12", "2.12.8")
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
class Foo {
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
class Foo {
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
class Bar
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
> ~ eval-foo
|
||||||
|
|
||||||
|
> ~ write-bar
|
||||||
|
|
||||||
|
> set watchOnFileInputEvent := (expectFailure / watchOnFileInputEvent).value
|
||||||
|
> ~ +expectFailure
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import java.nio.file._
|
import java.nio.file._
|
||||||
|
import java.nio.file.attribute.FileTime
|
||||||
|
|
||||||
import sbt.nio.Keys._
|
import sbt.nio.Keys._
|
||||||
import sbt.nio._
|
import sbt.nio._
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import StandardCopyOption.{ REPLACE_EXISTING => replace }
|
|
||||||
|
|
||||||
watchTriggeredMessage := { (i, path: Path, c) =>
|
watchTriggeredMessage := { (i, path: Path, c) =>
|
||||||
val prev = watchTriggeredMessage.value
|
val prev = watchTriggeredMessage.value
|
||||||
|
|
@ -16,14 +18,15 @@ watchOnIteration := { i: Int =>
|
||||||
val src =
|
val src =
|
||||||
base.resolve("src").resolve("main").resolve("scala").resolve("sbt").resolve("test")
|
base.resolve("src").resolve("main").resolve("scala").resolve("sbt").resolve("test")
|
||||||
val changes = base.resolve("changes")
|
val changes = base.resolve("changes")
|
||||||
Files.copy(changes.resolve("C.scala"), src.resolve("C.scala"), replace)
|
def copy(fileName: String): Unit = {
|
||||||
if (i < 5) {
|
|
||||||
val content =
|
val content =
|
||||||
new String(Files.readAllBytes(changes.resolve("A.scala"))) + "\n" + ("//" * i)
|
new String(Files.readAllBytes(changes.resolve(fileName))) + "\n" + ("//" * i)
|
||||||
Files.write(src.resolve("A.scala"), content.getBytes)
|
Files.write(src.resolve(fileName), content.getBytes)
|
||||||
} else {
|
|
||||||
Files.copy(changes.resolve("B.scala"), src.resolve("B.scala"), replace)
|
|
||||||
}
|
}
|
||||||
|
val c = src.resolve("C.scala")
|
||||||
|
Files.setLastModifiedTime(c, FileTime.fromMillis(Files.getLastModifiedTime(c).toMillis + 1111))
|
||||||
|
if (i < 5) copy("A.scala")
|
||||||
|
else copy("B.scala")
|
||||||
println(s"Waiting for changes...")
|
println(s"Waiting for changes...")
|
||||||
Watch.Ignore
|
Watch.Ignore
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -232,7 +232,9 @@ final class ScriptedTests(
|
||||||
case "source-dependencies/linearization" => LauncherBased // sbt/Package$
|
case "source-dependencies/linearization" => LauncherBased // sbt/Package$
|
||||||
case "source-dependencies/named" => LauncherBased // sbt/Package$
|
case "source-dependencies/named" => LauncherBased // sbt/Package$
|
||||||
case "source-dependencies/specialized" => LauncherBased // sbt/Package$
|
case "source-dependencies/specialized" => LauncherBased // sbt/Package$
|
||||||
case "watch/managed" => LauncherBased // sbt/Package$
|
case "watch/commands" =>
|
||||||
|
LauncherBased // java.lang.ClassNotFoundException: javax.tools.DiagnosticListener when run with java 11 and an old sbt launcher
|
||||||
|
case "watch/managed" => LauncherBased // sbt/Package$
|
||||||
case "tests/test-cross" =>
|
case "tests/test-cross" =>
|
||||||
LauncherBased // the sbt metabuild classpath leaks into the test interface classloader in older versions of sbt
|
LauncherBased // the sbt metabuild classpath leaks into the test interface classloader in older versions of sbt
|
||||||
case _ => RunFromSourceBased
|
case _ => RunFromSourceBased
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue