From 4007810adb60e20df90e645ddb029ccb580b8e25 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Thu, 9 May 2019 13:26:21 -0700 Subject: [PATCH] Add watchPersistFileStamps key The persistentFileStampCache does seem to work pretty well but in case users encounter issues, I add a boolean flag that allows the user to turn this behavior off and always re-stamp every source file in every task evaluation run. --- main/src/main/scala/sbt/Defaults.scala | 1 + .../main/scala/sbt/internal/Continuous.scala | 24 ++++++++++--------- main/src/main/scala/sbt/nio/Keys.scala | 3 +++ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index e8784039a..dc78c4984 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -153,6 +153,7 @@ object Defaults extends BuildCommon { inputFileStamper :== sbt.nio.FileStamper.Hash, outputFileStamper :== sbt.nio.FileStamper.LastModified, watchForceTriggerOnAnyChange :== true, + watchPersistFileStamps :== true, watchTriggers :== Nil, clean := { () }, sbt.nio.Keys.fileStampCache := { diff --git a/main/src/main/scala/sbt/internal/Continuous.scala b/main/src/main/scala/sbt/internal/Continuous.scala index 5aa62184a..c2b5657c4 100644 --- a/main/src/main/scala/sbt/internal/Continuous.scala +++ b/main/src/main/scala/sbt/internal/Continuous.scala @@ -291,20 +291,22 @@ private[sbt] object Continuous extends DeprecatedContinuous { } else { FileTreeRepository.default } - val attributeMap = new FileStamp.Cache - repo.addObserver(t => attributeMap.invalidate(t.path)) + val fileStampCache = new FileStamp.Cache + repo.addObserver(t => fileStampCache.invalidate(t.path)) try { - val stateWithRepo = state - .put(globalFileTreeRepository, repo) - .put(persistentFileStampCache, attributeMap) - setup(stateWithRepo, command) { (commands, s, valid, invalid) => + val stateWithRepo = state.put(globalFileTreeRepository, repo) + val fullState = + if (extracted.get(watchPersistFileStamps)) + stateWithRepo.put(persistentFileStampCache, fileStampCache) + else stateWithRepo + setup(fullState, command) { (commands, s, valid, invalid) => EvaluateTask.withStreams(extracted.structure, s)(_.use(streams in Global) { streams => implicit val logger: Logger = streams.log if (invalid.isEmpty) { val currentCount = new AtomicInteger(count) val configs = getAllConfigs(valid.map(v => v._1 -> v._2)) val callbacks = - aggregate(configs, logger, in, s, currentCount, isCommand, commands, attributeMap) + aggregate(configs, logger, in, s, currentCount, isCommand, commands, fileStampCache) val task = () => { currentCount.getAndIncrement() // abort as soon as one of the tasks fails @@ -401,7 +403,7 @@ private[sbt] object Continuous extends DeprecatedContinuous { count: AtomicInteger, isCommand: Boolean, commands: Seq[String], - attributeMap: FileStamp.Cache + fileStampCache: FileStamp.Cache )( implicit extracted: Extracted ): Callbacks = { @@ -411,7 +413,7 @@ private[sbt] object Continuous extends DeprecatedContinuous { 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, attributeMap) + getFileEvents(configs, rawLogger, state, count, commands, fileStampCache) val nextEvent: () => Watch.Action = combineInputAndFileEvents(nextInputEvent, nextFileEvent, logger) val onExit = () => { @@ -476,7 +478,7 @@ private[sbt] object Continuous extends DeprecatedContinuous { state: State, count: AtomicInteger, commands: Seq[String], - attributeMap: FileStamp.Cache + fileStampCache: FileStamp.Cache )(implicit extracted: Extracted): (() => Option[(Watch.Event, Watch.Action)], () => Unit) = { val trackMetaBuild = configs.forall(_.watchSettings.trackMetaBuild) val buildGlobs = @@ -491,7 +493,7 @@ private[sbt] object Continuous extends DeprecatedContinuous { def watchEvent(stamper: FileStamper, forceTrigger: Boolean): Option[Watch.Event] = { if (!event.exists) { Some(Deletion(event)) - attributeMap.remove(event.path) match { + fileStampCache.remove(event.path) match { case null => None case _ => Some(Deletion(event)) } diff --git a/main/src/main/scala/sbt/nio/Keys.scala b/main/src/main/scala/sbt/nio/Keys.scala index d7577d53c..abc993d3c 100644 --- a/main/src/main/scala/sbt/nio/Keys.scala +++ b/main/src/main/scala/sbt/nio/Keys.scala @@ -85,6 +85,9 @@ object Keys { 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 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]]( "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)