mirror of https://github.com/sbt/sbt.git
Improve watch messages
This commit reworks the watch start message so that instead of printing something like: [info] [watch] 1. Waiting for source changes... (press 'r' to re-run the command, 'x' to exit sbt or 'enter' to return to the shell) it instead prints something like: [info] 1. Monitoring source files for updates... [info] Project: filesJVM [info] Command: compile [info] Options: [info] <enter>: return to the shell [info] 'r': repeat the current command [info] 'x': exit sbt It will also print which path triggered the build.
This commit is contained in:
parent
c72005fd2b
commit
247d242008
|
|
@ -118,11 +118,12 @@ object Watched {
|
||||||
// Deprecated apis below
|
// Deprecated apis below
|
||||||
@deprecated("unused", "1.3.0")
|
@deprecated("unused", "1.3.0")
|
||||||
def projectWatchingMessage(projectId: String): WatchState => String =
|
def projectWatchingMessage(projectId: String): WatchState => String =
|
||||||
((ws: WatchState) => projectOnWatchMessage(projectId)(ws.count).get)
|
((ws: WatchState) => projectOnWatchMessage(projectId)(ws.count, projectId, Nil).get)
|
||||||
.label("Watched.projectWatchingMessage")
|
.label("Watched.projectWatchingMessage")
|
||||||
@deprecated("unused", "1.3.0")
|
@deprecated("unused", "1.3.0")
|
||||||
def projectOnWatchMessage(project: String): Int => Option[String] = { (count: Int) =>
|
def projectOnWatchMessage(project: String): (Int, String, Seq[String]) => Option[String] = {
|
||||||
Some(s"$count. ${waitMessage(s" in project $project")}")
|
(count: Int, _: String, _: Seq[String]) =>
|
||||||
|
Some(s"$count. ${waitMessage(s" in project $project")}")
|
||||||
}.label("Watched.projectOnWatchMessage")
|
}.label("Watched.projectOnWatchMessage")
|
||||||
|
|
||||||
@deprecated("This method is not used and may be removed in a future version of sbt", "1.3.0")
|
@deprecated("This method is not used and may be removed in a future version of sbt", "1.3.0")
|
||||||
|
|
|
||||||
|
|
@ -625,7 +625,6 @@ object Defaults extends BuildCommon {
|
||||||
clean := Clean.taskIn(ThisScope).value,
|
clean := Clean.taskIn(ThisScope).value,
|
||||||
consoleProject := consoleProjectTask.value,
|
consoleProject := consoleProjectTask.value,
|
||||||
watchTransitiveSources := watchTransitiveSourcesTask.value,
|
watchTransitiveSources := watchTransitiveSourcesTask.value,
|
||||||
watchStartMessage := Watched.projectOnWatchMessage(thisProjectRef.value.project),
|
|
||||||
watch := watchSetting.value,
|
watch := watchSetting.value,
|
||||||
fileOutputs += target.value ** AllPassFilter,
|
fileOutputs += target.value ** AllPassFilter,
|
||||||
transitiveGlobs := InputGraph.task.value,
|
transitiveGlobs := InputGraph.task.value,
|
||||||
|
|
|
||||||
|
|
@ -114,11 +114,11 @@ object Keys {
|
||||||
val watchOnIteration = settingKey[Int => Watch.Action]("Function that is invoked before waiting for file system events or user input events. This is only invoked if watchOnStart is not explicitly set.").withRank(DSetting)
|
val watchOnIteration = settingKey[Int => Watch.Action]("Function that is invoked before waiting for file system events or user input events. This is only invoked if watchOnStart is not explicitly set.").withRank(DSetting)
|
||||||
val watchOnStart = settingKey[Continuous.Arguments => () => Watch.Action]("Function is invoked before waiting for file system or input events. The returned Action is used to either trigger the build, terminate the watch or wait for events.").withRank(DSetting)
|
val watchOnStart = settingKey[Continuous.Arguments => () => Watch.Action]("Function is invoked before waiting for file system or input events. The returned Action is used to either trigger the build, terminate the watch or wait for events.").withRank(DSetting)
|
||||||
val watchService = settingKey[() => WatchService]("Service to use to monitor file system changes.").withRank(BMinusSetting).withRank(DSetting)
|
val watchService = settingKey[() => WatchService]("Service to use to monitor file system changes.").withRank(BMinusSetting).withRank(DSetting)
|
||||||
val watchStartMessage = settingKey[Int => Option[String]]("The message to show when triggered execution waits for sources to change. The parameter is the current watch iteration count.").withRank(DSetting)
|
val watchStartMessage = settingKey[(Int, String, Seq[String]) => Option[String]]("The message to show when triggered execution waits for sources to change. The parameters are the current watch iteration count, the current project name and the tasks that are being run with each build.").withRank(DSetting)
|
||||||
// The watchTasks key should really be named watch, but that is already taken by the deprecated watch key. I'd be surprised if there are any plugins that use it so I think we should consider breaking binary compatibility to rename this task.
|
// The watchTasks key should really be named watch, but that is already taken by the deprecated watch key. I'd be surprised if there are any plugins that use it so I think we should consider breaking binary compatibility to rename this task.
|
||||||
val watchTasks = InputKey[StateTransform]("watch", "Watch a task (or multiple tasks) and rebuild when its file inputs change or user input is received. The semantics are more or less the same as the `~` command except that it cannot transform the state on exit. This means that it cannot be used to reload the build.").withRank(DSetting)
|
val watchTasks = InputKey[StateTransform]("watch", "Watch a task (or multiple tasks) and rebuild when its file inputs change or user input is received. The semantics are more or less the same as the `~` command except that it cannot transform the state on exit. This means that it cannot be used to reload the build.").withRank(DSetting)
|
||||||
val watchTrackMetaBuild = settingKey[Boolean]("Toggles whether or not changing the build files (e.g. **/*.sbt, project/**/(*.scala | *.java)) should automatically trigger a project reload").withRank(DSetting)
|
val watchTrackMetaBuild = settingKey[Boolean]("Toggles whether or not changing the build files (e.g. **/*.sbt, project/**/(*.scala | *.java)) should automatically trigger a project reload").withRank(DSetting)
|
||||||
val watchTriggeredMessage = settingKey[(Int, Event[FileAttributes]) => Option[String]]("The message to show before triggered execution executes an action after sources change. The parameters are the path that triggered the build and the current watch iteration count.").withRank(DSetting)
|
val watchTriggeredMessage = settingKey[(Int, Event[FileAttributes], Seq[String]) => Option[String]]("The message to show before triggered execution executes an action after sources change. The parameters are the path that triggered the build and the current watch iteration count.").withRank(DSetting)
|
||||||
|
|
||||||
// Deprecated watch apis
|
// Deprecated watch apis
|
||||||
@deprecated("This is no longer used for continuous execution", "1.3.0")
|
@deprecated("This is no longer used for continuous execution", "1.3.0")
|
||||||
|
|
|
||||||
|
|
@ -145,9 +145,12 @@ object Watch {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action that indicates that we should exit and run the provided command.
|
* Action that indicates that we should exit and run the provided command.
|
||||||
|
*
|
||||||
* @param commands the commands to run after we exit the watch
|
* @param commands the commands to run after we exit the watch
|
||||||
*/
|
*/
|
||||||
final class Run(val commands: String*) extends CancelWatch
|
final class Run(val commands: String*) extends CancelWatch {
|
||||||
|
override def toString: String = s"Run(${commands.mkString(", ")})"
|
||||||
|
}
|
||||||
// For now leave this private in case this isn't the best unapply type signature since it can't
|
// For now leave this private in case this isn't the best unapply type signature since it can't
|
||||||
// be evolved in a binary compatible way.
|
// be evolved in a binary compatible way.
|
||||||
private object Run {
|
private object Run {
|
||||||
|
|
@ -289,33 +292,42 @@ object Watch {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts user input to an Action with the following rules:
|
* Converts user input to an Action with the following rules:
|
||||||
* 1) on all platforms, new lines exit the watch
|
* 1) 'x' or 'X' will exit sbt
|
||||||
* 2) on posix platforms, 'r' or 'R' will trigger a build
|
* 2) 'r' or 'R' will trigger a build
|
||||||
* 3) on posix platforms, 's' or 'S' will exit the watch and run the shell command. This is to
|
* 3) new line characters cancel the watch and return to the shell
|
||||||
* support the case where the user starts sbt in a continuous mode but wants to return to
|
|
||||||
* the shell without having to restart sbt.
|
|
||||||
*/
|
*/
|
||||||
final val defaultInputParser: Parser[Action] = {
|
final val defaultInputParser: Parser[Action] = {
|
||||||
def posixOnly(legal: String, action: Action): Parser[Action] =
|
val exitParser: Parser[Action] = chars("xX") ^^^ new Run("exit")
|
||||||
if (!Util.isWindows) chars(legal) ^^^ action
|
val rebuildParser: Parser[Action] = chars("rR") ^^^ Trigger
|
||||||
else Parser.invalid(Seq("Can't use jline for individual character entry on windows."))
|
val cancelParser: Parser[Action] = chars(legal = "\n\r") ^^^ new Run("iflast shell")
|
||||||
val rebuildParser: Parser[Action] = posixOnly(legal = "rR", Trigger)
|
exitParser | rebuildParser | cancelParser
|
||||||
val shellParser: Parser[Action] = posixOnly(legal = "sS", new Run("shell"))
|
|
||||||
val cancelParser: Parser[Action] = chars(legal = "\n\r") ^^^ CancelWatch
|
|
||||||
shellParser | rebuildParser | cancelParser
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private[this] val reRun =
|
private[this] val options = {
|
||||||
if (Util.isWindows) "" else ", 'r' to re-run the command or 's' to return to the shell"
|
val enter = "<enter>"
|
||||||
private[sbt] def waitMessage(project: String): String =
|
val newLine = if (Util.isWindows) enter else ""
|
||||||
s"Waiting for source changes$project... (press enter to interrupt$reRun)"
|
val opts = Seq(
|
||||||
|
s"$enter: return to the shell",
|
||||||
|
s"'r$newLine': repeat the current command",
|
||||||
|
s"'x$newLine': exit sbt"
|
||||||
|
)
|
||||||
|
s"Options:\n${opts.mkString(" ", "\n ", "")}"
|
||||||
|
}
|
||||||
|
private def waitMessage(project: String, commands: Seq[String]): String = {
|
||||||
|
val plural = if (commands.size > 1) "s" else ""
|
||||||
|
val cmds = commands.mkString("; ")
|
||||||
|
s"Monitoring source files for updates...\n" +
|
||||||
|
s"Project: $project\nCommand$plural: $cmds\n$options"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function that prints out the current iteration count and gives instructions for exiting
|
* A function that prints out the current iteration count and gives instructions for exiting
|
||||||
* or triggering the build.
|
* or triggering the build.
|
||||||
*/
|
*/
|
||||||
val defaultStartWatch: Int => Option[String] =
|
val defaultStartWatch: (Int, String, Seq[String]) => Option[String] = {
|
||||||
((count: Int) => Some(s"$count. ${waitMessage("")}")).label("Watched.defaultStartWatch")
|
(count: Int, project: String, commands: Seq[String]) =>
|
||||||
|
Some(s"$count. ${waitMessage(project, commands)}")
|
||||||
|
}.label("Watched.defaultStartWatch")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default no-op callback.
|
* Default no-op callback.
|
||||||
|
|
@ -325,7 +337,8 @@ object Watch {
|
||||||
private[sbt] val defaultCommandOnTermination: (Action, String, Int, State) => State =
|
private[sbt] val defaultCommandOnTermination: (Action, String, Int, State) => State =
|
||||||
onTerminationImpl(ContinuousExecutePrefix).label("Watched.defaultCommandOnTermination")
|
onTerminationImpl(ContinuousExecutePrefix).label("Watched.defaultCommandOnTermination")
|
||||||
private[sbt] val defaultTaskOnTermination: (Action, String, Int, State) => State =
|
private[sbt] val defaultTaskOnTermination: (Action, String, Int, State) => State =
|
||||||
onTerminationImpl("watch", ContinuousExecutePrefix).label("Watched.defaultTaskOnTermination")
|
onTerminationImpl("watch", ContinuousExecutePrefix)
|
||||||
|
.label("Watched.defaultTaskOnTermination")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default handler to transform the state when the watch terminates. When the [[Watch.Action]]
|
* Default handler to transform the state when the watch terminates. When the [[Watch.Action]]
|
||||||
|
|
@ -356,8 +369,15 @@ object Watch {
|
||||||
* `Keys.watchTriggeredMessage := Watched.defaultOnTriggerMessage`, then nothing is logged when
|
* `Keys.watchTriggeredMessage := Watched.defaultOnTriggerMessage`, then nothing is logged when
|
||||||
* a build is triggered.
|
* a build is triggered.
|
||||||
*/
|
*/
|
||||||
final val defaultOnTriggerMessage: (Int, Event[FileAttributes]) => Option[String] =
|
final val defaultOnTriggerMessage: (Int, Event[FileAttributes], Seq[String]) => Option[String] =
|
||||||
((_: Int, _: Event[FileAttributes]) => None).label("Watched.defaultOnTriggerMessage")
|
((_: Int, e: Event[FileAttributes], commands: Seq[String]) => {
|
||||||
|
val msg = s"Build triggered by ${e.entry.typedPath.toPath}. " +
|
||||||
|
s"Running ${commands.mkString("'", "; ", "'")}."
|
||||||
|
Some(msg)
|
||||||
|
}).label("Watched.defaultOnTriggerMessage")
|
||||||
|
|
||||||
|
final val noTriggerMessage: (Int, Event[FileAttributes], Seq[String]) => Option[String] =
|
||||||
|
(_, _, _) => None
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The minimum delay between file system polling when a `PollingWatchService` is used.
|
* The minimum delay between file system polling when a `PollingWatchService` is used.
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ object Continuous extends DeprecatedContinuous {
|
||||||
}
|
}
|
||||||
|
|
||||||
private[sbt] def setup[R](state: State, command: String)(
|
private[sbt] def setup[R](state: State, command: String)(
|
||||||
f: (State, Seq[(String, State, () => Boolean)], Seq[String]) => R
|
f: (Seq[String], State, Seq[(String, State, () => Boolean)], Seq[String]) => R
|
||||||
): R = {
|
): R = {
|
||||||
// First set up the state so that we can capture whether or not a task completed successfully
|
// First set up the state so that we can capture whether or not a task completed successfully
|
||||||
// or if it threw an Exception (we lose the actual exception, but that should still be printed
|
// or if it threw an Exception (we lose the actual exception, but that should still be printed
|
||||||
|
|
@ -273,7 +273,7 @@ object Continuous extends DeprecatedContinuous {
|
||||||
case Left(c) => (i :+ c, v)
|
case Left(c) => (i :+ c, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f(s, valid, invalid)
|
f(commands, s, valid, invalid)
|
||||||
}
|
}
|
||||||
|
|
||||||
private[sbt] def runToTermination(
|
private[sbt] def runToTermination(
|
||||||
|
|
@ -283,14 +283,14 @@ object Continuous extends DeprecatedContinuous {
|
||||||
isCommand: Boolean
|
isCommand: Boolean
|
||||||
): State = Watch.withCharBufferedStdIn { in =>
|
): State = Watch.withCharBufferedStdIn { in =>
|
||||||
val duped = new DupedInputStream(in)
|
val duped = new DupedInputStream(in)
|
||||||
setup(state.put(DupedSystemIn, duped), command) { (s, valid, invalid) =>
|
setup(state.put(DupedSystemIn, duped), command) { (commands, s, valid, invalid) =>
|
||||||
implicit val extracted: Extracted = Project.extract(s)
|
implicit val extracted: Extracted = Project.extract(s)
|
||||||
EvaluateTask.withStreams(extracted.structure, s)(_.use(Keys.streams in Global) { streams =>
|
EvaluateTask.withStreams(extracted.structure, s)(_.use(Keys.streams in Global) { streams =>
|
||||||
implicit val logger: Logger = streams.log
|
implicit val logger: Logger = streams.log
|
||||||
if (invalid.isEmpty) {
|
if (invalid.isEmpty) {
|
||||||
val currentCount = new AtomicInteger(count)
|
val currentCount = new AtomicInteger(count)
|
||||||
val configs = getAllConfigs(valid.map(v => v._1 -> v._2))
|
val configs = getAllConfigs(valid.map(v => v._1 -> v._2))
|
||||||
val callbacks = aggregate(configs, logger, in, s, currentCount, isCommand)
|
val callbacks = aggregate(configs, logger, in, s, currentCount, isCommand, commands)
|
||||||
val task = () => {
|
val task = () => {
|
||||||
currentCount.getAndIncrement()
|
currentCount.getAndIncrement()
|
||||||
// abort as soon as one of the tasks fails
|
// abort as soon as one of the tasks fails
|
||||||
|
|
@ -312,8 +312,8 @@ object Continuous extends DeprecatedContinuous {
|
||||||
} else {
|
} else {
|
||||||
// At least one of the commands in the multi command string could not be parsed, so we
|
// At least one of the commands in the multi command string could not be parsed, so we
|
||||||
// log an error and exit.
|
// log an error and exit.
|
||||||
val commands = invalid.mkString("'", "', '", "'")
|
val invalidCommands = invalid.mkString("'", "', '", "'")
|
||||||
logger.error(s"Terminating watch due to invalid command(s): $commands")
|
logger.error(s"Terminating watch due to invalid command(s): $invalidCommands")
|
||||||
state.fail
|
state.fail
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -378,16 +378,18 @@ object Continuous extends DeprecatedContinuous {
|
||||||
inputStream: InputStream,
|
inputStream: InputStream,
|
||||||
state: State,
|
state: State,
|
||||||
count: AtomicInteger,
|
count: AtomicInteger,
|
||||||
isCommand: Boolean
|
isCommand: Boolean,
|
||||||
|
commands: Seq[String]
|
||||||
)(
|
)(
|
||||||
implicit extracted: Extracted
|
implicit extracted: Extracted
|
||||||
): Callbacks = {
|
): Callbacks = {
|
||||||
|
val project = extracted.currentRef.project
|
||||||
val logger = setLevel(rawLogger, configs.map(_.watchSettings.logLevel).min, state)
|
val logger = setLevel(rawLogger, configs.map(_.watchSettings.logLevel).min, state)
|
||||||
val onEnter = () => configs.foreach(_.watchSettings.onEnter())
|
val onEnter = () => configs.foreach(_.watchSettings.onEnter())
|
||||||
val onStart: () => Watch.Action = getOnStart(configs, logger, count)
|
val onStart: () => Watch.Action = getOnStart(project, commands, configs, rawLogger, count)
|
||||||
val nextInputEvent: () => Watch.Action = parseInputEvents(configs, state, inputStream, logger)
|
val nextInputEvent: () => Watch.Action = parseInputEvents(configs, state, inputStream, logger)
|
||||||
val (nextFileEvent, cleanupFileMonitor): (() => Watch.Action, () => Unit) =
|
val (nextFileEvent, cleanupFileMonitor): (() => Option[(Event, Watch.Action)], () => Unit) =
|
||||||
getFileEvents(configs, logger, state, count)
|
getFileEvents(configs, rawLogger, state, count, commands)
|
||||||
val nextEvent: () => Watch.Action =
|
val nextEvent: () => Watch.Action =
|
||||||
combineInputAndFileEvents(nextInputEvent, nextFileEvent, logger)
|
combineInputAndFileEvents(nextInputEvent, nextFileEvent, logger)
|
||||||
val onExit = () => {
|
val onExit = () => {
|
||||||
|
|
@ -415,6 +417,8 @@ object Continuous extends DeprecatedContinuous {
|
||||||
}
|
}
|
||||||
|
|
||||||
private def getOnStart(
|
private def getOnStart(
|
||||||
|
project: String,
|
||||||
|
commands: Seq[String],
|
||||||
configs: Seq[Config],
|
configs: Seq[Config],
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
count: AtomicInteger
|
count: AtomicInteger
|
||||||
|
|
@ -426,8 +430,9 @@ object Continuous extends DeprecatedContinuous {
|
||||||
if (configs.size == 1) { // Only allow custom start messages for single tasks
|
if (configs.size == 1) { // Only allow custom start messages for single tasks
|
||||||
ws.startMessage match {
|
ws.startMessage match {
|
||||||
case Some(Left(sm)) => logger.info(sm(params.watchState(count.get())))
|
case Some(Left(sm)) => logger.info(sm(params.watchState(count.get())))
|
||||||
case Some(Right(sm)) => sm(count.get()).foreach(logger.info(_))
|
case Some(Right(sm)) => sm(count.get(), project, commands).foreach(logger.info(_))
|
||||||
case None => Watch.defaultStartWatch(count.get()).foreach(logger.info(_))
|
case None =>
|
||||||
|
Watch.defaultStartWatch(count.get(), project, commands).foreach(logger.info(_))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Watch.Ignore
|
Watch.Ignore
|
||||||
|
|
@ -438,7 +443,8 @@ object Continuous extends DeprecatedContinuous {
|
||||||
{
|
{
|
||||||
val res = f.view.map(_()).min
|
val res = f.view.map(_()).min
|
||||||
// Print the default watch message if there are multiple tasks
|
// Print the default watch message if there are multiple tasks
|
||||||
if (configs.size > 1) Watch.defaultStartWatch(count.get()).foreach(logger.info(_))
|
if (configs.size > 1)
|
||||||
|
Watch.defaultStartWatch(count.get(), project, commands).foreach(logger.info(_))
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -447,40 +453,14 @@ object Continuous extends DeprecatedContinuous {
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
state: State,
|
state: State,
|
||||||
count: AtomicInteger,
|
count: AtomicInteger,
|
||||||
)(implicit extracted: Extracted): (() => Watch.Action, () => Unit) = {
|
commands: Seq[String]
|
||||||
|
)(implicit extracted: Extracted): (() => Option[(Event, Watch.Action)], () => Unit) = {
|
||||||
val trackMetaBuild = configs.forall(_.watchSettings.trackMetaBuild)
|
val trackMetaBuild = configs.forall(_.watchSettings.trackMetaBuild)
|
||||||
val buildGlobs =
|
val buildGlobs =
|
||||||
if (trackMetaBuild) extracted.getOpt(Keys.fileInputs in Keys.settingsData).getOrElse(Nil)
|
if (trackMetaBuild) extracted.getOpt(Keys.fileInputs in Keys.settingsData).getOrElse(Nil)
|
||||||
else Nil
|
else Nil
|
||||||
val buildFilter = buildGlobs.toEntryFilter
|
val buildFilter = buildGlobs.toEntryFilter
|
||||||
|
|
||||||
/*
|
|
||||||
* This is a callback that will be invoked whenever onEvent returns a Trigger action. The
|
|
||||||
* motivation is to allow the user to specify this callback via setting so that, for example,
|
|
||||||
* they can clear the screen when the build triggers.
|
|
||||||
*/
|
|
||||||
val onTrigger: Event => Watch.Action = {
|
|
||||||
val f: Seq[Event => Unit] = configs.map { params =>
|
|
||||||
val ws = params.watchSettings
|
|
||||||
ws.onTrigger
|
|
||||||
.map(_.apply(params.arguments(logger)))
|
|
||||||
.getOrElse { event: Event =>
|
|
||||||
val globFilter =
|
|
||||||
(params.inputs() ++ params.triggers).toEntryFilter
|
|
||||||
if (globFilter(event.entry)) {
|
|
||||||
ws.triggerMessage match {
|
|
||||||
case Some(Left(tm)) => logger.info(tm(params.watchState(count.get())))
|
|
||||||
case Some(Right(tm)) => tm(count.get(), event).foreach(logger.info(_))
|
|
||||||
case None => // By default don't print anything
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
event: Event =>
|
|
||||||
f.view.foreach(_.apply(event))
|
|
||||||
Watch.Trigger
|
|
||||||
}
|
|
||||||
|
|
||||||
val defaultTrigger = if (Util.isWindows) Watch.ifChanged(Watch.Trigger) else Watch.trigger
|
val defaultTrigger = if (Util.isWindows) Watch.ifChanged(Watch.Trigger) else Watch.trigger
|
||||||
val onEvent: Event => (Event, Watch.Action) = {
|
val onEvent: Event => (Event, Watch.Action) = {
|
||||||
val f = configs.map { params =>
|
val f = configs.map { params =>
|
||||||
|
|
@ -504,10 +484,7 @@ object Continuous extends DeprecatedContinuous {
|
||||||
).min
|
).min
|
||||||
}
|
}
|
||||||
event: Event =>
|
event: Event =>
|
||||||
event -> (oe(event) match {
|
event -> oe(event)
|
||||||
case Watch.Trigger => onTrigger(event)
|
|
||||||
case a => a
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
event: Event =>
|
event: Event =>
|
||||||
f.view.map(_.apply(event)).minBy(_._2)
|
f.view.map(_.apply(event)).minBy(_._2)
|
||||||
|
|
@ -568,13 +545,43 @@ object Continuous extends DeprecatedContinuous {
|
||||||
quarantinePeriod,
|
quarantinePeriod,
|
||||||
retentionPeriod
|
retentionPeriod
|
||||||
)
|
)
|
||||||
|
/*
|
||||||
|
* This is a callback that will be invoked whenever onEvent returns a Trigger action. The
|
||||||
|
* motivation is to allow the user to specify this callback via setting so that, for example,
|
||||||
|
* they can clear the screen when the build triggers.
|
||||||
|
*/
|
||||||
|
val onTrigger: Event => Unit = { event: Event =>
|
||||||
|
configs.foreach { params =>
|
||||||
|
params.watchSettings.onTrigger.foreach(ot => ot(params.arguments(logger))(event))
|
||||||
|
}
|
||||||
|
if (configs.size == 1) {
|
||||||
|
val config = configs.head
|
||||||
|
config.watchSettings.triggerMessage match {
|
||||||
|
case Left(tm) => logger.info(tm(config.watchState(count.get())))
|
||||||
|
case Right(tm) => tm(count.get(), event, commands).foreach(logger.info(_))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Watch.defaultOnTriggerMessage(count.get(), event, commands).foreach(logger.info(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
val actions = antiEntropyMonitor.poll(2.milliseconds).map(onEvent)
|
val actions = antiEntropyMonitor.poll(2.milliseconds).map(onEvent)
|
||||||
if (actions.exists(_._2 != Watch.Ignore)) {
|
if (actions.exists(_._2 != Watch.Ignore)) {
|
||||||
val min = actions.minBy(_._2)
|
val builder = new StringBuilder
|
||||||
logger.debug(s"Received file event actions: ${actions.mkString(", ")}. Returning: $min")
|
val min = actions.minBy {
|
||||||
min._2
|
case (e, a) =>
|
||||||
} else Watch.Ignore
|
if (builder.nonEmpty) builder.append(", ")
|
||||||
|
val path = e.entry.typedPath.toPath.toString
|
||||||
|
builder.append(path)
|
||||||
|
builder.append(" -> ")
|
||||||
|
builder.append(a.toString)
|
||||||
|
a
|
||||||
|
}
|
||||||
|
logger.debug(s"Received file event actions: $builder. Returning: $min")
|
||||||
|
if (min._2 == Watch.Trigger) onTrigger(min._1)
|
||||||
|
Some(min)
|
||||||
|
} else None
|
||||||
}, () => monitor.close())
|
}, () => monitor.close())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -653,21 +660,27 @@ object Continuous extends DeprecatedContinuous {
|
||||||
}
|
}
|
||||||
|
|
||||||
private def combineInputAndFileEvents(
|
private def combineInputAndFileEvents(
|
||||||
nextInputEvent: () => Watch.Action,
|
nextInputAction: () => Watch.Action,
|
||||||
nextFileEvent: () => Watch.Action,
|
nextFileEvent: () => Option[(Event, Watch.Action)],
|
||||||
logger: Logger
|
logger: Logger
|
||||||
): () => Watch.Action = () => {
|
): () => Watch.Action = () => {
|
||||||
val Seq(inputEvent: Watch.Action, fileEvent: Watch.Action) =
|
val (inputAction: Watch.Action, fileEvent: Option[(Event, Watch.Action)] @unchecked) =
|
||||||
Seq(nextInputEvent, nextFileEvent).par.map(_.apply()).toIndexedSeq
|
Seq(nextInputAction, nextFileEvent).map(_.apply()).toIndexedSeq match {
|
||||||
val min: Watch.Action = Seq[Watch.Action](inputEvent, fileEvent).min
|
case Seq(ia: Watch.Action, fe @ Some(_)) => (ia, fe)
|
||||||
|
case Seq(ia: Watch.Action, None) => (ia, None)
|
||||||
|
}
|
||||||
|
val min: Watch.Action = (fileEvent.map(_._2).toSeq :+ inputAction).min
|
||||||
lazy val inputMessage =
|
lazy val inputMessage =
|
||||||
s"Received input event: $inputEvent." +
|
s"Received input event: $inputAction." +
|
||||||
(if (inputEvent != min) s" Dropping in favor of file event: $min" else "")
|
(if (inputAction != min) s" Dropping in favor of file event: $min" else "")
|
||||||
lazy val fileMessage =
|
if (inputAction != Watch.Ignore) logger.debug(inputMessage)
|
||||||
s"Received file event: $fileEvent." +
|
fileEvent
|
||||||
(if (fileEvent != min) s" Dropping in favor of input event: $min" else "")
|
.collect {
|
||||||
if (inputEvent != Watch.Ignore) logger.debug(inputMessage)
|
case (event, action) if action != Watch.Ignore =>
|
||||||
if (fileEvent != Watch.Ignore) logger.debug(fileMessage)
|
s"Received file event $action for ${event.entry.typedPath.toPath}." +
|
||||||
|
(if (action != min) s" Dropping in favor of input event: $min" else "")
|
||||||
|
}
|
||||||
|
.foreach(logger.debug(_))
|
||||||
min
|
min
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -696,8 +709,7 @@ object Continuous extends DeprecatedContinuous {
|
||||||
* @return the wrapped logger.
|
* @return the wrapped logger.
|
||||||
*/
|
*/
|
||||||
private def setLevel(logger: Logger, logLevel: Level.Value, state: State): Logger = {
|
private def setLevel(logger: Logger, logLevel: Level.Value, state: State): Logger = {
|
||||||
import Level._
|
val delegateLevel: Level.Value = state.get(Keys.logLevel.key).getOrElse(Level.Info)
|
||||||
val delegateLevel = state.get(Keys.logLevel.key).getOrElse(Info)
|
|
||||||
/*
|
/*
|
||||||
* The delegate logger may be set to, say, info level, but we want it to print out debug
|
* The delegate logger may be set to, say, info level, but we want it to print out debug
|
||||||
* messages if the logLevel variable above is Debug. To do this, we promote Debug messages
|
* messages if the logLevel variable above is Debug. To do this, we promote Debug messages
|
||||||
|
|
@ -804,7 +816,7 @@ object Continuous extends DeprecatedContinuous {
|
||||||
lazy val default = key.get(Keys.watchStartMessage).getOrElse(Watch.defaultStartWatch)
|
lazy val default = key.get(Keys.watchStartMessage).getOrElse(Watch.defaultStartWatch)
|
||||||
key.get(deprecatedWatchingMessage).map(Left(_)).getOrElse(Right(default))
|
key.get(deprecatedWatchingMessage).map(Left(_)).getOrElse(Right(default))
|
||||||
}
|
}
|
||||||
private def getTriggerMessage(key: ScopedKey[_])(implicit e: Extracted): TriggerMessage = Some {
|
private def getTriggerMessage(key: ScopedKey[_])(implicit e: Extracted): TriggerMessage = {
|
||||||
lazy val default =
|
lazy val default =
|
||||||
key.get(Keys.watchTriggeredMessage).getOrElse(Watch.defaultOnTriggerMessage)
|
key.get(Keys.watchTriggeredMessage).getOrElse(Watch.defaultOnTriggerMessage)
|
||||||
key.get(deprecatedWatchingMessage).map(Left(_)).getOrElse(Right(default))
|
key.get(deprecatedWatchingMessage).map(Left(_)).getOrElse(Right(default))
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,9 @@ import sbt.internal.io.{ WatchState => WS }
|
||||||
|
|
||||||
private[internal] trait DeprecatedContinuous {
|
private[internal] trait DeprecatedContinuous {
|
||||||
protected type Event = sbt.io.FileEventMonitor.Event[FileAttributes]
|
protected type Event = sbt.io.FileEventMonitor.Event[FileAttributes]
|
||||||
protected type StartMessage = Option[Either[WS => String, Int => Option[String]]]
|
protected type StartMessage =
|
||||||
protected type TriggerMessage = Option[Either[WS => String, (Int, Event) => Option[String]]]
|
Option[Either[WS => String, (Int, String, Seq[String]) => Option[String]]]
|
||||||
|
protected type TriggerMessage = Either[WS => String, (Int, Event, Seq[String]) => Option[String]]
|
||||||
protected type DeprecatedWatchState = WS
|
protected type DeprecatedWatchState = WS
|
||||||
protected val deprecatedWatchingMessage = sbt.Keys.watchingMessage
|
protected val deprecatedWatchingMessage = sbt.Keys.watchingMessage
|
||||||
protected val deprecatedTriggeredMessage = sbt.Keys.triggeredMessage
|
protected val deprecatedTriggeredMessage = sbt.Keys.triggeredMessage
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,17 @@ object Build {
|
||||||
assert(IO.read(file(stringFile)) == string)
|
assert(IO.read(file(stringFile)) == string)
|
||||||
}
|
}
|
||||||
lazy val foo = project.settings(
|
lazy val foo = project.settings(
|
||||||
watchStartMessage := { (count: Int) => Some(s"FOO $count") },
|
watchStartMessage := { (count: Int, _, _) => Some(s"FOO $count") },
|
||||||
Compile / compile / watchTriggers += baseDirectory.value * "foo.txt",
|
Compile / compile / watchTriggers += baseDirectory.value * "foo.txt",
|
||||||
Compile / compile / watchStartMessage := { (count: Int) =>
|
Compile / compile / watchStartMessage := { (count: Int, _, _) =>
|
||||||
// this checks that Compile / compile / watchStartMessage
|
// this checks that Compile / compile / watchStartMessage
|
||||||
// is preferred to Compile / watchStartMessage
|
// is preferred to Compile / watchStartMessage
|
||||||
val outputFile = baseDirectory.value / "foo.txt"
|
val outputFile = baseDirectory.value / "foo.txt"
|
||||||
IO.write(outputFile, "compile")
|
IO.write(outputFile, "compile")
|
||||||
Some(s"compile $count")
|
Some(s"compile $count")
|
||||||
},
|
},
|
||||||
Compile / watchStartMessage := { (count: Int) => Some(s"Compile $count") },
|
Compile / watchStartMessage := { (count: Int, _, _) => Some(s"Compile $count") },
|
||||||
Runtime / watchStartMessage := { (count: Int) => Some(s"Runtime $count") },
|
Runtime / watchStartMessage := { (count: Int, _, _) => Some(s"Runtime $count") },
|
||||||
setStringValue := {
|
setStringValue := {
|
||||||
val _ = (fileInputs in (bar, setStringValue)).value
|
val _ = (fileInputs in (bar, setStringValue)).value
|
||||||
setStringValueImpl.evaluated
|
setStringValueImpl.evaluated
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ object Build {
|
||||||
def setStringValueImpl: Def.Initialize[Task[Unit]] = Def.task {
|
def setStringValueImpl: Def.Initialize[Task[Unit]] = Def.task {
|
||||||
val i = (setStringValue / fileInputs).value
|
val i = (setStringValue / fileInputs).value
|
||||||
val (stringFile, string) = ("foo.txt", "bar")
|
val (stringFile, string) = ("foo.txt", "bar")
|
||||||
IO.write(file(stringFile), string)
|
val absoluteFile = baseDirectory.value.toPath.resolve(stringFile).toFile
|
||||||
|
IO.write(absoluteFile, string)
|
||||||
}
|
}
|
||||||
def checkStringValueImpl: Def.Initialize[InputTask[Unit]] = Def.inputTask {
|
def checkStringValueImpl: Def.Initialize[InputTask[Unit]] = Def.inputTask {
|
||||||
val Seq(stringFile, string) = Def.spaceDelimited().parsed
|
val Seq(stringFile, string) = Def.spaceDelimited().parsed
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ object Build {
|
||||||
val root = (project in file(".")).settings(
|
val root = (project in file(".")).settings(
|
||||||
useSuperShell := false,
|
useSuperShell := false,
|
||||||
watchInputStream := inputStream,
|
watchInputStream := inputStream,
|
||||||
watchStartMessage := { count =>
|
watchStartMessage := { (_, _, _) =>
|
||||||
Build.outputStream.write('\n'.toByte)
|
Build.outputStream.write('\n'.toByte)
|
||||||
Build.outputStream.flush()
|
Build.outputStream.flush()
|
||||||
Some("default start message")
|
Some("default start message")
|
||||||
|
|
@ -24,12 +24,12 @@ object Build {
|
||||||
// Note that the order is byeParser | helloParser. In general, we want the higher priority
|
// Note that the order is byeParser | helloParser. In general, we want the higher priority
|
||||||
// action to come first because otherwise we would potentially scan past it.
|
// action to come first because otherwise we would potentially scan past it.
|
||||||
val helloOrByeParser: Parser[Watch.Action] = byeParser | helloParser
|
val helloOrByeParser: Parser[Watch.Action] = byeParser | helloParser
|
||||||
val alternativeStartMessage: Int => Option[String] = { _ =>
|
val alternativeStartMessage: (Int, String, Seq[String]) => Option[String] = { (_, _, _) =>
|
||||||
outputStream.write("xybyexyblahxyhelloxy".getBytes)
|
outputStream.write("xybyexyblahxyhelloxy".getBytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
Some("alternative start message")
|
Some("alternative start message")
|
||||||
}
|
}
|
||||||
val otherAlternativeStartMessage: Int => Option[String] = { _ =>
|
val otherAlternativeStartMessage: (Int, String, Seq[String]) => Option[String] = { (_, _, _) =>
|
||||||
outputStream.write("xyhellobyexyblahx".getBytes)
|
outputStream.write("xyhellobyexyblahx".getBytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
Some("other alternative start message")
|
Some("other alternative start message")
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ object Build {
|
||||||
setStringValue / watchTriggers += baseDirectory.value * "foo.txt",
|
setStringValue / watchTriggers += baseDirectory.value * "foo.txt",
|
||||||
setStringValue := setStringValueImpl.evaluated,
|
setStringValue := setStringValueImpl.evaluated,
|
||||||
checkStringValue := checkStringValueImpl.evaluated,
|
checkStringValue := checkStringValueImpl.evaluated,
|
||||||
watchStartMessage := { _ =>
|
watchStartMessage := { (_, _, _) =>
|
||||||
IO.touch(baseDirectory.value / "foo.txt", true)
|
IO.touch(baseDirectory.value / "foo.txt", true)
|
||||||
Some("watching")
|
Some("watching")
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ object Build {
|
||||||
setStringValue / watchTriggers += baseDirectory.value * "foo.txt",
|
setStringValue / watchTriggers += baseDirectory.value * "foo.txt",
|
||||||
setStringValue := setStringValueImpl.evaluated,
|
setStringValue := setStringValueImpl.evaluated,
|
||||||
checkStringValue := checkStringValueImpl.evaluated,
|
checkStringValue := checkStringValueImpl.evaluated,
|
||||||
watchStartMessage := { _ =>
|
watchStartMessage := { (_, _, _) =>
|
||||||
IO.touch(baseDirectory.value / "foo.txt", true)
|
IO.touch(baseDirectory.value / "foo.txt", true)
|
||||||
Some("watching")
|
Some("watching")
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue