From f579b89577cbf72a1f5e52a1de7d4add22697639 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Wed, 10 Oct 2018 18:48:42 -0700 Subject: [PATCH] Fix windows reload loop On windows* it was possible to get into a loop where the build would continually restart because for some reason the build.sbt file would get touched during test (I did not see this behavior on osx). Thankfully, the repository keeps track of the file hash and when we detect that the build file has been updated, we check the file hash to see if it actually changed. Note that had this bug shipped, it would have been fixable by overriding the watchOnEvent task in user builds. The loop would occur if I ran ~filesJVM/test in https://github.com/swoval/swoval. It would not occur if I ran test:compile, so the fact that the build file is being touched seems to be related to the test run itself. --- main-command/src/main/scala/sbt/Watched.scala | 14 +++++++++++++- main/src/main/scala/sbt/Defaults.scala | 10 ++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index d697bb56c..e6b851d14 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -23,7 +23,7 @@ import sbt.internal.io.{ EventMonitor, Source, WatchState } import sbt.internal.util.Types.const import sbt.internal.util.complete.DefaultParsers import sbt.internal.util.{ AttributeKey, JLine } -import sbt.io.FileEventMonitor.Event +import sbt.io.FileEventMonitor.{ Creation, Deletion, Event, Update } import sbt.io._ import sbt.util.{ Level, Logger } import xsbti.compile.analysis.Stamp @@ -144,6 +144,18 @@ object Watched { } scanInput() } + private[sbt] def onEvent( + sources: Seq[WatchSource], + projectSources: Seq[WatchSource] + ): Event[StampedFile] => Watched.Action = + event => + if (sources.exists(_.accept(event.entry.typedPath.getPath))) Watched.Trigger + else if (projectSources.exists(_.accept(event.entry.typedPath.getPath))) event match { + case Update(prev, cur, _) if prev.value.map(_.stamp) != cur.value.map(_.stamp) => Reload + case _: Creation[_] | _: Deletion[_] => Reload + case _ => Ignore + } else Ignore + private[this] val reRun = if (isWin) "" else " or 'r' to re-run the command" private def waitMessage(project: String): String = s"Waiting for source changes$project... (press enter to interrupt$reRun)" diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 3c54cadf2..f4d329710 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -621,14 +621,8 @@ object Defaults extends BuildCommon { consoleProject := consoleProjectTask.value, watchTransitiveSources := watchTransitiveSourcesTask.value, watchProjectTransitiveSources := watchTransitiveSourcesTaskImpl(watchProjectSources).value, - watchOnEvent := { - val sources = watchTransitiveSources.value - val projectSources = watchProjectTransitiveSources.value - e => - if (sources.exists(_.accept(e.entry.typedPath.getPath))) Watched.Trigger - else if (projectSources.exists(_.accept(e.entry.typedPath.getPath))) Watched.Reload - else Watched.Ignore - }, + watchOnEvent := Watched + .onEvent(watchTransitiveSources.value, watchProjectTransitiveSources.value), watchHandleInput := Watched.handleInput, watchPreWatch := { (_, _) => Watched.Ignore