mirror of https://github.com/sbt/sbt.git
commit
df24c0d8aa
|
|
@ -9,7 +9,7 @@ package sbt
|
|||
|
||||
import java.io.{ File, IOException }
|
||||
import java.net.URI
|
||||
import java.util.concurrent.{ Executors, ForkJoinPool }
|
||||
import java.util.concurrent.ForkJoinPool
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.{ Locale, Properties }
|
||||
|
||||
|
|
|
|||
|
|
@ -177,21 +177,14 @@ object MainLoop {
|
|||
}
|
||||
|
||||
/** This is the main function State transfer function of the sbt command processing. */
|
||||
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 = {
|
||||
def processCommand(exec: Exec, state: State): State = {
|
||||
val channelName = exec.source map (_.channelName)
|
||||
StandardMain.exchange publishEventMessage
|
||||
ExecStatusEvent("Processing", channelName, exec.execId, Vector())
|
||||
|
||||
try {
|
||||
def process(): State = {
|
||||
val newState = runCommand()
|
||||
val newState = Command.process(exec.commandLine, state)
|
||||
val doneEvent = ExecStatusEvent(
|
||||
"Done",
|
||||
channelName,
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
inputs.foreach(i => repository.register(i.glob))
|
||||
val watchSettings = new WatchSettings(scopedKey)
|
||||
new Config(
|
||||
scopedKey,
|
||||
scopedKey.show,
|
||||
() => dynamicInputs.toSeq.sorted,
|
||||
watchSettings
|
||||
)
|
||||
|
|
@ -212,7 +212,7 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
val onFail = Command.command(failureCommandName)(identity)
|
||||
// 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.
|
||||
val s = (FailureWall :: state).copy(
|
||||
val s = state.copy(
|
||||
onFailure = Some(Exec(failureCommandName, None)),
|
||||
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.
|
||||
*/
|
||||
def makeTask(cmd: String): (String, State, () => Boolean) = {
|
||||
val newState = s.put(DynamicInputs, mutable.Set.empty[DynamicInput])
|
||||
val task = Parser
|
||||
.parse(cmd, Command.combine(newState.definedCommands)(newState))
|
||||
.getOrElse(
|
||||
throw new IllegalStateException(
|
||||
"No longer able to parse command after transforming state"
|
||||
)
|
||||
)
|
||||
val newState = s
|
||||
.put(DynamicInputs, mutable.Set.empty[DynamicInput])
|
||||
.copy(remainingCommands = Exec(cmd, None, None) :: Exec(FailureWall, None, None) :: Nil)
|
||||
(
|
||||
cmd,
|
||||
newState,
|
||||
() => {
|
||||
MainLoop
|
||||
.processCommand(Exec(cmd, None), newState, task)
|
||||
.remainingCommands
|
||||
.forall(_.commandLine != failureCommandName)
|
||||
@tailrec
|
||||
def impl(s: State): Boolean = {
|
||||
s.remainingCommands match {
|
||||
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
|
||||
// () => 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
|
||||
// the command cannot be converted to a task.
|
||||
val (invalid, valid) =
|
||||
|
|
@ -378,6 +385,9 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
} 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[_]] = {
|
||||
// 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.
|
||||
|
|
@ -391,9 +401,13 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
val aliases = BasicCommands.allAliases(state)
|
||||
aliases.collectFirst { case (`command`, aliased) => aliased } match {
|
||||
case Some(aliased) => impl(aliased)
|
||||
case _ =>
|
||||
val msg = s"Error attempting to extract scope from $command: $e."
|
||||
throw new IllegalStateException(msg)
|
||||
case None =>
|
||||
Parser.parse(command, state.combinedParser) match {
|
||||
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[_]]
|
||||
}
|
||||
|
|
@ -619,7 +633,7 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
configs.map { config =>
|
||||
// Create a logger with a scoped key prefix so that we can tell from which task there
|
||||
// were inputs that matched the event path.
|
||||
val configLogger = logger.withPrefix(config.key.show)
|
||||
val configLogger = logger.withPrefix(config.command)
|
||||
observers.addObserver { e =>
|
||||
if (config.inputs().exists(_.glob.matches(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
|
||||
* 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 watchSettings the [[WatchSettings]] instance for the task
|
||||
*/
|
||||
private final class Config private[internal] (
|
||||
val key: ScopedKey[_],
|
||||
val command: String,
|
||||
val inputs: () => Seq[DynamicInput],
|
||||
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.attribute.FileTime
|
||||
|
||||
import sbt.nio.Keys._
|
||||
import sbt.nio._
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import StandardCopyOption.{ REPLACE_EXISTING => replace }
|
||||
|
||||
watchTriggeredMessage := { (i, path: Path, c) =>
|
||||
val prev = watchTriggeredMessage.value
|
||||
|
|
@ -16,14 +18,15 @@ watchOnIteration := { i: Int =>
|
|||
val src =
|
||||
base.resolve("src").resolve("main").resolve("scala").resolve("sbt").resolve("test")
|
||||
val changes = base.resolve("changes")
|
||||
Files.copy(changes.resolve("C.scala"), src.resolve("C.scala"), replace)
|
||||
if (i < 5) {
|
||||
def copy(fileName: String): Unit = {
|
||||
val content =
|
||||
new String(Files.readAllBytes(changes.resolve("A.scala"))) + "\n" + ("//" * i)
|
||||
Files.write(src.resolve("A.scala"), content.getBytes)
|
||||
} else {
|
||||
Files.copy(changes.resolve("B.scala"), src.resolve("B.scala"), replace)
|
||||
new String(Files.readAllBytes(changes.resolve(fileName))) + "\n" + ("//" * i)
|
||||
Files.write(src.resolve(fileName), content.getBytes)
|
||||
}
|
||||
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...")
|
||||
Watch.Ignore
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,7 +232,9 @@ final class ScriptedTests(
|
|||
case "source-dependencies/linearization" => LauncherBased // sbt/Package$
|
||||
case "source-dependencies/named" => 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" =>
|
||||
LauncherBased // the sbt metabuild classpath leaks into the test interface classloader in older versions of sbt
|
||||
case _ => RunFromSourceBased
|
||||
|
|
|
|||
Loading…
Reference in New Issue