Skip to content

Commit

Permalink
Adapt to use the new WatchService
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
Duhemm committed Jun 20, 2017
1 parent 42dd511 commit f5060f2
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 25 deletions.
26 changes: 15 additions & 11 deletions main-command/src/main/scala/sbt/Watched.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,33 @@ 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.io.WatchService
import sbt.internal.io.{ 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[WatchState.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: Long = Watched.PollDelayMillis

/** 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 {
Expand All @@ -43,32 +46,32 @@ 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)
override def triggeredMessage(s: WatchState) = base.triggeredMessage(s)
}
def empty: Watched = new AWatched

val PollDelayMillis = 500
val PollDelayMillis: Long = 500
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)

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 =>
Expand All @@ -84,7 +87,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 =
Expand Down
38 changes: 28 additions & 10 deletions main/src/main/scala/sbt/Defaults.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -47,7 +48,8 @@ import sbt.io.{
PathFinder,
SimpleFileFilter,
DirectoryFilter,
Hash
Hash,
WatchService
}, Path._
import sbt.librarymanagement.Artifact.{ DocClassifier, SourceClassifier }
import sbt.librarymanagement.Configurations.{
Expand Down Expand Up @@ -250,7 +252,10 @@ object Defaults extends BuildCommon {
Previous.references :== new Previous.References,
concurrentRestrictions := defaultRestrictions.value,
parallelExecution :== true,
pollInterval :== 500,
pollInterval :== 500L,
watchService :== { () =>
FileSystems.getDefault.newWatchService
},
logBuffered :== false,
commands :== Nil,
showSuccess :== true,
Expand Down Expand Up @@ -314,7 +319,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 => (b, include, exclude))
},
managedSourceDirectories := Seq(sourceManaged.value),
managedSources := generate(sourceGenerators).value,
sourceGenerators :== Nil,
Expand All @@ -334,7 +344,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 => (b, include, exclude))
},
resourceGenerators :== Nil,
resourceGenerators += Def.task {
PluginDiscovery.writeDescriptors(discoveredSbtPlugins.value, resourceManaged.value)
Expand Down Expand Up @@ -536,7 +551,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[WatchState.Source]]] = {
import ScopeFilter.Make.{ inDependencies => inDeps, _ }
val selectDeps = ScopeFilter(inAggregates(ThisProject) || inDeps(ThisProject))
val allWatched = (watchSources ?? Nil).all(selectDeps)
Expand All @@ -553,6 +568,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
Expand All @@ -563,11 +579,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))
}
}
}

Expand Down
9 changes: 5 additions & 4 deletions main/src/main/scala/sbt/Keys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import sbt.internal.{
SessionSettings,
LogManager
}
import sbt.io.FileFilter
import sbt.io.{ FileFilter, WatchService }
import sbt.internal.io.WatchState
import sbt.internal.util.{ AttributeKey, SourcePosition }

Expand Down Expand Up @@ -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[Long]("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[WatchState.Source]]("watch-sources", "Defines the sources in this project for continuous execution to watch for changes.", BMinusSetting)
val watchTransitiveSources = TaskKey[Seq[WatchState.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)

Expand Down

0 comments on commit f5060f2

Please sign in to comment.