From 4193cc323d00a654a081aa3067b743529eb930c3 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sun, 2 Jun 2019 09:57:19 -0700 Subject: [PATCH 1/9] Remove leading semicolon from multi command help --- main-command/src/main/scala/sbt/BasicCommandStrings.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index 5240a060f..331852d12 100644 --- a/main-command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main-command/src/main/scala/sbt/BasicCommandStrings.scala @@ -151,7 +151,7 @@ $HelpCommand def Multi: String = ";" def MultiBrief: (String, String) = ( - Multi + " (" + Multi + " )*", + " (" + Multi + " )*", "Runs the provided semicolon-separated commands." ) def MultiDetailed: String = From 67cbd6c50e0e436fc8e1b40e357fb48a70956afc Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sun, 2 Jun 2019 11:19:55 -0700 Subject: [PATCH 2/9] Move watch default settings I had previously set some of the watch settings at the global level and some at the project level. While writing documentation for the new watch subsystem, I realized that the defaults should be set globally so that they can be overridden at the ThisBuild level. I also moved watchTriggers to sbt.nio.Keys. It was an oversight that it wasn't moved there in a5cefd45be77c4f7eb257a751d26c7ea66b9fdc9. --- main/src/main/scala/sbt/Defaults.scala | 17 +---------------- main/src/main/scala/sbt/Keys.scala | 1 - main/src/main/scala/sbt/ScriptedPlugin.scala | 1 + main/src/main/scala/sbt/nio/Keys.scala | 2 ++ main/src/main/scala/sbt/nio/Watch.scala | 18 ++++++++++++++++++ 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 4b6e11db9..68882c9dc 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -154,15 +154,11 @@ object Defaults extends BuildCommon { inputFileStamper :== sbt.nio.FileStamper.Hash, outputFileStamper :== sbt.nio.FileStamper.LastModified, onChangedBuildSource :== sbt.nio.Keys.WarnOnSourceChanges, - watchTriggeredMessage :== sbt.nio.Watch.defaultOnTriggerMessage, - watchForceTriggerOnAnyChange :== false, - watchPersistFileStamps :== true, - watchTriggers :== Nil, clean := { () }, unmanagedFileStampCache := state.value.get(persistentFileStampCache).getOrElse(new sbt.nio.FileStamp.Cache), managedFileStampCache := new sbt.nio.FileStamp.Cache, - ) ++ globalIvyCore ++ globalJvmCore + ) ++ globalIvyCore ++ globalJvmCore ++ Watch.defaults ) ++ globalSbtCore private[sbt] lazy val globalJvmCore: Seq[Setting[_]] = @@ -344,17 +340,6 @@ object Defaults extends BuildCommon { sys.env.contains("CI") || System.getProperty("sbt.ci", "false") == "true", // watch related settings pollInterval :== Watch.defaultPollInterval, - watchAntiEntropy :== Watch.defaultAntiEntropy, - watchAntiEntropyRetentionPeriod :== Watch.defaultAntiEntropyRetentionPeriod, - watchLogLevel :== Level.Info, - watchOnEnter :== Watch.defaultOnEnter, - watchOnFileInputEvent :== Watch.trigger, - watchDeletionQuarantinePeriod :== Watch.defaultDeletionQuarantinePeriod, - watchService :== Watched.newWatchService, - watchStartMessage :== Watch.defaultStartWatch, - watchTasks := Continuous.continuousTask.evaluated, - aggregate in watchTasks :== false, - watchTriggeredMessage :== Watch.defaultOnTriggerMessage, ) ) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 0057dd087..de3266bee 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -125,7 +125,6 @@ object Keys { val managedSources = taskKey[Seq[File]]("Sources generated by the build.").withRank(BTask) val sources = taskKey[Seq[File]]("All sources, both managed and unmanaged.").withRank(BTask) val sourcesInBase = settingKey[Boolean]("If true, sources from the project's base directory are included as main sources.") - val watchTriggers = settingKey[Seq[Glob]]("Describes files that should trigger a new continuous build.") // Filters val includeFilter = settingKey[FileFilter]("Filter for including sources and resources files from default directories.").withRank(CSetting) diff --git a/main/src/main/scala/sbt/ScriptedPlugin.scala b/main/src/main/scala/sbt/ScriptedPlugin.scala index 2bc1b13fe..113fdd114 100644 --- a/main/src/main/scala/sbt/ScriptedPlugin.scala +++ b/main/src/main/scala/sbt/ScriptedPlugin.scala @@ -12,6 +12,7 @@ import java.lang.reflect.Method import sbt.Def._ import sbt.Keys._ +import sbt.nio.Keys._ import sbt.Project._ import sbt.internal.inc.ModuleUtilities import sbt.internal.inc.classpath.ClasspathUtilities diff --git a/main/src/main/scala/sbt/nio/Keys.scala b/main/src/main/scala/sbt/nio/Keys.scala index 9d2702a96..7632e04cc 100644 --- a/main/src/main/scala/sbt/nio/Keys.scala +++ b/main/src/main/scala/sbt/nio/Keys.scala @@ -108,6 +108,8 @@ object Keys { "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 watchTriggers = + settingKey[Seq[Glob]]("Describes files that should trigger a new continuous build.") val watchTriggeredMessage = settingKey[(Int, Path, 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) diff --git a/main/src/main/scala/sbt/nio/Watch.scala b/main/src/main/scala/sbt/nio/Watch.scala index 5eb4cf344..745af45a1 100644 --- a/main/src/main/scala/sbt/nio/Watch.scala +++ b/main/src/main/scala/sbt/nio/Watch.scala @@ -15,10 +15,12 @@ import java.util.concurrent.TimeUnit import sbt.BasicCommandStrings.ContinuousExecutePrefix import sbt._ +import sbt.internal.Continuous import sbt.internal.LabeledFunctions._ import sbt.internal.nio.FileEvent import sbt.internal.util.complete.Parser import sbt.internal.util.complete.Parser._ +import sbt.nio.Keys._ import sbt.nio.file.FileAttributes import sbt.util.{ Level, Logger } @@ -476,4 +478,20 @@ object Watch { */ final val clearOnTrigger: Int => Option[String] = ((_: Int) => Some(Watched.clearScreen)).label("Watched.clearOnTrigger") + private[sbt] def defaults: Seq[Def.Setting[_]] = Seq( + sbt.Keys.watchAntiEntropy :== Watch.defaultAntiEntropy, + watchAntiEntropyRetentionPeriod :== Watch.defaultAntiEntropyRetentionPeriod, + watchLogLevel :== Level.Info, + watchOnEnter :== Watch.defaultOnEnter, + watchOnFileInputEvent :== Watch.trigger, + watchDeletionQuarantinePeriod :== Watch.defaultDeletionQuarantinePeriod, + sbt.Keys.watchService :== Watched.newWatchService, + watchStartMessage :== Watch.defaultStartWatch, + watchTasks := Continuous.continuousTask.evaluated, + sbt.Keys.aggregate in watchTasks :== false, + watchTriggeredMessage :== Watch.defaultOnTriggerMessage, + watchForceTriggerOnAnyChange :== false, + watchPersistFileStamps :== true, + watchTriggers :== Nil, + ) } From 2905373ff79961fce8ec62898789507c916dd19d Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sun, 2 Jun 2019 11:22:04 -0700 Subject: [PATCH 3/9] Use logger instead of directly printing to System.err --- main/src/main/scala/sbt/internal/Continuous.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/internal/Continuous.scala b/main/src/main/scala/sbt/internal/Continuous.scala index a87060d34..6c26587fa 100644 --- a/main/src/main/scala/sbt/internal/Continuous.scala +++ b/main/src/main/scala/sbt/internal/Continuous.scala @@ -365,8 +365,8 @@ private[sbt] object Continuous extends DeprecatedContinuous { val terminationAction = Watch(task, callbacks.onStart, callbacks.nextEvent) terminationAction match { case e: Watch.HandleUnexpectedError => - System.err.println("Caught unexpected error running continuous build:") - e.throwable.printStackTrace(System.err) + logger.error("Caught unexpected error running continuous build:") + e.throwable.getStackTrace.foreach(e => logger.error(e.toString)) case _ => } callbacks.onTermination(terminationAction, command, currentCount.get(), state) From 4f66b81e03dec29941de78dc8cbb4fcb262b4379 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sun, 2 Jun 2019 11:24:57 -0700 Subject: [PATCH 4/9] Fix parameters in watchTriggeredMessage --- main/src/main/scala/sbt/nio/Keys.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/nio/Keys.scala b/main/src/main/scala/sbt/nio/Keys.scala index 7632e04cc..528cfaabd 100644 --- a/main/src/main/scala/sbt/nio/Keys.scala +++ b/main/src/main/scala/sbt/nio/Keys.scala @@ -111,7 +111,7 @@ object Keys { val watchTriggers = settingKey[Seq[Glob]]("Describes files that should trigger a new continuous build.") val watchTriggeredMessage = settingKey[(Int, Path, 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." + "The message to show before triggered execution executes an action after sources change. The parameters are the current watch iteration count, the path that triggered the build and the names of the commands to run." ).withRank(DSetting) // internal keys From 3c81226ba9ebd05aa4e8269205d3a395e2012d77 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sun, 2 Jun 2019 12:13:54 -0700 Subject: [PATCH 5/9] Add sbt.nio.Watch to default imports I realized writing documentation that it is a pain to not have Watch available in build.sbt with an import. --- sbt/src/main/scala/sbt/Import.scala | 1 + .../sbt-test/watch/file-input-aggregation/project/Build.scala | 1 - sbt/src/sbt-test/watch/input-parser/project/Build.scala | 1 - sbt/src/sbt-test/watch/legacy-sources/build.sbt | 1 - sbt/src/sbt-test/watch/managed/build.sbt | 2 -- sbt/src/sbt-test/watch/on-change/build.sbt | 3 --- sbt/src/sbt-test/watch/overlapping/build.sbt | 2 -- 7 files changed, 1 insertion(+), 10 deletions(-) diff --git a/sbt/src/main/scala/sbt/Import.scala b/sbt/src/main/scala/sbt/Import.scala index b7addad35..5d3d72530 100644 --- a/sbt/src/main/scala/sbt/Import.scala +++ b/sbt/src/main/scala/sbt/Import.scala @@ -71,6 +71,7 @@ trait Import { type RelativeGlob = sbt.nio.file.RelativeGlob val RelativeGlob = sbt.nio.file.RelativeGlob val RecursiveGlob = sbt.nio.file.RecursiveGlob + val Watch = sbt.nio.Watch // sbt.util type AbstractLogger = sbt.util.AbstractLogger diff --git a/sbt/src/sbt-test/watch/file-input-aggregation/project/Build.scala b/sbt/src/sbt-test/watch/file-input-aggregation/project/Build.scala index 6702e49a2..6fe14128f 100644 --- a/sbt/src/sbt-test/watch/file-input-aggregation/project/Build.scala +++ b/sbt/src/sbt-test/watch/file-input-aggregation/project/Build.scala @@ -4,7 +4,6 @@ package input.aggregation import java.nio.file.Paths import sbt.Keys._ import sbt.internal.DynamicInput -import sbt.nio.{ file => _, _ } import sbt.nio.Keys._ /** diff --git a/sbt/src/sbt-test/watch/input-parser/project/Build.scala b/sbt/src/sbt-test/watch/input-parser/project/Build.scala index 399f4acde..8f61bb413 100644 --- a/sbt/src/sbt-test/watch/input-parser/project/Build.scala +++ b/sbt/src/sbt-test/watch/input-parser/project/Build.scala @@ -6,7 +6,6 @@ import complete.Parser._ import java.io.{ PipedInputStream, PipedOutputStream } import Keys._ -import sbt.nio.Watch import sbt.nio.Keys._ object Build { diff --git a/sbt/src/sbt-test/watch/legacy-sources/build.sbt b/sbt/src/sbt-test/watch/legacy-sources/build.sbt index 643a02868..3ee3097d9 100644 --- a/sbt/src/sbt-test/watch/legacy-sources/build.sbt +++ b/sbt/src/sbt-test/watch/legacy-sources/build.sbt @@ -1,5 +1,4 @@ import sbt.legacy.sources.Build._ -import sbt.nio.Watch Global / watchSources += new sbt.internal.io.Source(baseDirectory.value, "global.txt", NothingFilter, false) diff --git a/sbt/src/sbt-test/watch/managed/build.sbt b/sbt/src/sbt-test/watch/managed/build.sbt index 7384c265e..42d425cfe 100644 --- a/sbt/src/sbt-test/watch/managed/build.sbt +++ b/sbt/src/sbt-test/watch/managed/build.sbt @@ -1,7 +1,5 @@ import java.nio.file.Files -import sbt.nio.Watch - import scala.concurrent.duration._ Compile / sourceGenerators += Def.task { diff --git a/sbt/src/sbt-test/watch/on-change/build.sbt b/sbt/src/sbt-test/watch/on-change/build.sbt index 048ca05aa..3864a2ef0 100644 --- a/sbt/src/sbt-test/watch/on-change/build.sbt +++ b/sbt/src/sbt-test/watch/on-change/build.sbt @@ -1,9 +1,6 @@ import java.nio.file._ import java.nio.file.attribute.FileTime -import sbt.nio.Keys._ -import sbt.nio._ - import scala.concurrent.duration._ watchTriggeredMessage := { (i, path: Path, c) => diff --git a/sbt/src/sbt-test/watch/overlapping/build.sbt b/sbt/src/sbt-test/watch/overlapping/build.sbt index 73eab1fe7..5b3d01204 100644 --- a/sbt/src/sbt-test/watch/overlapping/build.sbt +++ b/sbt/src/sbt-test/watch/overlapping/build.sbt @@ -1,8 +1,6 @@ import java.nio.file.Files import java.nio.file.attribute.FileTime -import sbt.nio.Watch - import scala.concurrent.duration._ val foo = taskKey[Unit]("foo.txt") From 794840836891b23600569ae2412a84696aa2ea62 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sun, 2 Jun 2019 11:48:39 -0700 Subject: [PATCH 6/9] 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. --- .../main/scala/sbt/internal/Continuous.scala | 28 ++++++++----------- main/src/main/scala/sbt/nio/Keys.scala | 19 ++++++------- main/src/main/scala/sbt/nio/Watch.scala | 21 ++++++++++---- sbt/src/sbt-test/watch/alias/build.sbt | 2 +- .../project/Build.scala | 2 +- sbt/src/sbt-test/watch/on-change/build.sbt | 2 +- .../watch/on-start-watch/changes/extra.sbt | 2 +- sbt/src/sbt-test/watch/on-start-watch/test | 6 ++-- .../sbt-test/watch/task/changes/Build.scala | 2 +- 9 files changed, 43 insertions(+), 41 deletions(-) diff --git a/main/src/main/scala/sbt/internal/Continuous.scala b/main/src/main/scala/sbt/internal/Continuous.scala index 6c26587fa..94fe88a4a 100644 --- a/main/src/main/scala/sbt/internal/Continuous.scala +++ b/main/src/main/scala/sbt/internal/Continuous.scala @@ -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) diff --git a/main/src/main/scala/sbt/nio/Keys.scala b/main/src/main/scala/sbt/nio/Keys.scala index 528cfaabd..b85271515 100644 --- a/main/src/main/scala/sbt/nio/Keys.scala +++ b/main/src/main/scala/sbt/nio/Keys.scala @@ -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]( diff --git a/main/src/main/scala/sbt/nio/Watch.scala b/main/src/main/scala/sbt/nio/Watch.scala index 745af45a1..9a073f45b 100644 --- a/main/src/main/scala/sbt/nio/Watch.scala +++ b/main/src/main/scala/sbt/nio/Watch.scala @@ -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, diff --git a/sbt/src/sbt-test/watch/alias/build.sbt b/sbt/src/sbt-test/watch/alias/build.sbt index 0fab94fd9..f1d0d6c0a 100644 --- a/sbt/src/sbt-test/watch/alias/build.sbt +++ b/sbt/src/sbt-test/watch/alias/build.sbt @@ -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") diff --git a/sbt/src/sbt-test/watch/file-input-aggregation/project/Build.scala b/sbt/src/sbt-test/watch/file-input-aggregation/project/Build.scala index 6fe14128f..32b999320 100644 --- a/sbt/src/sbt-test/watch/file-input-aggregation/project/Build.scala +++ b/sbt/src/sbt-test/watch/file-input-aggregation/project/Build.scala @@ -40,7 +40,7 @@ object Build { watchOnFileInputEvent := { (_, _) => Watch.CancelWatch }, - Compile / compile / watchOnIteration := { _ => + Compile / compile / watchOnIteration := { (_, _, _) => Watch.CancelWatch }, checkTriggers := { diff --git a/sbt/src/sbt-test/watch/on-change/build.sbt b/sbt/src/sbt-test/watch/on-change/build.sbt index 3864a2ef0..260e5d1c2 100644 --- a/sbt/src/sbt-test/watch/on-change/build.sbt +++ b/sbt/src/sbt-test/watch/on-change/build.sbt @@ -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") diff --git a/sbt/src/sbt-test/watch/on-start-watch/changes/extra.sbt b/sbt/src/sbt-test/watch/on-start-watch/changes/extra.sbt index 4c4c95a65..85c2f3c85 100644 --- a/sbt/src/sbt-test/watch/on-start-watch/changes/extra.sbt +++ b/sbt/src/sbt-test/watch/on-start-watch/changes/extra.sbt @@ -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() diff --git a/sbt/src/sbt-test/watch/on-start-watch/test b/sbt/src/sbt-test/watch/on-start-watch/test index 905322c07..f796f7a13 100644 --- a/sbt/src/sbt-test/watch/on-start-watch/test +++ b/sbt/src/sbt-test/watch/on-start-watch/test @@ -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 diff --git a/sbt/src/sbt-test/watch/task/changes/Build.scala b/sbt/src/sbt-test/watch/task/changes/Build.scala index 5478afedf..28a790802 100644 --- a/sbt/src/sbt-test/watch/task/changes/Build.scala +++ b/sbt/src/sbt-test/watch/task/changes/Build.scala @@ -24,6 +24,6 @@ object Build { IO.touch(baseDirectory.value / "foo.txt", true) Some("watching") }, - watchOnIteration := { _ => Watch.CancelWatch } + watchOnIteration := { (_, _, _) => Watch.CancelWatch } ) } From 70899e5cad1dcc94b7d3640c8508a5242b161fd8 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sun, 2 Jun 2019 16:09:51 -0700 Subject: [PATCH 7/9] Switch private[sbt] status of Reload objects The Reload exception that I added in the sbt package really wasn't intended to be public. It's only meant to be used by checkMetaBuildSources, which the users shouldn't override. I put it in the top package though because I wanted it to be next to FullReload. I also am not sure why the Reload object in Watch was private[sbt], but while writing documentation, I realized that users couldn't access it. --- main-command/src/main/scala/sbt/MainControl.scala | 2 +- main/src/main/scala/sbt/nio/Watch.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main-command/src/main/scala/sbt/MainControl.scala b/main-command/src/main/scala/sbt/MainControl.scala index 4995be5bf..4a959fd19 100644 --- a/main-command/src/main/scala/sbt/MainControl.scala +++ b/main-command/src/main/scala/sbt/MainControl.scala @@ -21,7 +21,7 @@ final case class Reboot( def arguments = argsList.toArray } -case object Reload extends Exception(null, null, false, false) +private[sbt] case object Reload extends Exception(null, null, false, false) final case class ApplicationID( groupID: String, diff --git a/main/src/main/scala/sbt/nio/Watch.scala b/main/src/main/scala/sbt/nio/Watch.scala index 9a073f45b..5fd1f11a6 100644 --- a/main/src/main/scala/sbt/nio/Watch.scala +++ b/main/src/main/scala/sbt/nio/Watch.scala @@ -246,7 +246,7 @@ object Watch { * Action that indicates that the watch should pause while the build is reloaded. This is used to * automatically reload the project when the build files (e.g. build.sbt) are changed. */ - private[sbt] case object Reload extends CancelWatch + case object Reload extends CancelWatch /** * Action that indicates that we should exit and run the provided command. From 05aab1035a95b18a78166e151aea7d22329341ab Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Mon, 3 Dec 2018 14:59:36 -0800 Subject: [PATCH 8/9] revert doc --- build.sbt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.sbt b/build.sbt index 58c54d7ec..efba64139 100644 --- a/build.sbt +++ b/build.sbt @@ -18,6 +18,7 @@ ThisBuild / Test / scalafmtOnCompile := !(Global / insideCI).value def buildLevelSettings: Seq[Setting[_]] = inThisBuild( Seq( + doc in Compile := file("/dev/null"), organization := "org.scala-sbt", description := "sbt is an interactive build tool", bintrayOrganization := Some("sbt"), @@ -45,6 +46,7 @@ def buildLevelSettings: Seq[Setting[_]] = homepage := Some(url("https://github.com/sbt/sbt")), scmInfo := Some(ScmInfo(url("https://github.com/sbt/sbt"), "git@github.com:sbt/sbt.git")), resolvers += Resolver.mavenLocal, + scalafmtOnCompile := false, ) ) @@ -58,6 +60,7 @@ def commonSettings: Seq[Setting[_]] = Def.settings( |""".stripMargin ) ), + doc in Compile := file("/dev/null"), scalaVersion := baseScalaVersion, componentID := None, resolvers += Resolver.typesafeIvyRepo("releases"), @@ -391,6 +394,7 @@ lazy val scriptedSbtReduxProj = (project in file("scripted-sbt-redux")) .dependsOn(commandProj) .settings( baseSettings, + doc in Compile := file("/dev/null"), name := "Scripted sbt Redux", libraryDependencies ++= Seq(launcherInterface % "provided"), resourceGenerators in Compile += Def task { @@ -638,6 +642,7 @@ lazy val mainProj = (project in file("main")) ) .settings( testedBaseSettings, + doc in Compile := file("/dev/null"), name := "Main", checkPluginCross := { val sv = scalaVersion.value From 1ab666daf4d6faf456922a2920126c8ff607287c Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Mon, 3 Jun 2019 17:41:07 -0700 Subject: [PATCH 9/9] Change signature of pre watch methods It makes the method parameters more clear if we pass in the ProjectRef rather than the project name. We also don't lose information. --- main/src/main/scala/sbt/internal/Continuous.scala | 7 ++++--- .../main/scala/sbt/internal/DeprecatedContinuous.scala | 4 ++-- main/src/main/scala/sbt/nio/Keys.scala | 6 +++--- main/src/main/scala/sbt/nio/Watch.scala | 8 ++++---- sbt/src/sbt-test/watch/input-parser/project/Build.scala | 4 ++-- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/main/src/main/scala/sbt/internal/Continuous.scala b/main/src/main/scala/sbt/internal/Continuous.scala index 94fe88a4a..f212e5b64 100644 --- a/main/src/main/scala/sbt/internal/Continuous.scala +++ b/main/src/main/scala/sbt/internal/Continuous.scala @@ -466,7 +466,7 @@ private[sbt] object Continuous extends DeprecatedContinuous { )( implicit extracted: Extracted ): Callbacks = { - val project = extracted.currentRef.project + val project = extracted.currentRef val logger = setLevel(rawLogger, configs.map(_.watchSettings.logLevel).min, state) val beforeCommand = () => configs.foreach(_.watchSettings.beforeCommand()) val onStart: () => Watch.Action = getOnStart(project, commands, configs, rawLogger, count) @@ -497,7 +497,7 @@ private[sbt] object Continuous extends DeprecatedContinuous { } private def getOnStart( - project: String, + project: ProjectRef, commands: Seq[String], configs: Seq[Config], logger: Logger, @@ -900,7 +900,8 @@ private[sbt] object Continuous extends DeprecatedContinuous { val beforeCommand: () => Unit = key.get(watchBeforeCommand).getOrElse(() => {}) val onFileInputEvent: WatchOnEvent = key.get(watchOnFileInputEvent).getOrElse(Watch.trigger) - val onIteration: Option[(Int, String, Seq[String]) => Watch.Action] = key.get(watchOnIteration) + val onIteration: Option[(Int, ProjectRef, Seq[String]) => Watch.Action] = + key.get(watchOnIteration) val onTermination: Option[(Watch.Action, String, Int, State) => State] = key.get(watchOnTermination) val startMessage: StartMessage = getStartMessage(key) diff --git a/main/src/main/scala/sbt/internal/DeprecatedContinuous.scala b/main/src/main/scala/sbt/internal/DeprecatedContinuous.scala index 1db908859..2bffd0801 100644 --- a/main/src/main/scala/sbt/internal/DeprecatedContinuous.scala +++ b/main/src/main/scala/sbt/internal/DeprecatedContinuous.scala @@ -10,14 +10,14 @@ package sbt.internal import java.nio.file.Path import java.util.concurrent.atomic.AtomicReference -import sbt.{ State, Watched } +import sbt.{ ProjectRef, State, Watched } import sbt.internal.io.{ EventMonitor, Source, WatchState => WS } import sbt.internal.util.AttributeKey import sbt.nio.file.Glob private[internal] trait DeprecatedContinuous { protected type StartMessage = - Option[Either[WS => String, (Int, String, Seq[String]) => Option[String]]] + Option[Either[WS => String, (Int, ProjectRef, Seq[String]) => Option[String]]] protected type TriggerMessage = Either[WS => String, (Int, Path, Seq[String]) => Option[String]] protected type DeprecatedWatchState = WS protected val deprecatedWatchingMessage = sbt.Keys.watchingMessage diff --git a/main/src/main/scala/sbt/nio/Keys.scala b/main/src/main/scala/sbt/nio/Keys.scala index b85271515..a0c452777 100644 --- a/main/src/main/scala/sbt/nio/Keys.scala +++ b/main/src/main/scala/sbt/nio/Keys.scala @@ -18,7 +18,7 @@ import sbt.internal.nio.FileTreeRepository import sbt.internal.util.AttributeKey import sbt.internal.util.complete.Parser import sbt.nio.file.{ ChangedFiles, FileAttributes, FileTreeView, Glob } -import sbt.{ Def, InputKey, State, StateTransform } +import sbt.{ Def, InputKey, ProjectRef, State, StateTransform } import scala.concurrent.duration.FiniteDuration @@ -88,7 +88,7 @@ object Keys { 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, String, Seq[String]) => Watch.Action]( + val watchOnIteration = settingKey[(Int, ProjectRef, 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]( @@ -97,7 +97,7 @@ object Keys { val watchPersistFileStamps = settingKey[Boolean]( "Toggles whether or not the continuous build will reuse the file stamps computed in previous runs. Setting this to true decrease watch startup latency but could cause inconsistent results if many source files are concurrently modified." ).withRank(DSetting) - val watchStartMessage = settingKey[(Int, String, Seq[String]) => Option[String]]( + val watchStartMessage = settingKey[(Int, ProjectRef, 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. diff --git a/main/src/main/scala/sbt/nio/Watch.scala b/main/src/main/scala/sbt/nio/Watch.scala index 5fd1f11a6..82bae33f8 100644 --- a/main/src/main/scala/sbt/nio/Watch.scala +++ b/main/src/main/scala/sbt/nio/Watch.scala @@ -401,19 +401,19 @@ object Watch { ) s"Options:\n${opts.mkString(" ", "\n ", "")}" } - private def waitMessage(project: String, commands: Seq[String]): String = { + private def waitMessage(project: ProjectRef, 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" + s"Project: ${project.project}\nCommand$plural: $cmds\n$options" } /** * A function that prints out the current iteration count and gives instructions for exiting * or triggering the build. */ - val defaultStartWatch: (Int, String, Seq[String]) => Option[String] = { - (count: Int, project: String, commands: Seq[String]) => + val defaultStartWatch: (Int, ProjectRef, Seq[String]) => Option[String] = { + (count: Int, project: ProjectRef, commands: Seq[String]) => Some(s"$count. ${waitMessage(project, commands)}") }.label("Watched.defaultStartWatch") diff --git a/sbt/src/sbt-test/watch/input-parser/project/Build.scala b/sbt/src/sbt-test/watch/input-parser/project/Build.scala index 8f61bb413..eb30bc8d8 100644 --- a/sbt/src/sbt-test/watch/input-parser/project/Build.scala +++ b/sbt/src/sbt-test/watch/input-parser/project/Build.scala @@ -25,12 +25,12 @@ object Build { // 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. val helloOrByeParser: Parser[Watch.Action] = byeParser | helloParser - val alternativeStartMessage: (Int, String, Seq[String]) => Option[String] = { (_, _, _) => + val alternativeStartMessage: (Int, ProjectRef, Seq[String]) => Option[String] = { (_, _, _) => outputStream.write("xybyexyblahxyhelloxy".getBytes) outputStream.flush() Some("alternative start message") } - val otherAlternativeStartMessage: (Int, String, Seq[String]) => Option[String] = { (_, _, _) => + val otherAlternativeStartMessage: (Int, ProjectRef, Seq[String]) => Option[String] = { (_, _, _) => outputStream.write("xyhellobyexyblahx".getBytes) outputStream.flush() Some("other alternative start message")