From a5cefd45be77c4f7eb257a751d26c7ea66b9fdc9 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Wed, 17 Apr 2019 18:43:08 -0700 Subject: [PATCH] Clean up nio apis This commit refactors things so that the nio apis are located primarily in the nio package. Because the nio keys are a first class sbt feature, I had to add import sbt.nio._ and sbt.nio.Keys._ to the autoimports in BuildUtil.scala --- main/src/main/scala/sbt/Defaults.scala | 7 +- main/src/main/scala/sbt/EvaluateTask.scala | 28 +---- main/src/main/scala/sbt/Keys.scala | 32 +---- .../main/scala/sbt/internal/BuildUtil.scala | 2 +- .../scala/sbt/internal/CommandExchange.scala | 2 +- .../main/scala/sbt/internal/Continuous.scala | 63 +++++----- .../main/scala/sbt/internal/InputGraph.scala | 4 - main/src/main/scala/sbt/internal/Load.scala | 2 +- main/src/main/scala/sbt/nio/Keys.scala | 78 +++++++++++- main/src/main/scala/sbt/nio/Settings.scala | 45 +++++-- main/src/main/scala/sbt/{ => nio}/Watch.scala | 3 +- main/src/test/scala/sbt/WatchSpec.scala | 3 +- sbt/src/main/scala/sbt/Import.scala | 10 ++ sbt/src/sbt-test/nio/diff/build.sbt | 1 - sbt/src/sbt-test/nio/glob-dsl/build.sbt | 6 - .../intraproject-inputs/project/Build.scala | 1 - .../sbt-test/watch/command-parser/build.sbt | 2 +- .../watch/custom-config/project/Build.scala | 1 + .../watch/dynamic-inputs/project/Build.scala | 1 + .../project/Build.scala | 111 ++++++++++-------- .../watch/input-parser/project/Build.scala | 2 + .../sbt-test/watch/legacy-sources/build.sbt | 1 + .../watch/on-start-watch/changes/extra.sbt | 2 +- .../sbt-test/watch/on-start-watch/extra.sbt | 2 +- sbt/src/sbt-test/watch/on-start-watch/test | 6 +- .../watch/on-termination/project/Build.scala | 2 + .../sbt-test/watch/task/changes/Build.scala | 2 + .../sbt-test/watch/task/project/Build.scala | 2 + 28 files changed, 246 insertions(+), 175 deletions(-) rename main/src/main/scala/sbt/{ => nio}/Watch.scala (99%) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 6e53ae8de..df613c839 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -64,6 +64,7 @@ import sbt.librarymanagement.CrossVersion.{ binarySbtVersion, binaryScalaVersion import sbt.librarymanagement._ import sbt.librarymanagement.ivy._ import sbt.librarymanagement.syntax._ +import sbt.nio.Watch import sbt.nio.Keys._ import sbt.nio.file.FileTreeView import sbt.nio.file.syntax._ @@ -421,7 +422,7 @@ object Defaults extends BuildCommon { unmanagedSources := (unmanagedSources / fileStamps).value.map(_._1.toFile), managedSourceDirectories := Seq(sourceManaged.value), managedSources := { - val stamper = sbt.nio.Keys.stamper.value + val stamper = sbt.nio.Keys.pathToFileStamp.value val res = generate(sourceGenerators).value res.foreach(f => stamper(f.toPath)) res @@ -654,7 +655,7 @@ object Defaults extends BuildCommon { watchTransitiveSources := watchTransitiveSourcesTask.value, watch := watchSetting.value, fileOutputs += target.value ** AllPassFilter, - TransitiveDynamicInputs.transitiveDynamicInputs := InputGraph.task.value, + transitiveDynamicInputs := InputGraph.task.value, ) def generate(generators: SettingKey[Seq[Task[Seq[File]]]]): Initialize[Task[Seq[File]]] = @@ -2097,7 +2098,7 @@ object Classpaths { shellPrompt := shellPromptFromState, dynamicDependency := { (): Unit }, transitiveClasspathDependency := { (): Unit }, - TransitiveDynamicInputs.transitiveDynamicInputs :== Nil, + transitiveDynamicInputs :== Nil, ) ) diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index 987fbbb4e..cf233a17d 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -16,9 +16,8 @@ import sbt.Project.richInitializeTask import sbt.Scope.Global import sbt.internal.Aggregation.KeyValue import sbt.internal.TaskName._ -import sbt.internal.TransitiveDynamicInputs._ import sbt.internal.util._ -import sbt.internal.{ BuildStructure, GCUtil, Load, TaskProgress, TaskTimings, TaskTraceEvent, _ } +import sbt.internal._ import sbt.librarymanagement.{ Resolver, UpdateReport } import sbt.std.Transform.DummyTaskMap import sbt.util.{ Logger, Show } @@ -572,31 +571,6 @@ object EvaluateTask { stream }).value }) - } else if (scoped.key == transitiveDynamicInputs.key) { - scoped.scope.task.toOption.toSeq.map { key => - val updatedKey = ScopedKey(scoped.scope.copy(task = Zero), key) - transitiveDynamicInputs in scoped.scope := InputGraph.task(updatedKey).value - } - } else if (scoped.key == dynamicDependency.key) { - (dynamicDependency in scoped.scope := { - () - }) :: Nil - } else if (scoped.key == transitiveClasspathDependency.key) { - (transitiveClasspathDependency in scoped.scope := { - () - }) :: Nil - } else if (scoped.key == sbt.nio.Keys.allFiles.key) { - sbt.nio.Settings.allFiles(scoped) :: Nil - } else if (scoped.key == sbt.nio.Keys.allPaths.key) { - sbt.nio.Settings.allPaths(scoped) :: Nil - } else if (scoped.key == sbt.nio.Keys.changedFiles.key) { - sbt.nio.Settings.changedFiles(scoped) - } else if (scoped.key == sbt.nio.Keys.modifiedFiles.key) { - sbt.nio.Settings.modifiedFiles(scoped) - } else if (scoped.key == sbt.nio.Keys.removedFiles.key) { - sbt.nio.Settings.removedFiles(scoped) :: Nil - } else if (scoped.key == sbt.nio.Keys.stamper.key) { - sbt.nio.Settings.stamper(scoped) :: Nil } else { Nil } diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index bc96447e6..fa7965170 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -7,9 +7,8 @@ package sbt -import java.io.{ File, InputStream } +import java.io.File import java.net.URL -import java.nio.file.{ Path => NioPath } import org.apache.ivy.core.module.descriptor.ModuleDescriptor import org.apache.ivy.core.module.id.ModuleRevisionId @@ -21,16 +20,14 @@ import sbt.internal._ import sbt.internal.inc.ScalaInstance import sbt.internal.io.WatchState import sbt.internal.librarymanagement.{ CompatibilityWarningOptions, IvySbt } -import sbt.internal.nio.FileTreeRepository import sbt.internal.server.ServerHandler -import sbt.internal.util.complete.Parser import sbt.internal.util.{ AttributeKey, SourcePosition } import sbt.io._ import sbt.librarymanagement.Configurations.CompilerPlugin import sbt.librarymanagement.LibraryManagementCodec._ import sbt.librarymanagement._ import sbt.librarymanagement.ivy.{ Credentials, IvyConfiguration, IvyPaths, UpdateOptions } -import sbt.nio.file.{ FileAttributes, Glob } +import sbt.nio.file.Glob import sbt.testing.Framework import sbt.util.{ Level, Logger } import xsbti.compile._ @@ -98,24 +95,7 @@ object Keys { val suppressSbtShellNotification = settingKey[Boolean]("""True to suppress the "Executing in batch mode.." message.""").withRank(CSetting) val pollInterval = settingKey[FiniteDuration]("Interval between checks for modified sources by the continuous execution command.").withRank(BMinusSetting) val watchAntiEntropy = settingKey[FiniteDuration]("Duration for which the watch EventMonitor will ignore events for a file after that file has triggered a build.").withRank(BMinusSetting) - val watchAntiEntropyRetentionPeriod = settingKey[FiniteDuration]("Wall clock Duration for which a FileEventMonitor will store anti-entropy events. This prevents spurious triggers when a task takes a long time to run. Higher values will consume more memory but make spurious triggers less likely.").withRank(BMinusSetting) - val watchDeletionQuarantinePeriod = settingKey[FiniteDuration]("Period for which deletion events will be quarantined. This is to prevent spurious builds when a file is updated with a rename which manifests as a file deletion followed by a file creation. The higher this value is set, the longer the delay will be between a file deletion and a build trigger but the less likely it is for a spurious trigger.").withRank(DSetting) - val watchLogLevel = settingKey[sbt.util.Level.Value]("Transform the default logger in continuous builds.").withRank(DSetting) - 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 watchForceTriggerOnAnyChange = settingKey[Boolean]("Force the watch process to rerun the current task(s) if any relevant source change is detected regardless of whether or not the underlying file has actually changed.").withRank(DSetting) - val watchInputStream = taskKey[InputStream]("The input stream to read for user input events. This will usually be System.in").withRank(DSetting) - 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 watchOnFileInputEvent = settingKey[(Int, Watch.Event) => Watch.Action]("Callback to invoke if an event is triggered in a continuous build by one of the transitive inputs. This is only invoked if watchOnEvent is not explicitly set.").withRank(DSetting) - val watchOnTermination = settingKey[(Watch.Action, String, Int, State) => State]("Transforms the state upon completion of a watch. The String argument is the command that was run during the watch. The Int parameter specifies how many times the command was run during the watch.").withRank(DSetting) - val watchOnIteration = settingKey[Int => Watch.Action]("Function that is invoked before waiting for file system events or user input events.").withRank(DSetting) val watchService = settingKey[() => WatchService]("Service to use to monitor file system changes.").withRank(BMinusSetting).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. - 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 watchTriggeredMessage = settingKey[(Int, NioPath, 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("This is no longer used for continuous execution", "1.3.0") @@ -498,14 +478,6 @@ object Keys { @deprecated("No longer used", "1.3.0") private[sbt] val executeProgress = settingKey[State => TaskProgress]("Experimental task execution listener.").withRank(DTask) - private[sbt] val globalFileTreeRepository = AttributeKey[FileTreeRepository[FileAttributes]]( - "global-file-tree-repository", - "Provides a view into the file system that may or may not cache the tree in memory", - 1000 - ) - private[sbt] val dynamicDependency = settingKey[Unit]("Leaves a breadcrumb that the scoped task is evaluated inside of a dynamic task") - private[sbt] val transitiveClasspathDependency = settingKey[Unit]("Leaves a breadcrumb that the scoped task has transitive classpath dependencies") - val stateStreams = AttributeKey[Streams]("stateStreams", "Streams manager, which provides streams for different contexts. Setting this on State will override the default Streams implementation.") val resolvedScoped = Def.resolvedScoped val pluginData = taskKey[PluginData]("Information from the plugin build needed in the main build definition.").withRank(DTask) diff --git a/main/src/main/scala/sbt/internal/BuildUtil.scala b/main/src/main/scala/sbt/internal/BuildUtil.scala index 1401816d2..eb424a682 100644 --- a/main/src/main/scala/sbt/internal/BuildUtil.scala +++ b/main/src/main/scala/sbt/internal/BuildUtil.scala @@ -86,7 +86,7 @@ object BuildUtil { } def baseImports: Seq[String] = - "import _root_.scala.xml.{TopScope=>$scope}" :: "import _root_.sbt._" :: "import _root_.sbt.Keys._" :: Nil + "import _root_.scala.xml.{TopScope=>$scope}" :: "import _root_.sbt._" :: "import _root_.sbt.Keys._" :: "import _root_.sbt.nio.Keys._" :: Nil def getImports(unit: BuildUnit): Seq[String] = unit.plugins.detected.imports ++ unit.definitions.dslDefinitions.imports diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index be1a15214..96b4ae4a1 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -14,7 +14,7 @@ import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.atomic._ import sbt.BasicKeys._ -import sbt.Watch.NullLogger +import sbt.nio.Watch.NullLogger import sbt.internal.langserver.{ LogMessageParams, MessageType } import sbt.internal.server._ import sbt.internal.util.codec.JValueFormats diff --git a/main/src/main/scala/sbt/internal/Continuous.scala b/main/src/main/scala/sbt/internal/Continuous.scala index 0d6855d7b..03f966250 100644 --- a/main/src/main/scala/sbt/internal/Continuous.scala +++ b/main/src/main/scala/sbt/internal/Continuous.scala @@ -19,17 +19,18 @@ import sbt.BasicCommandStrings.{ } import sbt.BasicCommands.otherCommandParser import sbt.Def._ +import sbt.Keys._ import sbt.Scope.Global -import sbt.Watch.{ Creation, Deletion, Update } import sbt.internal.LabeledFunctions._ import sbt.internal.io.WatchState import sbt.internal.nio._ import sbt.internal.util.complete.Parser._ import sbt.internal.util.complete.{ Parser, Parsers } import sbt.internal.util.{ AttributeKey, JLine, Util } -import sbt.nio.Keys.fileInputs +import sbt.nio.Keys.{ fileInputs, _ } +import sbt.nio.Watch.{ Creation, Deletion, Update } import sbt.nio.file.FileAttributes -import sbt.nio.{ FileStamp, FileStamper } +import sbt.nio.{ FileStamp, FileStamper, Watch } import sbt.util.{ Level, _ } import scala.annotation.tailrec @@ -103,8 +104,8 @@ private[sbt] object Continuous extends DeprecatedContinuous { */ private[sbt] def continuous: Command = Command(ContinuousExecutePrefix, continuousBriefHelp, continuousDetail)(continuousParser) { - case (state, (initialCount, command)) => - runToTermination(state, command, initialCount, isCommand = true) + case (s, (initialCount, command)) => + runToTermination(s, command, initialCount, isCommand = true) } /** @@ -117,7 +118,7 @@ private[sbt] object Continuous extends DeprecatedContinuous { Def.inputTask { val (initialCount, command) = continuousParser.parsed new StateTransform( - runToTermination(Keys.state.value, command, initialCount, isCommand = false) + runToTermination(state.value, command, initialCount, isCommand = false) ) } @@ -165,7 +166,7 @@ private[sbt] object Continuous extends DeprecatedContinuous { // Extract all of the globs that we will monitor during the continuous build. val inputs = { - val configs = scopedKey.get(Keys.internalDependencyConfigurations).getOrElse(Nil) + val configs = scopedKey.get(internalDependencyConfigurations).getOrElse(Nil) val args = new InputGraph.Arguments(scopedKey, extracted, compiledMap, logger, configs, state) InputGraph.transitiveDynamicInputs(args) } @@ -191,7 +192,7 @@ private[sbt] object Continuous extends DeprecatedContinuous { lazy val exception = new IllegalStateException("Tried to access FileTreeRepository for uninitialized state") state - .get(Keys.globalFileTreeRepository) + .get(globalFileTreeRepository) .getOrElse(throw exception) } @@ -279,18 +280,18 @@ private[sbt] object Continuous extends DeprecatedContinuous { ): State = withCharBufferedStdIn { in => implicit val extracted: Extracted = Project.extract(state) val repo = if ("polling" == System.getProperty("sbt.watch.mode")) { - val service = - new PollingWatchService(extracted.getOpt(Keys.pollInterval).getOrElse(500.millis)) - FileTreeRepository.legacy((_: Any) => {}, service) + val service = new PollingWatchService(extracted.getOpt(pollInterval).getOrElse(500.millis)) + FileTreeRepository + .legacy((_: Any) => {}, service) } else { FileTreeRepository.default } try { val stateWithRepo = state - .put(Keys.globalFileTreeRepository, repo) - .put(sbt.nio.Keys.persistentFileAttributeMap, new sbt.nio.Keys.FileAttributeMap) + .put(globalFileTreeRepository, repo) + .put(persistentFileAttributeMap, new sbt.nio.Keys.FileAttributeMap) setup(stateWithRepo, command) { (commands, s, valid, invalid) => - EvaluateTask.withStreams(extracted.structure, s)(_.use(Keys.streams in Global) { streams => + EvaluateTask.withStreams(extracted.structure, s)(_.use(streams in Global) { streams => implicit val logger: Logger = streams.log if (invalid.isEmpty) { val currentCount = new AtomicInteger(count) @@ -459,10 +460,10 @@ private[sbt] object Continuous extends DeprecatedContinuous { count: AtomicInteger, commands: Seq[String] )(implicit extracted: Extracted): (() => Option[(Watch.Event, Watch.Action)], () => Unit) = { - val attributeMap = state.get(sbt.nio.Keys.persistentFileAttributeMap).get + val attributeMap = state.get(persistentFileAttributeMap).get val trackMetaBuild = configs.forall(_.watchSettings.trackMetaBuild) val buildGlobs = - if (trackMetaBuild) extracted.getOpt(fileInputs in Keys.settingsData).getOrElse(Nil) + if (trackMetaBuild) extracted.getOpt(fileInputs in settingsData).getOrElse(Nil) else Nil val retentionPeriod = configs.map(_.watchSettings.antiEntropyRetentionPeriod).max @@ -765,26 +766,26 @@ private[sbt] object Continuous extends DeprecatedContinuous { implicit extracted: Extracted ) { val antiEntropy: FiniteDuration = - key.get(Keys.watchAntiEntropy).getOrElse(Watch.defaultAntiEntropy) + key.get(watchAntiEntropy).getOrElse(Watch.defaultAntiEntropy) val antiEntropyRetentionPeriod: FiniteDuration = key - .get(Keys.watchAntiEntropyRetentionPeriod) + .get(watchAntiEntropyRetentionPeriod) .getOrElse(Watch.defaultAntiEntropyRetentionPeriod) val deletionQuarantinePeriod: FiniteDuration = - key.get(Keys.watchDeletionQuarantinePeriod).getOrElse(Watch.defaultDeletionQuarantinePeriod) - val inputHandler: Option[InputStream => Watch.Action] = key.get(Keys.watchInputHandler) + key.get(watchDeletionQuarantinePeriod).getOrElse(Watch.defaultDeletionQuarantinePeriod) + val inputHandler: Option[InputStream => Watch.Action] = key.get(watchInputHandler) val inputParser: Parser[Watch.Action] = - key.get(Keys.watchInputParser).getOrElse(Watch.defaultInputParser) - val logLevel: Level.Value = key.get(Keys.watchLogLevel).getOrElse(Level.Info) - val onEnter: () => Unit = key.get(Keys.watchOnEnter).getOrElse(() => {}) - val onExit: () => Unit = key.get(Keys.watchOnExit).getOrElse(() => {}) + 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 onFileInputEvent: WatchOnEvent = - key.get(Keys.watchOnFileInputEvent).getOrElse(Watch.trigger) - val onIteration: Option[Int => Watch.Action] = key.get(Keys.watchOnIteration) + key.get(watchOnFileInputEvent).getOrElse(Watch.trigger) + val onIteration: Option[Int => Watch.Action] = key.get(watchOnIteration) val onTermination: Option[(Watch.Action, String, Int, State) => State] = - key.get(Keys.watchOnTermination) + key.get(watchOnTermination) val startMessage: StartMessage = getStartMessage(key) - val trackMetaBuild: Boolean = key.get(Keys.watchTrackMetaBuild).getOrElse(true) + val trackMetaBuild: Boolean = key.get(watchTrackMetaBuild).getOrElse(true) val triggerMessage: TriggerMessage = getTriggerMessage(key) // Unlike the rest of the settings, InputStream is a TaskKey which means that if it is set, @@ -792,7 +793,7 @@ private[sbt] object Continuous extends DeprecatedContinuous { // logical that users may want to use a different InputStream on each task invocation. The // alternative would be SettingKey[() => InputStream], but that doesn't feel right because // one might want the InputStream to depend on other tasks. - val inputStream: Option[TaskKey[InputStream]] = key.get(Keys.watchInputStream) + val inputStream: Option[TaskKey[InputStream]] = key.get(watchInputStream) } /** @@ -813,14 +814,14 @@ private[sbt] object Continuous extends DeprecatedContinuous { def arguments(logger: Logger): Arguments = new Arguments(logger, inputs()) } private def getStartMessage(key: ScopedKey[_])(implicit e: Extracted): StartMessage = Some { - lazy val default = key.get(Keys.watchStartMessage).getOrElse(Watch.defaultStartWatch) + lazy val default = key.get(watchStartMessage).getOrElse(Watch.defaultStartWatch) key.get(deprecatedWatchingMessage).map(Left(_)).getOrElse(Right(default)) } private def getTriggerMessage( key: ScopedKey[_] )(implicit e: Extracted): TriggerMessage = { lazy val default = - key.get(Keys.watchTriggeredMessage).getOrElse(Watch.defaultOnTriggerMessage) + key.get(watchTriggeredMessage).getOrElse(Watch.defaultOnTriggerMessage) key.get(deprecatedWatchingMessage).map(Left(_)).getOrElse(Right(default)) } diff --git a/main/src/main/scala/sbt/internal/InputGraph.scala b/main/src/main/scala/sbt/internal/InputGraph.scala index e4996c8b8..ac1a09ffa 100644 --- a/main/src/main/scala/sbt/internal/InputGraph.scala +++ b/main/src/main/scala/sbt/internal/InputGraph.scala @@ -21,10 +21,6 @@ import sbt.nio.Keys._ import scala.annotation.tailrec -private[sbt] object TransitiveDynamicInputs { - val transitiveDynamicInputs = - Def.taskKey[Seq[DynamicInput]]("The transitive inputs and triggers for a key") -} private[sbt] object InputGraph { private implicit class SourceOps(val source: Source) { def toGlob: Glob = { diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index ab3520449..69f81f7b8 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -107,7 +107,7 @@ private[sbt] object Load { compilers, evalPluginDef, delegates, - EvaluateTask.injectStreams, + s => EvaluateTask.injectStreams(s) ++ Settings.inject(s), pluginMgmt, inject, None, diff --git a/main/src/main/scala/sbt/nio/Keys.scala b/main/src/main/scala/sbt/nio/Keys.scala index 16d2a1ce2..782088d34 100644 --- a/main/src/main/scala/sbt/nio/Keys.scala +++ b/main/src/main/scala/sbt/nio/Keys.scala @@ -7,11 +7,19 @@ package sbt.nio +import java.io.InputStream import java.nio.file.Path import sbt.BuildSyntax.{ settingKey, taskKey } +import sbt.KeyRanks.{ BMinusSetting, DSetting } +import sbt.internal.DynamicInput +import sbt.internal.nio.FileTreeRepository import sbt.internal.util.AttributeKey +import sbt.internal.util.complete.Parser import sbt.nio.file.{ FileAttributes, FileTreeView, Glob } +import sbt.{ Def, InputKey, State, StateTransform } + +import scala.concurrent.duration.FiniteDuration object Keys { val allPaths = taskKey[Seq[Path]]( @@ -49,7 +57,75 @@ object Keys { private[sbt] val fileAttributeMap = taskKey[FileAttributeMap]( "Map of file stamps that may be cleared between task evaluation runs." ) - private[sbt] val stamper = taskKey[Path => FileStamp]( + private[sbt] val pathToFileStamp = taskKey[Path => FileStamp]( "A function that computes a file stamp for a path. It may have the side effect of updating a cache." ) + + val watchAntiEntropyRetentionPeriod = settingKey[FiniteDuration]( + "Wall clock Duration for which a FileEventMonitor will store anti-entropy events. This prevents spurious triggers when a task takes a long time to run. Higher values will consume more memory but make spurious triggers less likely." + ).withRank(BMinusSetting) + val watchDeletionQuarantinePeriod = settingKey[FiniteDuration]( + "Period for which deletion events will be quarantined. This is to prevent spurious builds when a file is updated with a rename which manifests as a file deletion followed by a file creation. The higher this value is set, the longer the delay will be between a file deletion and a build trigger but the less likely it is for a spurious trigger." + ).withRank(DSetting) + private[this] val forceTriggerOnAnyChangeMessage = + "Force the watch process to rerun the current task(s) if any relevant source change is " + + "detected regardless of whether or not the underlying file has actually changed." + + 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]( + "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]( + "The input stream to read for user input events. This will usually be System.in" + ).withRank(DSetting) + 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 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]( + "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]( + "Transforms the state upon completion of a watch. The String argument is the command that was run during the watch. The Int parameter specifies how many times the command was run during the watch." + ).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. + 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 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) + + private[sbt] val globalFileTreeRepository = AttributeKey[FileTreeRepository[FileAttributes]]( + "global-file-tree-repository", + "Provides a view into the file system that may or may not cache the tree in memory", + 1000 + ) + private[sbt] val dynamicDependency = settingKey[Unit]( + "Leaves a breadcrumb that the scoped task is evaluated inside of a dynamic task" + ) + private[sbt] val transitiveClasspathDependency = settingKey[Unit]( + "Leaves a breadcrumb that the scoped task has transitive classpath dependencies" + ) + private[sbt] val transitiveDynamicInputs = + taskKey[Seq[DynamicInput]]("The transitive inputs and triggers for a key") } diff --git a/main/src/main/scala/sbt/nio/Settings.scala b/main/src/main/scala/sbt/nio/Settings.scala index ef2fa2665..bdcc182cc 100644 --- a/main/src/main/scala/sbt/nio/Settings.scala +++ b/main/src/main/scala/sbt/nio/Settings.scala @@ -10,13 +10,38 @@ package nio import java.nio.file.{ Files, Path } -import sbt.Keys._ -import sbt.internal.{ Continuous, DynamicInput } +import sbt.internal.{ Continuous, DynamicInput, InputGraph } import sbt.nio.FileStamp.{ fileStampJsonFormatter, pathJsonFormatter } import sbt.nio.FileStamper.{ Hash, LastModified } import sbt.nio.Keys._ private[sbt] object Settings { + private[sbt] val inject: Def.ScopedKey[_] => Seq[Def.Setting[_]] = scopedKey => { + if (scopedKey.key == transitiveDynamicInputs.key) { + scopedKey.scope.task.toOption.toSeq.map { key => + val updatedKey = Def.ScopedKey(scopedKey.scope.copy(task = Zero), key) + transitiveDynamicInputs in scopedKey.scope := InputGraph.task(updatedKey).value + } + } else if (scopedKey.key == dynamicDependency.key) { + (dynamicDependency in scopedKey.scope := { () }) :: Nil + } else if (scopedKey.key == transitiveClasspathDependency.key) { + (transitiveClasspathDependency in scopedKey.scope := { () }) :: Nil + } else if (scopedKey.key == allFiles.key) { + allFilesImpl(scopedKey) :: Nil + } else if (scopedKey.key == allPaths.key) { + allPathsImpl(scopedKey) :: Nil + } else if (scopedKey.key == changedFiles.key) { + changedFilesImpl(scopedKey) + } else if (scopedKey.key == modifiedFiles.key) { + modifiedFilesImpl(scopedKey) + } else if (scopedKey.key == removedFiles.key) { + removedFilesImpl(scopedKey) :: Nil + } else if (scopedKey.key == pathToFileStamp.key) { + stamper(scopedKey) :: Nil + } else { + Nil + } + } /** * This adds the [[sbt.Keys.taskDefinitionKey]] to the work for each [[Task]]. Without @@ -58,7 +83,7 @@ private[sbt] object Settings { * @param scopedKey the key whose file inputs we are seeking * @return a task definition that retrieves the input files and their attributes scoped to a particular task. */ - private[sbt] def allPaths(scopedKey: Def.ScopedKey[_]): Def.Setting[_] = + private[this] def allPathsImpl(scopedKey: Def.ScopedKey[_]): Def.Setting[_] = addTaskDefinition(Keys.allPaths in scopedKey.scope := { (Keys.allPathsAndAttributes in scopedKey.scope).value.map(_._1) }) @@ -70,7 +95,7 @@ private[sbt] object Settings { * @param scopedKey the key whose file inputs we are seeking * @return a task definition that retrieves all of the input paths scoped to the input key. */ - private[sbt] def allFiles(scopedKey: Def.ScopedKey[_]): Def.Setting[_] = + private[this] def allFilesImpl(scopedKey: Def.ScopedKey[_]): Def.Setting[_] = addTaskDefinition(Keys.allFiles in scopedKey.scope := { (Keys.allPathsAndAttributes in scopedKey.scope).value.collect { case (p, a) if a.isRegularFile && !Files.isHidden(p) => p @@ -86,7 +111,7 @@ private[sbt] object Settings { * @param scopedKey the key whose fileInputs we are seeking * @return a task definition that retrieves the changed input files scoped to the key. */ - private[sbt] def changedFiles(scopedKey: Def.ScopedKey[_]): Seq[Def.Setting[_]] = + private[this] def changedFilesImpl(scopedKey: Def.ScopedKey[_]): Seq[Def.Setting[_]] = addTaskDefinition(Keys.changedFiles in scopedKey.scope := { val current = (Keys.fileStamps in scopedKey.scope).value (Keys.fileStamps in scopedKey.scope).previous match { @@ -110,7 +135,7 @@ private[sbt] object Settings { */ private[sbt] def fileStamps(scopedKey: Def.ScopedKey[_]): Def.Setting[_] = addTaskDefinition(Keys.fileStamps in scopedKey.scope := { - val stamper = (Keys.stamper in scopedKey.scope).value + val stamper = (Keys.pathToFileStamp in scopedKey.scope).value (Keys.allPathsAndAttributes in scopedKey.scope).value.collect { case (p, a) if a.isRegularFile && !Files.isHidden(p) => p -> stamper(p) } @@ -125,7 +150,7 @@ private[sbt] object Settings { * @param scopedKey the key whose modified files we are seeking * @return a task definition that retrieves the changed input files scoped to the key. */ - private[sbt] def modifiedFiles(scopedKey: Def.ScopedKey[_]): Seq[Def.Setting[_]] = + private[this] def modifiedFilesImpl(scopedKey: Def.ScopedKey[_]): Seq[Def.Setting[_]] = (Keys.modifiedFiles in scopedKey.scope := { val current = (Keys.fileStamps in scopedKey.scope).value (Keys.fileStamps in scopedKey.scope).previous match { @@ -151,7 +176,7 @@ private[sbt] object Settings { * @param scopedKey the key whose removed files we are seeking * @return a task definition that retrieves the changed input files scoped to the key. */ - private[sbt] def removedFiles(scopedKey: Def.ScopedKey[_]): Def.Setting[_] = + private[this] def removedFilesImpl(scopedKey: Def.ScopedKey[_]): Def.Setting[_] = addTaskDefinition(Keys.removedFiles in scopedKey.scope := { val current = (Keys.allFiles in scopedKey.scope).value (Keys.allFiles in scopedKey.scope).previous match { @@ -167,8 +192,8 @@ private[sbt] object Settings { * * @return a task definition for a function from `Path` to [[FileStamp]]. */ - private[sbt] def stamper(scopedKey: Def.ScopedKey[_]): Def.Setting[_] = - addTaskDefinition((Keys.stamper in scopedKey.scope) := { + private[this] def stamper(scopedKey: Def.ScopedKey[_]): Def.Setting[_] = + addTaskDefinition((Keys.pathToFileStamp in scopedKey.scope) := { val attributeMap = Keys.fileAttributeMap.value val stamper = (Keys.fileStamper in scopedKey.scope).value path: Path => diff --git a/main/src/main/scala/sbt/Watch.scala b/main/src/main/scala/sbt/nio/Watch.scala similarity index 99% rename from main/src/main/scala/sbt/Watch.scala rename to main/src/main/scala/sbt/nio/Watch.scala index 65fe42e05..6d8dd4ad1 100644 --- a/main/src/main/scala/sbt/Watch.scala +++ b/main/src/main/scala/sbt/nio/Watch.scala @@ -5,7 +5,7 @@ * Licensed under Apache License 2.0 (see LICENSE) */ -package sbt +package sbt.nio import java.nio.file.Path import java.time.format.{ DateTimeFormatter, TextStyle } @@ -14,6 +14,7 @@ import java.util.Locale import java.util.concurrent.TimeUnit import sbt.BasicCommandStrings.ContinuousExecutePrefix +import sbt._ import sbt.internal.LabeledFunctions._ import sbt.internal.nio.FileEvent import sbt.internal.util.Util diff --git a/main/src/test/scala/sbt/WatchSpec.scala b/main/src/test/scala/sbt/WatchSpec.scala index 1e04f3f20..d6f90d497 100644 --- a/main/src/test/scala/sbt/WatchSpec.scala +++ b/main/src/test/scala/sbt/WatchSpec.scala @@ -12,11 +12,12 @@ import java.nio.file.{ Files, Path } import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger } import org.scalatest.{ FlatSpec, Matchers } -import sbt.Watch.{ NullLogger, _ } import sbt.WatchSpec._ import sbt.internal.nio.{ FileEvent, FileEventMonitor, FileTreeRepository } import sbt.io._ import sbt.io.syntax._ +import sbt.nio.Watch +import sbt.nio.Watch.{ NullLogger, _ } import sbt.nio.file.{ FileAttributes, Glob } import sbt.util.Logger diff --git a/sbt/src/main/scala/sbt/Import.scala b/sbt/src/main/scala/sbt/Import.scala index e790c863d..b1f0adfb4 100644 --- a/sbt/src/main/scala/sbt/Import.scala +++ b/sbt/src/main/scala/sbt/Import.scala @@ -60,6 +60,16 @@ trait Import { type WatchSource = sbt.internal.io.Source val WatchSource = sbt.internal.io.Source + // sbt.nio + val ** = sbt.nio.file.** + val * = sbt.nio.file.* + val AnyPath = sbt.nio.file.AnyPath + type Glob = sbt.nio.file.Glob + val Glob = sbt.nio.file.Glob + type RelativeGlob = sbt.nio.file.RelativeGlob + val RelativeGlob = sbt.nio.file.RelativeGlob + val RecursiveGlob = sbt.nio.file.RecursiveGlob + // sbt.util type AbstractLogger = sbt.util.AbstractLogger type BasicCache[I, O] = sbt.util.BasicCache[I, O] diff --git a/sbt/src/sbt-test/nio/diff/build.sbt b/sbt/src/sbt-test/nio/diff/build.sbt index d096f00ef..29873bbd0 100644 --- a/sbt/src/sbt-test/nio/diff/build.sbt +++ b/sbt/src/sbt-test/nio/diff/build.sbt @@ -1,5 +1,4 @@ import sbt.nio.Keys._ -import sbt.nio.file._ val fileInputTask = taskKey[Unit]("task with file inputs") diff --git a/sbt/src/sbt-test/nio/glob-dsl/build.sbt b/sbt/src/sbt-test/nio/glob-dsl/build.sbt index aa69dc8d0..abb617aeb 100644 --- a/sbt/src/sbt-test/nio/glob-dsl/build.sbt +++ b/sbt/src/sbt-test/nio/glob-dsl/build.sbt @@ -1,9 +1,3 @@ -import java.nio.file._ - -import sbt.nio.Keys._ -import sbt.nio.file._ -import sbt.Keys._ - // The project contains two files: { Foo.txt, Bar.md } in the subdirector base/subdir/nested-subdir // Check that we can correctly extract Foo.txt with a recursive source diff --git a/sbt/src/sbt-test/nio/intraproject-inputs/project/Build.scala b/sbt/src/sbt-test/nio/intraproject-inputs/project/Build.scala index 98f04d790..7728d6ce4 100644 --- a/sbt/src/sbt-test/nio/intraproject-inputs/project/Build.scala +++ b/sbt/src/sbt-test/nio/intraproject-inputs/project/Build.scala @@ -9,7 +9,6 @@ import sbt.nio.Keys._ * private apis. */ object Build { - import sbt.internal.TransitiveDynamicInputs._ val cached = settingKey[Unit]("") val newInputs = settingKey[Unit]("") diff --git a/sbt/src/sbt-test/watch/command-parser/build.sbt b/sbt/src/sbt-test/watch/command-parser/build.sbt index 208148289..be401b740 100644 --- a/sbt/src/sbt-test/watch/command-parser/build.sbt +++ b/sbt/src/sbt-test/watch/command-parser/build.sbt @@ -10,4 +10,4 @@ checkStringValue := checkStringValueImpl.evaluated setStringValue / watchTriggers := baseDirectory.value * "string.txt" :: Nil -watchOnFileInputEvent := { (_, _) => Watch.CancelWatch } +watchOnFileInputEvent := { (_, _) => sbt.nio.Watch.CancelWatch } diff --git a/sbt/src/sbt-test/watch/custom-config/project/Build.scala b/sbt/src/sbt-test/watch/custom-config/project/Build.scala index 07070d23d..b91c96d57 100644 --- a/sbt/src/sbt-test/watch/custom-config/project/Build.scala +++ b/sbt/src/sbt-test/watch/custom-config/project/Build.scala @@ -3,6 +3,7 @@ package sbt.input.aggregation import sbt._ import Keys._ import sbt.nio.Keys._ +import sbt.nio.Watch object Build { val setStringValue = inputKey[Unit]("set a global string to a value") diff --git a/sbt/src/sbt-test/watch/dynamic-inputs/project/Build.scala b/sbt/src/sbt-test/watch/dynamic-inputs/project/Build.scala index bf306414d..d5ec0fb15 100644 --- a/sbt/src/sbt-test/watch/dynamic-inputs/project/Build.scala +++ b/sbt/src/sbt-test/watch/dynamic-inputs/project/Build.scala @@ -4,6 +4,7 @@ import java.nio.file.Path import sbt._ import Keys._ import sbt.nio.Keys._ +import sbt.nio.Watch object Build { val reloadFile = settingKey[File]("file to toggle whether or not to reload") 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 75b15ff61..cdd2f73a1 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 @@ -1,22 +1,23 @@ package sbt package input.aggregation +import java.nio.file.Paths import sbt.Keys._ import sbt.internal.DynamicInput -import sbt.internal.TransitiveDynamicInputs._ +import sbt.nio.{ file => _, _ } import sbt.nio.Keys._ -import sbt.nio.file.Glob -import java.nio.file.Paths /** - * This test is for internal logic so it must be in the sbt package because it uses package - * private apis. - */ + * This test is for internal logic so it must be in the sbt package because it uses package + * private apis. + */ object Build { val setStringValue = inputKey[Unit]("set a global string to a value") val checkStringValue = inputKey[Unit]("check the value of a global") val checkTriggers = taskKey[Unit]("Check that the triggers are correctly aggregated.") - val checkGlobs = taskKey[Unit]("Check that the globs are correctly aggregated and that the globs are the union of the inputs and the triggers") + val checkGlobs = taskKey[Unit]( + "Check that the globs are correctly aggregated and that the globs are the union of the inputs and the triggers" + ) def setStringValueImpl: Def.Initialize[InputTask[Unit]] = Def.inputTask { val Seq(stringFile, string) = Def.spaceDelimited().parsed.map(_.trim) IO.write(file(stringFile), string) @@ -25,43 +26,49 @@ object Build { val Seq(stringFile, string) = Def.spaceDelimited().parsed assert(IO.read(file(stringFile)) == string) } - // This is a hack to exclude the default compile file inputs def triggers(t: Seq[DynamicInput]): Seq[Glob] = t.collect { + // This is a hack to exclude the default compile and resource file inputs case i if !i.glob.toString.contains("*") => i.glob } - lazy val foo = project.settings( - setStringValue := { - val _ = (fileInputs in (bar, setStringValue)).value - setStringValueImpl.evaluated - }, - checkStringValue := checkStringValueImpl.evaluated, - watchOnFileInputEvent := { (_, _) => Watch.CancelWatch }, - Compile / compile / watchOnIteration := { _ => Watch.CancelWatch }, - checkTriggers := { - val actual = triggers((Compile / compile / transitiveDynamicInputs).value).toSet - val base = baseDirectory.value.getParentFile - // This checks that since foo depends on bar there is a transitive trigger generated - // for the "bar.txt" trigger added to bar / Compile / unmanagedResources (which is a - // transitive dependency of - val expected: Set[Glob] = Set(base * "baz.txt", (base / "bar") * "bar.txt") - assert(actual == expected) - }, - Test / test / watchTriggers += baseDirectory.value * "test.txt", - Test / checkTriggers := { - val testTriggers = triggers((Test / test / transitiveDynamicInputs).value).toSet - // This validates that since the "test.txt" trigger is only added to the Test / test task, - // that the Test / compile does not pick it up. Both of them pick up the the triggers that - // are found in the test above for the compile configuration because of the transitive - // classpath dependency that is added in Defaults.internalDependencies. - val compileTriggers = triggers((Test / compile / transitiveDynamicInputs).value).toSet - val base = baseDirectory.value.getParentFile - val expected: Set[Glob] = Set( - base * "baz.txt", (base / "bar") * "bar.txt", (base / "foo") * "test.txt") - assert(testTriggers == expected) - assert((testTriggers - ((base / "foo") * "test.txt")) == compileTriggers) - }, - ).dependsOn(bar) + lazy val foo = project + .settings( + setStringValue := { + val _ = (fileInputs in (bar, setStringValue)).value + setStringValueImpl.evaluated + }, + checkStringValue := checkStringValueImpl.evaluated, + watchOnFileInputEvent := { (_, _) => + Watch.CancelWatch + }, + Compile / compile / watchOnIteration := { _ => + Watch.CancelWatch + }, + checkTriggers := { + val actual = triggers((Compile / compile / transitiveDynamicInputs).value).toSet + val base = baseDirectory.value.getParentFile + // This checks that since foo depends on bar there is a transitive trigger generated + // for the "bar.txt" trigger added to bar / Compile / unmanagedResources (which is a + // transitive dependency of + val expected: Set[Glob] = Set(base * "baz.txt", (base / "bar") * "bar.txt") + assert(actual == expected) + }, + Test / test / watchTriggers += (baseDirectory.value / "test.txt").toGlob, + Test / checkTriggers := { + val testTriggers = triggers((Test / test / transitiveDynamicInputs).value).toSet + // This validates that since the "test.txt" trigger is only added to the Test / test task, + // that the Test / compile does not pick it up. Both of them pick up the the triggers that + // are found in the test above for the compile configuration because of the transitive + // classpath dependency that is added in Defaults.internalDependencies. + val compileTriggers = triggers((Test / compile / transitiveDynamicInputs).value).toSet + val base = baseDirectory.value.getParentFile + val expected: Set[Glob] = + Set(base * "baz.txt", (base / "bar") * "bar.txt", (base / "foo") * "test.txt") + assert(testTriggers == expected) + assert((testTriggers - ((base / "foo") * "test.txt")) == compileTriggers) + }, + ) + .dependsOn(bar) lazy val bar = project.settings( fileInputs in setStringValue += baseDirectory.value * "foo.txt", @@ -80,18 +87,22 @@ object Build { val testTriggers = triggers((Test / test / transitiveDynamicInputs).value).toSet val compileTriggers = triggers((Test / compile / transitiveDynamicInputs).value).toSet val base = baseDirectory.value.getParentFile - val expected: Set[Glob] = Set( - base * "baz.txt", (base / "bar") * "bar.txt", (base / "bar") * "bar-test.txt") + val expected: Set[Glob] = + Set(base * "baz.txt", (base / "bar") * "bar.txt", (base / "bar") * "bar-test.txt") assert(testTriggers == expected) assert(testTriggers == compileTriggers) }, ) - lazy val root = (project in file(".")).aggregate(foo, bar).settings( - watchOnFileInputEvent := { (_, _) => Watch.CancelWatch }, - checkTriggers := { - val actual = triggers((Compile / compile / transitiveDynamicInputs).value) - val expected: Seq[Glob] = baseDirectory.value * "baz.txt" :: Nil - assert(actual == expected) - }, - ) + lazy val root = (project in file(".")) + .aggregate(foo, bar) + .settings( + watchOnFileInputEvent := { (_, _) => + Watch.CancelWatch + }, + checkTriggers := { + val actual = triggers((Compile / compile / transitiveDynamicInputs).value) + val expected: Seq[Glob] = baseDirectory.value * "baz.txt" :: Nil + assert(actual == expected) + }, + ) } 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 c5aafd05f..399f4acde 100644 --- a/sbt/src/sbt-test/watch/input-parser/project/Build.scala +++ b/sbt/src/sbt-test/watch/input-parser/project/Build.scala @@ -6,6 +6,8 @@ import complete.Parser._ import java.io.{ PipedInputStream, PipedOutputStream } import Keys._ +import sbt.nio.Watch +import sbt.nio.Keys._ object Build { val root = (project in file(".")).settings( diff --git a/sbt/src/sbt-test/watch/legacy-sources/build.sbt b/sbt/src/sbt-test/watch/legacy-sources/build.sbt index 3ee3097d9..643a02868 100644 --- a/sbt/src/sbt-test/watch/legacy-sources/build.sbt +++ b/sbt/src/sbt-test/watch/legacy-sources/build.sbt @@ -1,4 +1,5 @@ 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/on-start-watch/changes/extra.sbt b/sbt/src/sbt-test/watch/on-start-watch/changes/extra.sbt index 36ddd06e3..b00c50d20 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,4 +1,4 @@ val checkReloaded = taskKey[Unit]("Asserts that the build was reloaded") checkReloaded := { () } -watchOnIteration := { _ => Watch.CancelWatch } +watchOnIteration := { _ => sbt.nio.Watch.CancelWatch } diff --git a/sbt/src/sbt-test/watch/on-start-watch/extra.sbt b/sbt/src/sbt-test/watch/on-start-watch/extra.sbt index e563dea91..4012ef561 100644 --- a/sbt/src/sbt-test/watch/on-start-watch/extra.sbt +++ b/sbt/src/sbt-test/watch/on-start-watch/extra.sbt @@ -1 +1 @@ -watchOnIteration := { _ => Watch.Reload } +watchOnIteration := { _ => sbt.nio.Watch.Reload } diff --git a/sbt/src/sbt-test/watch/on-start-watch/test b/sbt/src/sbt-test/watch/on-start-watch/test index a917df61d..a04ec90b3 100644 --- a/sbt/src/sbt-test/watch/on-start-watch/test +++ b/sbt/src/sbt-test/watch/on-start-watch/test @@ -6,19 +6,19 @@ $ copy-file changes/extra.sbt extra.sbt # verify that the watch terminates when we reach the specified count > resetCount -> set watchOnIteration := { (count: Int) => if (count == 2) Watch.CancelWatch else 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 Watch.HandleError(new Exception("")) else 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) Watch.Trigger else if (count == 3) Watch.CancelWatch else 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/on-termination/project/Build.scala b/sbt/src/sbt-test/watch/on-termination/project/Build.scala index 976f27874..2acab46d0 100644 --- a/sbt/src/sbt-test/watch/on-termination/project/Build.scala +++ b/sbt/src/sbt-test/watch/on-termination/project/Build.scala @@ -2,6 +2,8 @@ package sbt.watch.task import sbt._ import Keys._ +import sbt.nio.Watch +import sbt.nio.Keys._ object Build { val reloadFile = settingKey[File]("file to toggle whether or not to reload") diff --git a/sbt/src/sbt-test/watch/task/changes/Build.scala b/sbt/src/sbt-test/watch/task/changes/Build.scala index 5c163a241..dba7ff00a 100644 --- a/sbt/src/sbt-test/watch/task/changes/Build.scala +++ b/sbt/src/sbt-test/watch/task/changes/Build.scala @@ -2,6 +2,8 @@ package sbt.watch.task import sbt._ import Keys._ +import sbt.nio.Keys._ +import sbt.nio.Watch object Build { val setStringValue = inputKey[Unit]("set a global string to a value") diff --git a/sbt/src/sbt-test/watch/task/project/Build.scala b/sbt/src/sbt-test/watch/task/project/Build.scala index d2498566a..9ce2bc924 100644 --- a/sbt/src/sbt-test/watch/task/project/Build.scala +++ b/sbt/src/sbt-test/watch/task/project/Build.scala @@ -2,6 +2,8 @@ package sbt.watch.task import sbt._ import Keys._ +import sbt.nio.Keys._ +import sbt.nio.Watch object Build { val reloadFile = settingKey[File]("file to toggle whether or not to reload")