diff --git a/main-command/src/main/scala/sbt/FileTreeViewConfig.scala b/main-command/src/main/scala/sbt/FileTreeViewConfig.scala index 4b1179bfe..c1dd8fa6d 100644 --- a/main-command/src/main/scala/sbt/FileTreeViewConfig.scala +++ b/main-command/src/main/scala/sbt/FileTreeViewConfig.scala @@ -7,7 +7,7 @@ package sbt import sbt.Watched.WatchSource -import sbt.internal.io.{ WatchServiceBackedObservable, WatchState } +import sbt.internal.io.{ HybridPollingFileTreeRepository, WatchServiceBackedObservable, WatchState } import sbt.io._ import FileTreeDataView.{ Observable, Observer } import sbt.util.Logger @@ -109,4 +109,64 @@ object FileTreeViewConfig { FileEventMonitor.antiEntropy(copied, antiEntropy, msg => logger.debug(msg.toString)) } ) + + /** + * Provides a default [[FileTreeViewConfig]]. When the pollingSources argument is empty, it + * returns the same config as [[sbt.FileTreeViewConfig.default(antiEntropy:scala\.concurrent\.duration\.FiniteDuration)*]]. + * Otherwise, it returns the same config as [[polling]]. + * @param antiEntropy the duration of the period after a path triggers a build for which it is + * quarantined from triggering another build + * @param pollingInterval the frequency with which the sbt.io.FileEventMonitor polls the file + * system for the paths included in pollingSources + * @param pollingSources the sources that will not be cached in the sbt.io.FileTreeRepository and that + * will be periodically polled for changes during continuous builds. + * @return + */ + def default( + antiEntropy: FiniteDuration, + pollingInterval: FiniteDuration, + pollingSources: Seq[WatchSource] + ): FileTreeViewConfig = { + if (pollingSources.isEmpty) default(antiEntropy) + else polling(antiEntropy, pollingInterval, pollingSources) + } + + /** + * Provides a polling [[FileTreeViewConfig]]. Unlike the view returned by newDataView in + * [[sbt.FileTreeViewConfig.default(antiEntropy:scala\.concurrent\.duration\.FiniteDuration)*]], + * the view returned by newDataView will not cache any portion of the file system tree that is is + * covered by the pollingSources parameter. The monitor that is generated by newMonitor, will + * poll these directories for changes rather than relying on file system events from the + * operating system. Any paths that are registered with the view that are not included in the + * pollingSources will be cached and monitored using file system events from the operating system + * in the same way that they are in the default view. + * + * @param antiEntropy the duration of the period after a path triggers a build for which it is + * quarantined from triggering another build + * @param pollingInterval the frequency with which the FileEventMonitor polls the file system + * for the paths included in pollingSources + * @param pollingSources the sources that will not be cached in the sbt.io.FileTreeRepository and that + * will be periodically polled for changes during continuous builds. + * @return a [[FileTreeViewConfig]] instance. + */ + def polling( + antiEntropy: FiniteDuration, + pollingInterval: FiniteDuration, + pollingSources: Seq[WatchSource], + ): FileTreeViewConfig = FileTreeViewConfig( + () => FileTreeRepository.hybrid(StampedFile.converter, pollingSources: _*), + ( + repository: HybridPollingFileTreeRepository[StampedFile], + sources: Seq[WatchSource], + logger: Logger + ) => { + repository.register(sources) + FileEventMonitor + .antiEntropy( + repository.toPollingObservable(pollingInterval, sources, NullWatchLogger), + antiEntropy, + msg => logger.debug(msg.toString) + ) + } + ) } diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 1aa5bb35f..212d02442 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -250,6 +250,7 @@ object Defaults extends BuildCommon { extraLoggers :== { _ => Nil }, + pollingDirectories :== Nil, watchSources :== Nil, watchProjectSources :== Nil, skip :== false, @@ -271,7 +272,8 @@ object Defaults extends BuildCommon { None }, watchStartMessage := Watched.defaultStartWatch, - fileTreeViewConfig := FileTreeViewConfig.default(watchAntiEntropy.value), + fileTreeViewConfig := FileTreeViewConfig + .default(watchAntiEntropy.value, pollInterval.value, pollingDirectories.value), fileTreeView := state.value .get(BasicKeys.globalFileTreeView) .getOrElse(FileTreeView.DEFAULT.asDataView(StampedFile.converter)), @@ -655,7 +657,8 @@ object Defaults extends BuildCommon { }, watchStartMessage := Watched.projectOnWatchMessage(thisProjectRef.value.project), watch := watchSetting.value, - fileTreeViewConfig := FileTreeViewConfig.default(watchAntiEntropy.value) + fileTreeViewConfig := FileTreeViewConfig + .default(watchAntiEntropy.value, pollInterval.value, pollingDirectories.value) ) def generate(generators: SettingKey[Seq[Task[Seq[File]]]]): Initialize[Task[Seq[File]]] = diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index b0513181b..ebf6a8cd1 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -147,6 +147,7 @@ object Keys { val suppressSbtShellNotification = settingKey[Boolean]("""True to suppress the "Executing in batch mode.." message.""").withRank(CSetting) val fileTreeView = taskKey[FileTreeDataView[StampedFile]]("A view of the file system") val pollInterval = settingKey[FiniteDuration]("Interval between checks for modified sources by the continuous execution command.").withRank(BMinusSetting) + val pollingDirectories = settingKey[Seq[Watched.WatchSource]]("Directories that cannot be cached and must always be rescanned. Typically these will be NFS mounted or something similar.").withRank(DSetting) 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 watchConfig = taskKey[WatchConfig]("The configuration for continuous execution.").withRank(BMinusSetting) val watchLogger = taskKey[Logger]("A logger that reports watch events.").withRank(DSetting)