Adapt to use the new `WatchService`

This commit adapts `Watched` so that it supports the new `WatchService`
infrastructure introduced in sbt/io. The goal of this infrastructure is
to provide and API for and several implementations of services that
monitor changes to the file system.

The service to use to monitor the file system can be configured with the
key `watchService`.
This commit is contained in:
Martin Duhem 2017-06-20 21:03:18 +02:00 committed by Eugene Yokota
parent 25d393bd8b
commit 0daca42c29
3 changed files with 53 additions and 28 deletions

View File

@ -6,30 +6,35 @@ package sbt
import BasicCommandStrings.ClearOnFailure
import State.FailureWall
import annotation.tailrec
import java.io.File
import java.nio.file.FileSystems
import sbt.io.PathFinder
import sbt.internal.io.{ SourceModificationWatch, WatchState }
import scala.concurrent.duration._
import sbt.io.WatchService
import sbt.internal.io.{ Source, SourceModificationWatch, WatchState }
import sbt.internal.util.AttributeKey
import sbt.internal.util.Types.const
trait Watched {
/** The files watched when an action is run with a preceeding ~ */
def watchPaths(s: State): Seq[File] = Nil
def watchSources(s: State): Seq[Source] = Nil
def terminateWatch(key: Int): Boolean = Watched.isEnter(key)
/**
* The time in milliseconds between checking for changes. The actual time between the last change made to a file and the
* execution time is between `pollInterval` and `pollInterval*2`.
*/
def pollInterval: Int = Watched.PollDelayMillis
def pollInterval: FiniteDuration = Watched.PollDelay
/** The message to show when triggered execution waits for sources to change.*/
private[sbt] def watchingMessage(s: WatchState): String = Watched.defaultWatchingMessage(s)
/** The message to show before an action is run. */
private[sbt] def triggeredMessage(s: WatchState): String = Watched.defaultTriggeredMessage(s)
/** The `WatchService` to use to monitor the file system. */
private[sbt] def watchService(): WatchService = FileSystems.getDefault.newWatchService()
}
object Watched {
@ -43,7 +48,7 @@ object Watched {
def multi(base: Watched, paths: Seq[Watched]): Watched =
new AWatched {
override def watchPaths(s: State) = (base.watchPaths(s) /: paths)(_ ++ _.watchPaths(s))
override def watchSources(s: State) = (base.watchSources(s) /: paths)(_ ++ _.watchSources(s))
override def terminateWatch(key: Int): Boolean = base.terminateWatch(key)
override val pollInterval = (base +: paths).map(_.pollInterval).min
override def watchingMessage(s: WatchState) = base.watchingMessage(s)
@ -51,15 +56,16 @@ object Watched {
}
def empty: Watched = new AWatched
val PollDelayMillis = 500
val PollDelay: FiniteDuration = 500.milliseconds
def isEnter(key: Int): Boolean = key == 10 || key == 13
def printIfDefined(msg: String) = if (!msg.isEmpty) System.out.println(msg)
def executeContinuously(watched: Watched, s: State, next: String, repeat: String): State = {
@tailrec def shouldTerminate: Boolean =
(System.in.available > 0) && (watched.terminateWatch(System.in.read()) || shouldTerminate)
val sourcesFinder = PathFinder { watched watchPaths s }
val watchState = s get ContinuousState getOrElse WatchState.empty
val sources = watched.watchSources(s)
val service = watched.watchService()
val watchState = s get ContinuousState getOrElse WatchState.empty(service, sources)
if (watchState.count > 0)
printIfDefined(watched watchingMessage watchState)
@ -67,8 +73,7 @@ object Watched {
val (triggered, newWatchState) =
try {
val (triggered, newWatchState) =
SourceModificationWatch.watch(sourcesFinder, watched.pollInterval, watchState)(
shouldTerminate)
SourceModificationWatch.watch(watched.pollInterval, watchState)(shouldTerminate)
(triggered, newWatchState)
} catch {
case e: Exception =>
@ -84,7 +89,8 @@ object Watched {
(ClearOnFailure :: next :: FailureWall :: repeat :: s).put(ContinuousState, newWatchState)
} else {
while (System.in.available() > 0) System.in.read()
s.put(ContinuousState, WatchState.empty)
service.close()
s.remove(ContinuousState)
}
}
val ContinuousState =

View File

@ -5,6 +5,7 @@ package sbt
import Def.{ Initialize, ScopedKey, Setting, SettingsDefinition }
import java.io.{ File, PrintWriter }
import java.nio.file.FileSystems
import java.net.{ URI, URL }
import java.util.Optional
import java.util.concurrent.{ TimeUnit, Callable }
@ -23,7 +24,7 @@ import sbt.internal._
import sbt.internal.CommandStrings.ExportStream
import sbt.internal.inc.ZincUtil
import sbt.internal.inc.JavaInterfaceUtil._
import sbt.internal.io.WatchState
import sbt.internal.io.{ Source, WatchState }
import sbt.internal.librarymanagement._
import sbt.internal.librarymanagement.mavenint.{
PomExtraDependencyAttributes,
@ -47,7 +48,8 @@ import sbt.io.{
PathFinder,
SimpleFileFilter,
DirectoryFilter,
Hash
Hash,
WatchService
}, Path._
import sbt.librarymanagement.Artifact.{ DocClassifier, SourceClassifier }
import sbt.librarymanagement.Configurations.{
@ -250,7 +252,10 @@ object Defaults extends BuildCommon {
Previous.references :== new Previous.References,
concurrentRestrictions := defaultRestrictions.value,
parallelExecution :== true,
pollInterval :== 500,
pollInterval :== new FiniteDuration(500, TimeUnit.MILLISECONDS),
watchService :== { () =>
FileSystems.getDefault.newWatchService
},
logBuffered :== false,
commands :== Nil,
showSuccess :== true,
@ -317,7 +322,12 @@ object Defaults extends BuildCommon {
unmanagedSources := collectFiles(unmanagedSourceDirectories,
includeFilter in unmanagedSources,
excludeFilter in unmanagedSources).value,
watchSources in ConfigGlobal ++= unmanagedSources.value,
watchSources in ConfigGlobal ++= {
val bases = unmanagedSourceDirectories.value
val include = (includeFilter in unmanagedSources).value
val exclude = (excludeFilter in unmanagedSources).value
bases.map(b => new Source(b, include, exclude))
},
managedSourceDirectories := Seq(sourceManaged.value),
managedSources := generate(sourceGenerators).value,
sourceGenerators :== Nil,
@ -337,7 +347,12 @@ object Defaults extends BuildCommon {
unmanagedResources := collectFiles(unmanagedResourceDirectories,
includeFilter in unmanagedResources,
excludeFilter in unmanagedResources).value,
watchSources in ConfigGlobal ++= unmanagedResources.value,
watchSources in ConfigGlobal ++= {
val bases = unmanagedResourceDirectories.value
val include = (includeFilter in unmanagedResources).value
val exclude = (excludeFilter in unmanagedResources).value
bases.map(b => new Source(b, include, exclude))
},
resourceGenerators :== Nil,
resourceGenerators += Def.task {
PluginDiscovery.writeDescriptors(discoveredSbtPlugins.value, resourceManaged.value)
@ -537,7 +552,7 @@ object Defaults extends BuildCommon {
def generate(generators: SettingKey[Seq[Task[Seq[File]]]]): Initialize[Task[Seq[File]]] =
generators { _.join.map(_.flatten) }
def watchTransitiveSourcesTask: Initialize[Task[Seq[File]]] = {
def watchTransitiveSourcesTask: Initialize[Task[Seq[Source]]] = {
import ScopeFilter.Make.{ inDependencies => inDeps, _ }
val selectDeps = ScopeFilter(inAggregates(ThisProject) || inDeps(ThisProject))
val allWatched = (watchSources ?? Nil).all(selectDeps)
@ -554,6 +569,7 @@ object Defaults extends BuildCommon {
def watchSetting: Initialize[Watched] =
Def.setting {
val getService = watchService.value
val interval = pollInterval.value
val base = thisProjectRef.value
val msg = watchingMessage.value
@ -564,11 +580,13 @@ object Defaults extends BuildCommon {
override def pollInterval = interval
override def watchingMessage(s: WatchState) = msg(s)
override def triggeredMessage(s: WatchState) = trigMsg(s)
override def watchPaths(s: State) = EvaluateTask(Project structure s, key, s, base) match {
case Some((_, Value(ps))) => ps
case Some((_, Inc(i))) => throw i
case None => sys.error("key not found: " + Def.displayFull(key))
}
override def watchService() = getService()
override def watchSources(s: State) =
EvaluateTask(Project structure s, key, s, base) match {
case Some((_, Value(ps))) => ps
case Some((_, Inc(i))) => throw i
case None => sys.error("key not found: " + Def.displayFull(key))
}
}
}

View File

@ -36,8 +36,8 @@ import sbt.internal.{
SessionSettings,
LogManager
}
import sbt.io.FileFilter
import sbt.internal.io.WatchState
import sbt.io.{ FileFilter, WatchService }
import sbt.internal.io.{ Source, WatchState }
import sbt.internal.util.{ AttributeKey, SourcePosition }
import sbt.librarymanagement.Configurations.CompilerPlugin
@ -130,9 +130,10 @@ object Keys {
val analysis = AttributeKey[CompileAnalysis]("analysis", "Analysis of compilation, including dependencies and generated outputs.", DSetting)
val watch = SettingKey(BasicKeys.watch)
val suppressSbtShellNotification = SettingKey[Boolean]("suppressSbtShellNotification", """True to suppress the "Executing in batch mode.." message.""", CSetting)
val pollInterval = SettingKey[Int]("poll-interval", "Interval between checks for modified sources by the continuous execution command.", BMinusSetting)
val watchSources = TaskKey[Seq[File]]("watch-sources", "Defines the sources in this project for continuous execution to watch for changes.", BMinusSetting)
val watchTransitiveSources = TaskKey[Seq[File]]("watch-transitive-sources", "Defines the sources in all projects for continuous execution to watch.", CSetting)
val pollInterval = SettingKey[FiniteDuration]("poll-interval", "Interval between checks for modified sources by the continuous execution command.", BMinusSetting)
val watchService = SettingKey[() => WatchService]("watch-service", "Service to use to monitor file system changes.", BMinusSetting)
val watchSources = TaskKey[Seq[Source]]("watch-sources", "Defines the sources in this project for continuous execution to watch for changes.", BMinusSetting)
val watchTransitiveSources = TaskKey[Seq[Source]]("watch-transitive-sources", "Defines the sources in all projects for continuous execution to watch.", CSetting)
val watchingMessage = SettingKey[WatchState => String]("watching-message", "The message to show when triggered execution waits for sources to change.", DSetting)
val triggeredMessage = SettingKey[WatchState => String]("triggered-message", "The message to show before triggered execution executes an action after sources change.", DSetting)