mirror of https://github.com/sbt/sbt.git
Simplify watch callbacks
While writing documentation for the watch subsystem, I realized that it's awkward to configure watch to clear the screen before task evaluation. To make this easier, I added a setting watchBeforeCommand which is an arbitrary function that will run before the watch process evaluates the command(s). I also added helper functions for adding clear screen functionality. I also realized that we weren't using the watchOnEnter or watchOnExit callbacks anywhere. I had added these to support setting up some state before watch starts and cleaning it up before it exits for plugin authors. It makes sense to remove that functionality for 1.3.0 and only if a need presents itself re-add it in a later version of sbt. I also made a few apis private[sbt] that I'm not sure about. Writing documentation made me realize that some of these are redundant and/or not ready for general consumption.
This commit is contained in:
parent
3c81226ba9
commit
7948408368
|
|
@ -351,17 +351,17 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
aggregate(configs, logger, in, s, currentCount, isCommand, commands, fileStampCache)
|
||||
val task = () => {
|
||||
currentCount.getAndIncrement()
|
||||
callbacks.beforeCommand()
|
||||
// abort as soon as one of the tasks fails
|
||||
valid.takeWhile(_._3.apply())
|
||||
updateLegacyWatchState(s, configs.flatMap(_.inputs().map(_.glob)), currentCount.get())
|
||||
()
|
||||
}
|
||||
callbacks.onEnter()
|
||||
// Here we enter the Watched.watch state machine. We will not return until one of the
|
||||
// state machine callbacks returns Watched.CancelWatch, Watched.Custom, Watched.HandleError
|
||||
// or Watched.ReloadException. The task defined above will be run at least once. It will be run
|
||||
// additional times whenever the state transition callbacks return Watched.Trigger.
|
||||
try {
|
||||
// Here we enter the Watched.watch state machine. We will not return until one of the
|
||||
// state machine callbacks returns Watched.CancelWatch, Watched.Custom, Watched.HandleError
|
||||
// or Watched.ReloadException. The task defined above will be run at least once. It will be run
|
||||
// additional times whenever the state transition callbacks return Watched.Trigger.
|
||||
val terminationAction = Watch(task, callbacks.onStart, callbacks.nextEvent)
|
||||
terminationAction match {
|
||||
case e: Watch.HandleUnexpectedError =>
|
||||
|
|
@ -427,7 +427,7 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
|
||||
private class Callbacks(
|
||||
val nextEvent: () => Watch.Action,
|
||||
val onEnter: () => Unit,
|
||||
val beforeCommand: () => Unit,
|
||||
val onExit: () => Unit,
|
||||
val onStart: () => Watch.Action,
|
||||
val onTermination: (Watch.Action, String, Int, State) => State
|
||||
|
|
@ -468,19 +468,16 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
): Callbacks = {
|
||||
val project = extracted.currentRef.project
|
||||
val logger = setLevel(rawLogger, configs.map(_.watchSettings.logLevel).min, state)
|
||||
val onEnter = () => configs.foreach(_.watchSettings.onEnter())
|
||||
val beforeCommand = () => configs.foreach(_.watchSettings.beforeCommand())
|
||||
val onStart: () => Watch.Action = getOnStart(project, commands, configs, rawLogger, count)
|
||||
val nextInputEvent: () => Watch.Action = parseInputEvents(configs, state, inputStream, logger)
|
||||
val (nextFileEvent, cleanupFileMonitor): (() => Option[(Watch.Event, Watch.Action)], () => Unit) =
|
||||
getFileEvents(configs, rawLogger, state, count, commands, fileStampCache)
|
||||
val nextEvent: () => Watch.Action =
|
||||
combineInputAndFileEvents(nextInputEvent, nextFileEvent, logger)
|
||||
val onExit = () => {
|
||||
cleanupFileMonitor()
|
||||
configs.foreach(_.watchSettings.onExit())
|
||||
}
|
||||
val onExit = () => cleanupFileMonitor()
|
||||
val onTermination = getOnTermination(configs, isCommand)
|
||||
new Callbacks(nextEvent, onEnter, onExit, onStart, onTermination)
|
||||
new Callbacks(nextEvent, beforeCommand, onExit, onStart, onTermination)
|
||||
}
|
||||
|
||||
private def getOnTermination(
|
||||
|
|
@ -509,7 +506,7 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
val f: () => Seq[Watch.Action] = () => {
|
||||
configs.map { params =>
|
||||
val ws = params.watchSettings
|
||||
ws.onIteration.map(_(count.get)).getOrElse {
|
||||
ws.onIteration.map(_(count.get, project, commands)).getOrElse {
|
||||
if (configs.size == 1) { // Only allow custom start messages for single tasks
|
||||
ws.startMessage match {
|
||||
case Some(Left(sm)) => logger.info(sm(params.watchState(count.get())))
|
||||
|
|
@ -900,11 +897,10 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
val inputParser: Parser[Watch.Action] =
|
||||
key.get(watchInputParser).getOrElse(Watch.defaultInputParser)
|
||||
val logLevel: Level.Value = key.get(watchLogLevel).getOrElse(Level.Info)
|
||||
val onEnter: () => Unit = key.get(watchOnEnter).getOrElse(() => {})
|
||||
val onExit: () => Unit = key.get(watchOnExit).getOrElse(() => {})
|
||||
val beforeCommand: () => Unit = key.get(watchBeforeCommand).getOrElse(() => {})
|
||||
val onFileInputEvent: WatchOnEvent =
|
||||
key.get(watchOnFileInputEvent).getOrElse(Watch.trigger)
|
||||
val onIteration: Option[Int => Watch.Action] = key.get(watchOnIteration)
|
||||
val onIteration: Option[(Int, String, Seq[String]) => Watch.Action] = key.get(watchOnIteration)
|
||||
val onTermination: Option[(Watch.Action, String, Int, State) => State] =
|
||||
key.get(watchOnTermination)
|
||||
val startMessage: StartMessage = getStartMessage(key)
|
||||
|
|
|
|||
|
|
@ -68,12 +68,12 @@ object Keys {
|
|||
"detected regardless of whether or not the underlying file has actually changed."
|
||||
|
||||
// watch related keys
|
||||
val watchBeforeCommand = settingKey[() => Unit](
|
||||
"Function to run prior to running a command in a continuous build."
|
||||
).withRank(DSetting)
|
||||
val watchForceTriggerOnAnyChange =
|
||||
Def.settingKey[Boolean](forceTriggerOnAnyChangeMessage).withRank(DSetting)
|
||||
val watchLogLevel =
|
||||
settingKey[sbt.util.Level.Value]("Transform the default logger in continuous builds.")
|
||||
.withRank(DSetting)
|
||||
val watchInputHandler = settingKey[InputStream => Watch.Action](
|
||||
private[sbt] val watchInputHandler = settingKey[InputStream => Watch.Action](
|
||||
"Function that is periodically invoked to determine if the continuous build should be stopped or if a build should be triggered. It will usually read from stdin to respond to user commands. This is only invoked if watchInputStream is set."
|
||||
).withRank(DSetting)
|
||||
val watchInputStream = taskKey[InputStream](
|
||||
|
|
@ -82,16 +82,13 @@ object Keys {
|
|||
val watchInputParser = settingKey[Parser[Watch.Action]](
|
||||
"A parser of user input that can be used to trigger or exit a continuous build"
|
||||
).withRank(DSetting)
|
||||
val watchOnEnter = settingKey[() => Unit](
|
||||
"Function to run prior to beginning a continuous build. This will run before the continuous task(s) is(are) first evaluated."
|
||||
).withRank(DSetting)
|
||||
val watchOnExit = settingKey[() => Unit](
|
||||
"Function to run upon exit of a continuous build. It can be used to cleanup resources used during the watch."
|
||||
).withRank(DSetting)
|
||||
val watchLogLevel =
|
||||
settingKey[sbt.util.Level.Value]("Transform the default logger in continuous builds.")
|
||||
.withRank(DSetting)
|
||||
val watchOnFileInputEvent = settingKey[(Int, Watch.Event) => Watch.Action](
|
||||
"Callback to invoke if an event is triggered in a continuous build by one of the files matching an fileInput glob for the task and its transitive dependencies"
|
||||
).withRank(DSetting)
|
||||
val watchOnIteration = settingKey[Int => Watch.Action](
|
||||
val watchOnIteration = settingKey[(Int, String, Seq[String]) => Watch.Action](
|
||||
"Function that is invoked before waiting for file system events or user input events."
|
||||
).withRank(DSetting)
|
||||
val watchOnTermination = settingKey[(Watch.Action, String, Int, State) => State](
|
||||
|
|
|
|||
|
|
@ -420,7 +420,7 @@ object Watch {
|
|||
/**
|
||||
* Default no-op callback.
|
||||
*/
|
||||
val defaultOnEnter: () => Unit = () => {}
|
||||
val defaultBeforeCommand: () => Unit = () => {}
|
||||
|
||||
private[sbt] val defaultCommandOnTermination: (Action, String, Int, State) => State =
|
||||
onTerminationImpl(ContinuousExecutePrefix).label("Watched.defaultCommandOnTermination")
|
||||
|
|
@ -473,16 +473,25 @@ object Watch {
|
|||
final val defaultPollInterval: FiniteDuration = 500.milliseconds
|
||||
|
||||
/**
|
||||
* A constant function that returns an Option wrapped string that clears the screen when
|
||||
* written to stdout.
|
||||
* Clears the console screen when evaluated.
|
||||
*/
|
||||
final val clearOnTrigger: Int => Option[String] =
|
||||
((_: Int) => Some(Watched.clearScreen)).label("Watched.clearOnTrigger")
|
||||
final val clearScreen: () => Unit =
|
||||
(() => println("\u001b[2J\u001b[0;0H")).label("Watch.clearScreen")
|
||||
|
||||
/**
|
||||
* A function that first clears the screen and then returns the default on trigger message.
|
||||
*/
|
||||
final val clearScreenOnTrigger: (Int, Path, Seq[String]) => Option[String] = {
|
||||
(count: Int, path: Path, commands: Seq[String]) =>
|
||||
clearScreen()
|
||||
defaultOnTriggerMessage(count, path, commands)
|
||||
}.label("Watch.clearScreenOnTrigger")
|
||||
|
||||
private[sbt] def defaults: Seq[Def.Setting[_]] = Seq(
|
||||
sbt.Keys.watchAntiEntropy :== Watch.defaultAntiEntropy,
|
||||
watchAntiEntropyRetentionPeriod :== Watch.defaultAntiEntropyRetentionPeriod,
|
||||
watchLogLevel :== Level.Info,
|
||||
watchOnEnter :== Watch.defaultOnEnter,
|
||||
watchBeforeCommand :== Watch.defaultBeforeCommand,
|
||||
watchOnFileInputEvent :== Watch.trigger,
|
||||
watchDeletionQuarantinePeriod :== Watch.defaultDeletionQuarantinePeriod,
|
||||
sbt.Keys.watchService :== Watched.newWatchService,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
val foo = taskKey[Unit]("foo")
|
||||
foo := println("foo")
|
||||
|
||||
foo / watchOnIteration := { _ => sbt.nio.Watch.CancelWatch }
|
||||
foo / watchOnIteration := { (_, _, _) => sbt.nio.Watch.CancelWatch }
|
||||
addCommandAlias("bar", "foo")
|
||||
addCommandAlias("baz", "foo")
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ object Build {
|
|||
watchOnFileInputEvent := { (_, _) =>
|
||||
Watch.CancelWatch
|
||||
},
|
||||
Compile / compile / watchOnIteration := { _ =>
|
||||
Compile / compile / watchOnIteration := { (_, _, _) =>
|
||||
Watch.CancelWatch
|
||||
},
|
||||
checkTriggers := {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ watchTriggeredMessage := { (i, path: Path, c) =>
|
|||
prev(i, path, c)
|
||||
}
|
||||
|
||||
watchOnIteration := { i: Int =>
|
||||
watchOnIteration := { (i: Int, _, _) =>
|
||||
val base = baseDirectory.value.toPath
|
||||
val src =
|
||||
base.resolve("src").resolve("main").resolve("scala").resolve("sbt").resolve("test")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
val checkReloaded = taskKey[Unit]("Asserts that the build was reloaded")
|
||||
checkReloaded := { () }
|
||||
|
||||
watchOnIteration := { _ => sbt.nio.Watch.CancelWatch }
|
||||
watchOnIteration := { (_, _, _) => sbt.nio.Watch.CancelWatch }
|
||||
|
||||
Compile / compile := {
|
||||
Count.increment()
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@
|
|||
|
||||
# verify that the watch terminates when we reach the specified count
|
||||
> resetCount
|
||||
> set watchOnIteration := { (count: Int) => if (count == 2) sbt.nio.Watch.CancelWatch else sbt.nio.Watch.Ignore }
|
||||
> set watchOnIteration := { (count: Int, _, _) => if (count == 2) sbt.nio.Watch.CancelWatch else sbt.nio.Watch.Ignore }
|
||||
> ~compile
|
||||
> checkCount 2
|
||||
|
||||
# verify that the watch terminates and returns an error when we reach the specified count
|
||||
> resetCount
|
||||
> set watchOnIteration := { (count: Int) => if (count == 2) new sbt.nio.Watch.HandleError(new Exception("")) else sbt.nio.Watch.Ignore }
|
||||
> set watchOnIteration := { (count: Int, _, _) => if (count == 2) new sbt.nio.Watch.HandleError(new Exception("")) else sbt.nio.Watch.Ignore }
|
||||
# Returning Watch.HandleError causes the '~' command to fail
|
||||
-> ~compile
|
||||
> checkCount 2
|
||||
|
||||
# verify that a re-build is triggered when we reach the specified count
|
||||
> resetCount
|
||||
> set watchOnIteration := { (count: Int) => if (count == 2) sbt.nio.Watch.Trigger else if (count == 3) sbt.nio.Watch.CancelWatch else sbt.nio.Watch.Ignore }
|
||||
> set watchOnIteration := { (count: Int, _, _) => if (count == 2) sbt.nio.Watch.Trigger else if (count == 3) sbt.nio.Watch.CancelWatch else sbt.nio.Watch.Ignore }
|
||||
> ~compile
|
||||
> checkCount 3
|
||||
|
|
|
|||
|
|
@ -24,6 +24,6 @@ object Build {
|
|||
IO.touch(baseDirectory.value / "foo.txt", true)
|
||||
Some("watching")
|
||||
},
|
||||
watchOnIteration := { _ => Watch.CancelWatch }
|
||||
watchOnIteration := { (_, _, _) => Watch.CancelWatch }
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue