From 602554a411dd4f8a119e0b11ac87c317efa18f8e Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Wed, 9 Jan 2019 19:05:23 -0800 Subject: [PATCH 1/5] Add scaladoc for StampedFile --- main-command/src/main/scala/sbt/Watched.scala | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index 2e9c4bbd7..95f415a7c 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -571,14 +571,36 @@ object WatchConfig { } } +/** + * A File that has a [[Stamp]] value associated with it. In general, the stamp method should be + * a cached value that can be read without doing any io. This can be used to improve performance + * anywhere where we need to check if files have changed before doing potentially expensive work. + */ trait StampedFile extends File { def stamp: Stamp } + +/** + * Provides converter functions from TypedPath to [[StampedFile]]. + */ object StampedFile { + + /** + * Converts a TypedPath instance to a [[StampedFile]] by calculating the file hash. + */ val sourceConverter: TypedPath => StampedFile = new StampedFileImpl(_: TypedPath, forceLastModified = false) + + /** + * Converts a TypedPath instance to a [[StampedFile]] using the last modified time. + */ val binaryConverter: TypedPath => StampedFile = new StampedFileImpl(_: TypedPath, forceLastModified = true) + + /** + * A combined convert that converts TypedPath instances representing *.jar and *.class files + * using the last modified time and all other files using the file hash. + */ val converter: TypedPath => StampedFile = (tp: TypedPath) => tp.toPath.toString match { case s if s.endsWith(".jar") => binaryConverter(tp) @@ -586,6 +608,13 @@ object StampedFile { case _ => sourceConverter(tp) } + /** + * Adds a default ordering that just delegates to the java.io.File.compareTo method. + */ + implicit case object ordering extends Ordering[StampedFile] { + override def compare(left: StampedFile, right: StampedFile): Int = left.compareTo(right) + } + private class StampedFileImpl(typedPath: TypedPath, forceLastModified: Boolean) extends java.io.File(typedPath.toPath.toString) with StampedFile { From d39bb96c41c108efb39342cc8c92c44ea2d2286a Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Wed, 9 Jan 2019 19:06:34 -0800 Subject: [PATCH 2/5] Move StampedFile into its own file --- .../src/main/scala/sbt/StampedFile.scala | 73 +++++++++++++++++++ main-command/src/main/scala/sbt/Watched.scala | 56 -------------- 2 files changed, 73 insertions(+), 56 deletions(-) create mode 100644 main-command/src/main/scala/sbt/StampedFile.scala diff --git a/main-command/src/main/scala/sbt/StampedFile.scala b/main-command/src/main/scala/sbt/StampedFile.scala new file mode 100644 index 000000000..743658bbc --- /dev/null +++ b/main-command/src/main/scala/sbt/StampedFile.scala @@ -0,0 +1,73 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt + +import java.io.File +import java.nio.file.Path + +import sbt.internal.inc.Stamper +import sbt.io.TypedPath +import xsbti.compile.analysis.Stamp + +/** + * A File that has a [[Stamp]] value associated with it. In general, the stamp method should be + * a cached value that can be read without doing any io. This can be used to improve performance + * anywhere where we need to check if files have changed before doing potentially expensive work. + */ +trait StampedFile extends File with TypedPath { + def stamp: Stamp +} + +/** + * Provides converter functions from TypedPath to [[StampedFile]]. + */ +object StampedFile { + + /** + * Converts a TypedPath instance to a [[StampedFile]] by calculating the file hash. + */ + val sourceConverter: TypedPath => StampedFile = + new StampedFileImpl(_: TypedPath, forceLastModified = false) + + /** + * Converts a TypedPath instance to a [[StampedFile]] using the last modified time. + */ + val binaryConverter: TypedPath => StampedFile = + new StampedFileImpl(_: TypedPath, forceLastModified = true) + + /** + * A combined convert that converts TypedPath instances representing *.jar and *.class files + * using the last modified time and all other files using the file hash. + */ + val converter: TypedPath => StampedFile = (tp: TypedPath) => + tp.toPath.toString match { + case s if s.endsWith(".jar") => binaryConverter(tp) + case s if s.endsWith(".class") => binaryConverter(tp) + case _ => sourceConverter(tp) + } + + /** + * Adds a default ordering that just delegates to the java.io.File.compareTo method. + */ + implicit case object ordering extends Ordering[StampedFile] { + override def compare(left: StampedFile, right: StampedFile): Int = left.compareTo(right) + } + + private class StampedFileImpl(typedPath: TypedPath, forceLastModified: Boolean) + extends java.io.File(typedPath.toPath.toString) + with StampedFile { + override val stamp: Stamp = + if (forceLastModified || typedPath.isDirectory) Stamper.forLastModified(this) + else Stamper.forHash(this) + override def exists: Boolean = typedPath.exists + override def isDirectory: Boolean = typedPath.isDirectory + override def isFile: Boolean = typedPath.isFile + override def isSymbolicLink: Boolean = typedPath.isSymbolicLink + override def toPath: Path = typedPath.toPath + } +} diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index 95f415a7c..16fb1aae1 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -19,7 +19,6 @@ import sbt.BasicCommandStrings.{ import sbt.BasicCommands.otherCommandParser import sbt.internal.LabeledFunctions._ import sbt.internal.LegacyWatched -import sbt.internal.inc.Stamper import sbt.internal.io.{ EventMonitor, Source, WatchState } import sbt.internal.util.Types.const import sbt.internal.util.complete.{ DefaultParsers, Parser } @@ -27,7 +26,6 @@ import sbt.internal.util.{ AttributeKey, JLine } import sbt.io.FileEventMonitor.{ Creation, Deletion, Event, Update } import sbt.io._ import sbt.util.{ Level, Logger } -import xsbti.compile.analysis.Stamp import scala.annotation.tailrec import scala.concurrent.duration._ @@ -570,57 +568,3 @@ object WatchConfig { } } } - -/** - * A File that has a [[Stamp]] value associated with it. In general, the stamp method should be - * a cached value that can be read without doing any io. This can be used to improve performance - * anywhere where we need to check if files have changed before doing potentially expensive work. - */ -trait StampedFile extends File { - def stamp: Stamp -} - -/** - * Provides converter functions from TypedPath to [[StampedFile]]. - */ -object StampedFile { - - /** - * Converts a TypedPath instance to a [[StampedFile]] by calculating the file hash. - */ - val sourceConverter: TypedPath => StampedFile = - new StampedFileImpl(_: TypedPath, forceLastModified = false) - - /** - * Converts a TypedPath instance to a [[StampedFile]] using the last modified time. - */ - val binaryConverter: TypedPath => StampedFile = - new StampedFileImpl(_: TypedPath, forceLastModified = true) - - /** - * A combined convert that converts TypedPath instances representing *.jar and *.class files - * using the last modified time and all other files using the file hash. - */ - val converter: TypedPath => StampedFile = (tp: TypedPath) => - tp.toPath.toString match { - case s if s.endsWith(".jar") => binaryConverter(tp) - case s if s.endsWith(".class") => binaryConverter(tp) - case _ => sourceConverter(tp) - } - - /** - * Adds a default ordering that just delegates to the java.io.File.compareTo method. - */ - implicit case object ordering extends Ordering[StampedFile] { - override def compare(left: StampedFile, right: StampedFile): Int = left.compareTo(right) - } - - private class StampedFileImpl(typedPath: TypedPath, forceLastModified: Boolean) - extends java.io.File(typedPath.toPath.toString) - with StampedFile { - override val stamp: Stamp = - if (forceLastModified || typedPath.isDirectory) - Stamper.forLastModified(typedPath.toPath.toFile) - else Stamper.forHash(typedPath.toPath.toFile) - } -} From 0bdc30b60b4eae6a58e2b8752c96d005fb111f15 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Thu, 31 Jan 2019 17:51:14 -0800 Subject: [PATCH 3/5] Rename StampedFile to Stamped --- .../src/main/scala/sbt/BasicKeys.scala | 2 +- .../main/scala/sbt/FileTreeViewConfig.scala | 36 +++++++++---------- .../sbt/{StampedFile.scala => Stamped.scala} | 28 +++++++-------- main-command/src/main/scala/sbt/Watched.scala | 14 ++++---- .../src/test/scala/sbt/WatchedSpec.scala | 4 +-- main/src/main/scala/sbt/Defaults.scala | 2 +- main/src/main/scala/sbt/Keys.scala | 4 +-- main/src/main/scala/sbt/Main.scala | 2 +- .../scala/sbt/internal/ExternalHooks.scala | 12 +++---- .../scala/sbt/internal/FileManagement.scala | 2 +- 10 files changed, 53 insertions(+), 53 deletions(-) rename main-command/src/main/scala/sbt/{StampedFile.scala => Stamped.scala} (65%) diff --git a/main-command/src/main/scala/sbt/BasicKeys.scala b/main-command/src/main/scala/sbt/BasicKeys.scala index 0f71774f8..6b9b13171 100644 --- a/main-command/src/main/scala/sbt/BasicKeys.scala +++ b/main-command/src/main/scala/sbt/BasicKeys.scala @@ -102,7 +102,7 @@ object BasicKeys { "List of template resolver infos.", 1000 ) - private[sbt] val globalFileTreeView = AttributeKey[FileTreeDataView[StampedFile]]( + private[sbt] val globalFileTreeView = AttributeKey[FileTreeDataView[Stamped]]( "globalFileTreeView", "provides a view into the file system that may or may not cache the tree in memory", 1000 diff --git a/main-command/src/main/scala/sbt/FileTreeViewConfig.scala b/main-command/src/main/scala/sbt/FileTreeViewConfig.scala index c1dd8fa6d..d7b490b97 100644 --- a/main-command/src/main/scala/sbt/FileTreeViewConfig.scala +++ b/main-command/src/main/scala/sbt/FileTreeViewConfig.scala @@ -18,15 +18,15 @@ import scala.concurrent.duration._ * Configuration for viewing and monitoring the file system. */ final class FileTreeViewConfig private ( - val newDataView: () => FileTreeDataView[StampedFile], + val newDataView: () => FileTreeDataView[Stamped], val newMonitor: ( - FileTreeDataView[StampedFile], + FileTreeDataView[Stamped], Seq[WatchSource], Logger - ) => FileEventMonitor[StampedFile] + ) => FileEventMonitor[Stamped] ) object FileTreeViewConfig { - private implicit class RepositoryOps(val repository: FileTreeRepository[StampedFile]) { + private implicit class RepositoryOps(val repository: FileTreeRepository[Stamped]) { def register(sources: Seq[WatchSource]): Unit = sources foreach { s => repository.register(s.base.toPath, if (s.recursive) Integer.MAX_VALUE else 0) } @@ -34,10 +34,10 @@ object FileTreeViewConfig { /** * Create a new FileTreeViewConfig. This factory takes a generic parameter, T, that is bounded - * by {{{sbt.io.FileTreeDataView[StampedFile]}}}. The reason for this is to ensure that a + * by {{{sbt.io.FileTreeDataView[Stamped]}}}. The reason for this is to ensure that a * sbt.io.FileTreeDataView that is instantiated by [[FileTreeViewConfig.newDataView]] can be * passed into [[FileTreeViewConfig.newMonitor]] without constraining the type of view to be - * {{{sbt.io.FileTreeDataView[StampedFile]}}}. + * {{{sbt.io.FileTreeDataView[Stamped]}}}. * @param newDataView create a new sbt.io.FileTreeDataView. This value may be cached in a global * attribute * @param newMonitor create a new sbt.io.FileEventMonitor using the sbt.io.FileTreeDataView @@ -45,13 +45,13 @@ object FileTreeViewConfig { * @tparam T the subtype of sbt.io.FileTreeDataView that is returned by [[FileTreeViewConfig.newDataView]] * @return a [[FileTreeViewConfig]] instance. */ - def apply[T <: FileTreeDataView[StampedFile]]( + def apply[T <: FileTreeDataView[Stamped]]( newDataView: () => T, - newMonitor: (T, Seq[WatchSource], Logger) => FileEventMonitor[StampedFile] + newMonitor: (T, Seq[WatchSource], Logger) => FileEventMonitor[Stamped] ): FileTreeViewConfig = new FileTreeViewConfig( newDataView, - (view: FileTreeDataView[StampedFile], sources: Seq[WatchSource], logger: Logger) => + (view: FileTreeDataView[Stamped], sources: Seq[WatchSource], logger: Logger) => newMonitor(view.asInstanceOf[T], sources, logger) ) @@ -70,14 +70,14 @@ object FileTreeViewConfig { antiEntropy: FiniteDuration ): FileTreeViewConfig = FileTreeViewConfig( - () => FileTreeView.DEFAULT.asDataView(StampedFile.converter), - (_: FileTreeDataView[StampedFile], sources, logger) => { + () => FileTreeView.DEFAULT.asDataView(Stamped.converter), + (_: FileTreeDataView[Stamped], sources, logger) => { val ioLogger: sbt.io.WatchLogger = msg => logger.debug(msg.toString) FileEventMonitor.antiEntropy( new WatchServiceBackedObservable( WatchState.empty(Watched.createWatchService(), sources), delay, - StampedFile.converter, + Stamped.converter, closeService = true, ioLogger ), @@ -97,11 +97,11 @@ object FileTreeViewConfig { */ def default(antiEntropy: FiniteDuration): FileTreeViewConfig = FileTreeViewConfig( - () => FileTreeRepository.default(StampedFile.converter), - (repository: FileTreeRepository[StampedFile], sources: Seq[WatchSource], logger: Logger) => { + () => FileTreeRepository.default(Stamped.converter), + (repository: FileTreeRepository[Stamped], sources: Seq[WatchSource], logger: Logger) => { repository.register(sources) - val copied = new Observable[StampedFile] { - override def addObserver(observer: Observer[StampedFile]): Int = + val copied = new Observable[Stamped] { + override def addObserver(observer: Observer[Stamped]): Int = repository.addObserver(observer) override def removeObserver(handle: Int): Unit = repository.removeObserver(handle) override def close(): Unit = {} // Don't close the underlying observable @@ -154,9 +154,9 @@ object FileTreeViewConfig { pollingInterval: FiniteDuration, pollingSources: Seq[WatchSource], ): FileTreeViewConfig = FileTreeViewConfig( - () => FileTreeRepository.hybrid(StampedFile.converter, pollingSources: _*), + () => FileTreeRepository.hybrid(Stamped.converter, pollingSources: _*), ( - repository: HybridPollingFileTreeRepository[StampedFile], + repository: HybridPollingFileTreeRepository[Stamped], sources: Seq[WatchSource], logger: Logger ) => { diff --git a/main-command/src/main/scala/sbt/StampedFile.scala b/main-command/src/main/scala/sbt/Stamped.scala similarity index 65% rename from main-command/src/main/scala/sbt/StampedFile.scala rename to main-command/src/main/scala/sbt/Stamped.scala index 743658bbc..0f2ac87ef 100644 --- a/main-command/src/main/scala/sbt/StampedFile.scala +++ b/main-command/src/main/scala/sbt/Stamped.scala @@ -19,32 +19,32 @@ import xsbti.compile.analysis.Stamp * a cached value that can be read without doing any io. This can be used to improve performance * anywhere where we need to check if files have changed before doing potentially expensive work. */ -trait StampedFile extends File with TypedPath { +trait Stamped extends File with TypedPath { def stamp: Stamp } /** - * Provides converter functions from TypedPath to [[StampedFile]]. + * Provides converter functions from TypedPath to [[Stamped]]. */ -object StampedFile { +object Stamped { /** - * Converts a TypedPath instance to a [[StampedFile]] by calculating the file hash. + * Converts a TypedPath instance to a [[Stamped]] by calculating the file hash. */ - val sourceConverter: TypedPath => StampedFile = - new StampedFileImpl(_: TypedPath, forceLastModified = false) + val sourceConverter: TypedPath => Stamped = + new StampedImpl(_: TypedPath, forceLastModified = false) /** - * Converts a TypedPath instance to a [[StampedFile]] using the last modified time. + * Converts a TypedPath instance to a [[Stamped]] using the last modified time. */ - val binaryConverter: TypedPath => StampedFile = - new StampedFileImpl(_: TypedPath, forceLastModified = true) + val binaryConverter: TypedPath => Stamped = + new StampedImpl(_: TypedPath, forceLastModified = true) /** * A combined convert that converts TypedPath instances representing *.jar and *.class files * using the last modified time and all other files using the file hash. */ - val converter: TypedPath => StampedFile = (tp: TypedPath) => + val converter: TypedPath => Stamped = (tp: TypedPath) => tp.toPath.toString match { case s if s.endsWith(".jar") => binaryConverter(tp) case s if s.endsWith(".class") => binaryConverter(tp) @@ -54,13 +54,13 @@ object StampedFile { /** * Adds a default ordering that just delegates to the java.io.File.compareTo method. */ - implicit case object ordering extends Ordering[StampedFile] { - override def compare(left: StampedFile, right: StampedFile): Int = left.compareTo(right) + implicit case object ordering extends Ordering[Stamped] { + override def compare(left: Stamped, right: Stamped): Int = left.compareTo(right) } - private class StampedFileImpl(typedPath: TypedPath, forceLastModified: Boolean) + private class StampedImpl(typedPath: TypedPath, forceLastModified: Boolean) extends java.io.File(typedPath.toPath.toString) - with StampedFile { + with Stamped { override val stamp: Stamp = if (forceLastModified || typedPath.isDirectory) Stamper.forLastModified(this) else Stamper.forHash(this) diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index 16fb1aae1..2369eb768 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -146,7 +146,7 @@ object Watched { private[sbt] def onEvent( sources: Seq[WatchSource], projectSources: Seq[WatchSource] - ): Event[StampedFile] => Watched.Action = + ): Event[Stamped] => Watched.Action = event => if (sources.exists(_.accept(event.entry.typedPath.toPath))) Watched.Trigger else if (projectSources.exists(_.accept(event.entry.typedPath.toPath))) event match { @@ -458,7 +458,7 @@ trait WatchConfig { * * @return an sbt.io.FileEventMonitor instance. */ - def fileEventMonitor: FileEventMonitor[StampedFile] + def fileEventMonitor: FileEventMonitor[Stamped] /** * A function that is periodically invoked to determine whether the watch should stop or @@ -481,7 +481,7 @@ trait WatchConfig { * @param event the detected sbt.io.FileEventMonitor.Event. * @return the next [[Watched.Action Action]] to run. */ - def onWatchEvent(event: Event[StampedFile]): Watched.Action + def onWatchEvent(event: Event[Stamped]): Watched.Action /** * Transforms the state after the watch terminates. @@ -537,10 +537,10 @@ object WatchConfig { */ def default( logger: Logger, - fileEventMonitor: FileEventMonitor[StampedFile], + fileEventMonitor: FileEventMonitor[Stamped], handleInput: InputStream => Watched.Action, preWatch: (Int, Boolean) => Watched.Action, - onWatchEvent: Event[StampedFile] => Watched.Action, + onWatchEvent: Event[Stamped] => Watched.Action, onWatchTerminated: (Watched.Action, String, State) => State, triggeredMessage: (TypedPath, Int) => Option[String], watchingMessage: Int => Option[String] @@ -555,11 +555,11 @@ object WatchConfig { val wm = watchingMessage new WatchConfig { override def logger: Logger = l - override def fileEventMonitor: FileEventMonitor[StampedFile] = fem + override def fileEventMonitor: FileEventMonitor[Stamped] = fem override def handleInput(inputStream: InputStream): Watched.Action = hi(inputStream) override def preWatch(count: Int, lastResult: Boolean): Watched.Action = pw(count, lastResult) - override def onWatchEvent(event: Event[StampedFile]): Watched.Action = owe(event) + override def onWatchEvent(event: Event[Stamped]): Watched.Action = owe(event) override def onWatchTerminated(action: Watched.Action, command: String, state: State): State = owt(action, command, state) override def triggeredMessage(typedPath: TypedPath, count: Int): Option[String] = diff --git a/main-command/src/test/scala/sbt/WatchedSpec.scala b/main-command/src/test/scala/sbt/WatchedSpec.scala index 5615a5dd8..9ff04c683 100644 --- a/main-command/src/test/scala/sbt/WatchedSpec.scala +++ b/main-command/src/test/scala/sbt/WatchedSpec.scala @@ -26,11 +26,11 @@ class WatchedSpec extends FlatSpec with Matchers { private val fileTreeViewConfig = FileTreeViewConfig.default(50.millis) def config( sources: Seq[WatchSource], - fileEventMonitor: Option[FileEventMonitor[StampedFile]] = None, + fileEventMonitor: Option[FileEventMonitor[Stamped]] = None, logger: Logger = NullLogger, handleInput: InputStream => Action = _ => Ignore, preWatch: (Int, Boolean) => Action = (_, _) => CancelWatch, - onWatchEvent: Event[StampedFile] => Action = _ => Ignore, + onWatchEvent: Event[Stamped] => Action = _ => Ignore, triggeredMessage: (TypedPath, Int) => Option[String] = (_, _) => None, watchingMessage: Int => Option[String] = _ => None ): WatchConfig = { diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index f5749a935..43127b399 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -274,7 +274,7 @@ object Defaults extends BuildCommon { fileTreeViewConfig := FileManagement.defaultFileTreeView.value, fileTreeView := state.value .get(BasicKeys.globalFileTreeView) - .getOrElse(FileTreeView.DEFAULT.asDataView(StampedFile.converter)), + .getOrElse(FileTreeView.DEFAULT.asDataView(Stamped.converter)), externalHooks := { val view = fileTreeView.value compileOptions => diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 6696e9171..54c3b18bd 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -93,14 +93,14 @@ object Keys { @deprecated("This is no longer used for continuous execution", "1.3.0") val watch = SettingKey(BasicKeys.watch) 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 fileTreeView = taskKey[FileTreeDataView[Stamped]]("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) val watchHandleInput = settingKey[InputStream => Watched.Action]("Function that is periodically invoked to determine if the continous build should be stopped or if a build should be triggered. It will usually read from stdin to respond to user commands.").withRank(BMinusSetting) - val watchOnEvent = taskKey[Event[StampedFile] => Watched.Action]("Determines how to handle a file event").withRank(BMinusSetting) + val watchOnEvent = taskKey[Event[Stamped] => Watched.Action]("Determines how to handle a file event").withRank(BMinusSetting) val watchOnTermination = taskKey[(Watched.Action, String, State) => State]("Transforms the input state after the continuous build completes.").withRank(BMinusSetting) val watchService = settingKey[() => WatchService]("Service to use to monitor file system changes.").withRank(BMinusSetting) val watchProjectSources = taskKey[Seq[Watched.WatchSource]]("Defines the sources for the sbt meta project to watch to trigger a reload.").withRank(CSetting) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 8241d5b50..10fd4951f 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -862,7 +862,7 @@ object BuiltinCommands { () } val (_, config: FileTreeViewConfig) = extracted.runTask(Keys.fileTreeViewConfig, s) - val view: FileTreeDataView[StampedFile] = config.newDataView() + val view: FileTreeDataView[Stamped] = config.newDataView() val newState = s.addExitHook(cleanup()) cleanup() newState diff --git a/main/src/main/scala/sbt/internal/ExternalHooks.scala b/main/src/main/scala/sbt/internal/ExternalHooks.scala index 143e3b850..c6fe0302e 100644 --- a/main/src/main/scala/sbt/internal/ExternalHooks.scala +++ b/main/src/main/scala/sbt/internal/ExternalHooks.scala @@ -9,7 +9,7 @@ package sbt.internal import java.nio.file.Paths import java.util.Optional -import sbt.StampedFile +import sbt.Stamped import sbt.internal.inc.ExternalLookup import sbt.io.syntax.File import sbt.io.{ FileTreeRepository, FileTreeDataView, TypedPath } @@ -20,17 +20,17 @@ import scala.collection.mutable private[sbt] object ExternalHooks { private val javaHome = Option(System.getProperty("java.home")).map(Paths.get(_)) - def apply(options: CompileOptions, view: FileTreeDataView[StampedFile]): DefaultExternalHooks = { + def apply(options: CompileOptions, view: FileTreeDataView[Stamped]): DefaultExternalHooks = { import scala.collection.JavaConverters._ val sources = options.sources() val cachedSources = new java.util.HashMap[File, Stamp] - val converter: File => Stamp = f => StampedFile.sourceConverter(TypedPath(f.toPath)).stamp + val converter: File => Stamp = f => Stamped.sourceConverter(TypedPath(f.toPath)).stamp sources.foreach { - case sf: StampedFile => cachedSources.put(sf, sf.stamp) - case f: File => cachedSources.put(f, converter(f)) + case sf: Stamped => cachedSources.put(sf, sf.stamp) + case f: File => cachedSources.put(f, converter(f)) } view match { - case r: FileTreeRepository[StampedFile] => + case r: FileTreeRepository[Stamped] => r.register(options.classesDirectory.toPath, Integer.MAX_VALUE) options.classpath.foreach { f => r.register(f.toPath, Integer.MAX_VALUE) diff --git a/main/src/main/scala/sbt/internal/FileManagement.scala b/main/src/main/scala/sbt/internal/FileManagement.scala index ec6eb2b8c..7932d0124 100644 --- a/main/src/main/scala/sbt/internal/FileManagement.scala +++ b/main/src/main/scala/sbt/internal/FileManagement.scala @@ -51,7 +51,7 @@ private[sbt] object FileManagement { val view = fileTreeView.value val include = filter.toTask.value val ex = excludes.toTask.value - val sourceFilter: Entry[StampedFile] => Boolean = (entry: Entry[StampedFile]) => { + val sourceFilter: Entry[Stamped] => Boolean = (entry: Entry[Stamped]) => { entry.value match { case Right(sf) => include.accept(sf) && !ex.accept(sf) case _ => false From ba0494df1466128913cbbd0f425edc77f832edf8 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Thu, 31 Jan 2019 18:08:09 -0800 Subject: [PATCH 4/5] Stop Stamped from inheriting File and TypedPath In the FileTreeDataView use case, we were previously working with FileTreeDataView[Stamped], which actually contained a lot of redundant information because FileTreeDataView.Entry[_] has a toTypedPath method that could be used to read the path related fields in Stamped. Instead, we can just return the Stamp itself in FileTreeDataView.list* methods and convert to Stamped.File where needed (i.e. in ExternalHooks). Also move BasicKeys.globalFileTreeView to Keys since it isn't actually used in the main-command project. --- .../src/main/scala/sbt/BasicKeys.scala | 6 +--- .../main/scala/sbt/FileTreeViewConfig.scala | 29 +++++++-------- main-command/src/main/scala/sbt/Stamped.scala | 35 ++++++++++--------- main-command/src/main/scala/sbt/Watched.scala | 21 +++++------ .../src/test/scala/sbt/WatchedSpec.scala | 5 +-- main/src/main/scala/sbt/Defaults.scala | 2 +- main/src/main/scala/sbt/Keys.scala | 10 ++++-- main/src/main/scala/sbt/Main.scala | 9 ++--- .../scala/sbt/internal/ExternalHooks.scala | 10 +++--- .../scala/sbt/internal/FileManagement.scala | 15 ++++---- 10 files changed, 76 insertions(+), 66 deletions(-) diff --git a/main-command/src/main/scala/sbt/BasicKeys.scala b/main-command/src/main/scala/sbt/BasicKeys.scala index 6b9b13171..153ca1747 100644 --- a/main-command/src/main/scala/sbt/BasicKeys.scala +++ b/main-command/src/main/scala/sbt/BasicKeys.scala @@ -15,6 +15,7 @@ import sbt.internal.server.ServerHandler import sbt.io.FileTreeDataView import sbt.librarymanagement.ModuleID import sbt.util.Level +import xsbti.compile.analysis.Stamp object BasicKeys { val historyPath = AttributeKey[Option[File]]( @@ -102,11 +103,6 @@ object BasicKeys { "List of template resolver infos.", 1000 ) - private[sbt] val globalFileTreeView = AttributeKey[FileTreeDataView[Stamped]]( - "globalFileTreeView", - "provides a view into the file system that may or may not cache the tree in memory", - 1000 - ) } case class TemplateResolverInfo(module: ModuleID, implementationClass: String) diff --git a/main-command/src/main/scala/sbt/FileTreeViewConfig.scala b/main-command/src/main/scala/sbt/FileTreeViewConfig.scala index d7b490b97..c79311345 100644 --- a/main-command/src/main/scala/sbt/FileTreeViewConfig.scala +++ b/main-command/src/main/scala/sbt/FileTreeViewConfig.scala @@ -11,6 +11,7 @@ import sbt.internal.io.{ HybridPollingFileTreeRepository, WatchServiceBackedObse import sbt.io._ import FileTreeDataView.{ Observable, Observer } import sbt.util.Logger +import xsbti.compile.analysis.Stamp import scala.concurrent.duration._ @@ -18,15 +19,15 @@ import scala.concurrent.duration._ * Configuration for viewing and monitoring the file system. */ final class FileTreeViewConfig private ( - val newDataView: () => FileTreeDataView[Stamped], + val newDataView: () => FileTreeDataView[Stamp], val newMonitor: ( - FileTreeDataView[Stamped], + FileTreeDataView[Stamp], Seq[WatchSource], Logger - ) => FileEventMonitor[Stamped] + ) => FileEventMonitor[Stamp] ) object FileTreeViewConfig { - private implicit class RepositoryOps(val repository: FileTreeRepository[Stamped]) { + private implicit class RepositoryOps(val repository: FileTreeRepository[Stamp]) { def register(sources: Seq[WatchSource]): Unit = sources foreach { s => repository.register(s.base.toPath, if (s.recursive) Integer.MAX_VALUE else 0) } @@ -34,10 +35,10 @@ object FileTreeViewConfig { /** * Create a new FileTreeViewConfig. This factory takes a generic parameter, T, that is bounded - * by {{{sbt.io.FileTreeDataView[Stamped]}}}. The reason for this is to ensure that a + * by {{{sbt.io.FileTreeDataView[Stamp]}}}. The reason for this is to ensure that a * sbt.io.FileTreeDataView that is instantiated by [[FileTreeViewConfig.newDataView]] can be * passed into [[FileTreeViewConfig.newMonitor]] without constraining the type of view to be - * {{{sbt.io.FileTreeDataView[Stamped]}}}. + * {{{sbt.io.FileTreeDataView[Stamp]}}}. * @param newDataView create a new sbt.io.FileTreeDataView. This value may be cached in a global * attribute * @param newMonitor create a new sbt.io.FileEventMonitor using the sbt.io.FileTreeDataView @@ -45,13 +46,13 @@ object FileTreeViewConfig { * @tparam T the subtype of sbt.io.FileTreeDataView that is returned by [[FileTreeViewConfig.newDataView]] * @return a [[FileTreeViewConfig]] instance. */ - def apply[T <: FileTreeDataView[Stamped]]( + def apply[T <: FileTreeDataView[Stamp]]( newDataView: () => T, - newMonitor: (T, Seq[WatchSource], Logger) => FileEventMonitor[Stamped] + newMonitor: (T, Seq[WatchSource], Logger) => FileEventMonitor[Stamp] ): FileTreeViewConfig = new FileTreeViewConfig( newDataView, - (view: FileTreeDataView[Stamped], sources: Seq[WatchSource], logger: Logger) => + (view: FileTreeDataView[Stamp], sources: Seq[WatchSource], logger: Logger) => newMonitor(view.asInstanceOf[T], sources, logger) ) @@ -71,7 +72,7 @@ object FileTreeViewConfig { ): FileTreeViewConfig = FileTreeViewConfig( () => FileTreeView.DEFAULT.asDataView(Stamped.converter), - (_: FileTreeDataView[Stamped], sources, logger) => { + (_: FileTreeDataView[Stamp], sources, logger) => { val ioLogger: sbt.io.WatchLogger = msg => logger.debug(msg.toString) FileEventMonitor.antiEntropy( new WatchServiceBackedObservable( @@ -98,10 +99,10 @@ object FileTreeViewConfig { def default(antiEntropy: FiniteDuration): FileTreeViewConfig = FileTreeViewConfig( () => FileTreeRepository.default(Stamped.converter), - (repository: FileTreeRepository[Stamped], sources: Seq[WatchSource], logger: Logger) => { + (repository: FileTreeRepository[Stamp], sources: Seq[WatchSource], logger: Logger) => { repository.register(sources) - val copied = new Observable[Stamped] { - override def addObserver(observer: Observer[Stamped]): Int = + val copied = new Observable[Stamp] { + override def addObserver(observer: Observer[Stamp]): Int = repository.addObserver(observer) override def removeObserver(handle: Int): Unit = repository.removeObserver(handle) override def close(): Unit = {} // Don't close the underlying observable @@ -156,7 +157,7 @@ object FileTreeViewConfig { ): FileTreeViewConfig = FileTreeViewConfig( () => FileTreeRepository.hybrid(Stamped.converter, pollingSources: _*), ( - repository: HybridPollingFileTreeRepository[Stamped], + repository: HybridPollingFileTreeRepository[Stamp], sources: Seq[WatchSource], logger: Logger ) => { diff --git a/main-command/src/main/scala/sbt/Stamped.scala b/main-command/src/main/scala/sbt/Stamped.scala index 0f2ac87ef..e7a03e2b9 100644 --- a/main-command/src/main/scala/sbt/Stamped.scala +++ b/main-command/src/main/scala/sbt/Stamped.scala @@ -7,7 +7,7 @@ package sbt -import java.io.File +import java.io.{ File => JFile } import java.nio.file.Path import sbt.internal.inc.Stamper @@ -15,11 +15,12 @@ import sbt.io.TypedPath import xsbti.compile.analysis.Stamp /** - * A File that has a [[Stamp]] value associated with it. In general, the stamp method should be - * a cached value that can be read without doing any io. This can be used to improve performance - * anywhere where we need to check if files have changed before doing potentially expensive work. + * A File that has a compile analysis Stamp value associated with it. In general, the stamp method + * should be a cached value that can be read without doing any io. This can be used to improve + * performance anywhere where we need to check if files have changed before doing potentially + * expensive work. */ -trait Stamped extends File with TypedPath { +trait Stamped { def stamp: Stamp } @@ -27,24 +28,25 @@ trait Stamped extends File with TypedPath { * Provides converter functions from TypedPath to [[Stamped]]. */ object Stamped { + type File = JFile with Stamped with TypedPath + def file(typedPath: TypedPath, stamp: Stamp): JFile with Stamped with TypedPath = + new StampedFileImpl(typedPath, stamp) /** * Converts a TypedPath instance to a [[Stamped]] by calculating the file hash. */ - val sourceConverter: TypedPath => Stamped = - new StampedImpl(_: TypedPath, forceLastModified = false) + val sourceConverter: TypedPath => Stamp = tp => Stamper.forHash(tp.toPath.toFile) /** * Converts a TypedPath instance to a [[Stamped]] using the last modified time. */ - val binaryConverter: TypedPath => Stamped = - new StampedImpl(_: TypedPath, forceLastModified = true) + val binaryConverter: TypedPath => Stamp = tp => Stamper.forLastModified(tp.toPath.toFile) /** * A combined convert that converts TypedPath instances representing *.jar and *.class files * using the last modified time and all other files using the file hash. */ - val converter: TypedPath => Stamped = (tp: TypedPath) => + val converter: TypedPath => Stamp = (tp: TypedPath) => tp.toPath.toString match { case s if s.endsWith(".jar") => binaryConverter(tp) case s if s.endsWith(".class") => binaryConverter(tp) @@ -54,16 +56,15 @@ object Stamped { /** * Adds a default ordering that just delegates to the java.io.File.compareTo method. */ - implicit case object ordering extends Ordering[Stamped] { - override def compare(left: Stamped, right: Stamped): Int = left.compareTo(right) + implicit case object ordering extends Ordering[Stamped.File] { + override def compare(left: Stamped.File, right: Stamped.File): Int = left.compareTo(right) } - private class StampedImpl(typedPath: TypedPath, forceLastModified: Boolean) + private final class StampedImpl(override val stamp: Stamp) extends Stamped + private final class StampedFileImpl(typedPath: TypedPath, override val stamp: Stamp) extends java.io.File(typedPath.toPath.toString) - with Stamped { - override val stamp: Stamp = - if (forceLastModified || typedPath.isDirectory) Stamper.forLastModified(this) - else Stamper.forHash(this) + with Stamped + with TypedPath { override def exists: Boolean = typedPath.exists override def isDirectory: Boolean = typedPath.isDirectory override def isFile: Boolean = typedPath.isFile diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index 2369eb768..3afbacb31 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -26,6 +26,7 @@ import sbt.internal.util.{ AttributeKey, JLine } import sbt.io.FileEventMonitor.{ Creation, Deletion, Event, Update } import sbt.io._ import sbt.util.{ Level, Logger } +import xsbti.compile.analysis.Stamp import scala.annotation.tailrec import scala.concurrent.duration._ @@ -146,13 +147,13 @@ object Watched { private[sbt] def onEvent( sources: Seq[WatchSource], projectSources: Seq[WatchSource] - ): Event[Stamped] => Watched.Action = + ): Event[Stamp] => Watched.Action = event => if (sources.exists(_.accept(event.entry.typedPath.toPath))) Watched.Trigger else if (projectSources.exists(_.accept(event.entry.typedPath.toPath))) event match { - case Update(prev, cur, _) if prev.value.map(_.stamp) != cur.value.map(_.stamp) => Reload - case _: Creation[_] | _: Deletion[_] => Reload - case _ => Ignore + case Update(prev, cur, _) if prev.value != cur.value => Reload + case _: Creation[_] | _: Deletion[_] => Reload + case _ => Ignore } else Ignore private[this] val reRun = if (isWin) "" else " or 'r' to re-run the command" @@ -458,7 +459,7 @@ trait WatchConfig { * * @return an sbt.io.FileEventMonitor instance. */ - def fileEventMonitor: FileEventMonitor[Stamped] + def fileEventMonitor: FileEventMonitor[Stamp] /** * A function that is periodically invoked to determine whether the watch should stop or @@ -481,7 +482,7 @@ trait WatchConfig { * @param event the detected sbt.io.FileEventMonitor.Event. * @return the next [[Watched.Action Action]] to run. */ - def onWatchEvent(event: Event[Stamped]): Watched.Action + def onWatchEvent(event: Event[Stamp]): Watched.Action /** * Transforms the state after the watch terminates. @@ -537,10 +538,10 @@ object WatchConfig { */ def default( logger: Logger, - fileEventMonitor: FileEventMonitor[Stamped], + fileEventMonitor: FileEventMonitor[Stamp], handleInput: InputStream => Watched.Action, preWatch: (Int, Boolean) => Watched.Action, - onWatchEvent: Event[Stamped] => Watched.Action, + onWatchEvent: Event[Stamp] => Watched.Action, onWatchTerminated: (Watched.Action, String, State) => State, triggeredMessage: (TypedPath, Int) => Option[String], watchingMessage: Int => Option[String] @@ -555,11 +556,11 @@ object WatchConfig { val wm = watchingMessage new WatchConfig { override def logger: Logger = l - override def fileEventMonitor: FileEventMonitor[Stamped] = fem + override def fileEventMonitor: FileEventMonitor[Stamp] = fem override def handleInput(inputStream: InputStream): Watched.Action = hi(inputStream) override def preWatch(count: Int, lastResult: Boolean): Watched.Action = pw(count, lastResult) - override def onWatchEvent(event: Event[Stamped]): Watched.Action = owe(event) + override def onWatchEvent(event: Event[Stamp]): Watched.Action = owe(event) override def onWatchTerminated(action: Watched.Action, command: String, state: State): State = owt(action, command, state) override def triggeredMessage(typedPath: TypedPath, count: Int): Option[String] = diff --git a/main-command/src/test/scala/sbt/WatchedSpec.scala b/main-command/src/test/scala/sbt/WatchedSpec.scala index 9ff04c683..3f41ecea7 100644 --- a/main-command/src/test/scala/sbt/WatchedSpec.scala +++ b/main-command/src/test/scala/sbt/WatchedSpec.scala @@ -17,6 +17,7 @@ import sbt.WatchedSpec._ import sbt.io.FileEventMonitor.Event import sbt.io.{ FileEventMonitor, IO, TypedPath } import sbt.util.Logger +import xsbti.compile.analysis.Stamp import scala.collection.mutable import scala.concurrent.duration._ @@ -26,11 +27,11 @@ class WatchedSpec extends FlatSpec with Matchers { private val fileTreeViewConfig = FileTreeViewConfig.default(50.millis) def config( sources: Seq[WatchSource], - fileEventMonitor: Option[FileEventMonitor[Stamped]] = None, + fileEventMonitor: Option[FileEventMonitor[Stamp]] = None, logger: Logger = NullLogger, handleInput: InputStream => Action = _ => Ignore, preWatch: (Int, Boolean) => Action = (_, _) => CancelWatch, - onWatchEvent: Event[Stamped] => Action = _ => Ignore, + onWatchEvent: Event[Stamp] => Action = _ => Ignore, triggeredMessage: (TypedPath, Int) => Option[String] = (_, _) => None, watchingMessage: Int => Option[String] = _ => None ): WatchConfig = { diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 43127b399..c9679e35f 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -273,7 +273,7 @@ object Defaults extends BuildCommon { watchStartMessage := Watched.defaultStartWatch, fileTreeViewConfig := FileManagement.defaultFileTreeView.value, fileTreeView := state.value - .get(BasicKeys.globalFileTreeView) + .get(Keys.globalFileTreeView) .getOrElse(FileTreeView.DEFAULT.asDataView(Stamped.converter)), externalHooks := { val view = fileTreeView.value diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 54c3b18bd..58339fcee 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -31,6 +31,7 @@ import sbt.librarymanagement.ivy.{ Credentials, IvyConfiguration, IvyPaths, Upda import sbt.testing.Framework import sbt.util.{ Level, Logger } import xsbti.compile._ +import xsbti.compile.analysis.Stamp import scala.concurrent.duration.{ Duration, FiniteDuration } import scala.xml.{ NodeSeq, Node => XNode } @@ -93,14 +94,14 @@ object Keys { @deprecated("This is no longer used for continuous execution", "1.3.0") val watch = SettingKey(BasicKeys.watch) val suppressSbtShellNotification = settingKey[Boolean]("""True to suppress the "Executing in batch mode.." message.""").withRank(CSetting) - val fileTreeView = taskKey[FileTreeDataView[Stamped]]("A view of the file system") + val fileTreeView = taskKey[FileTreeDataView[Stamp]]("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) val watchHandleInput = settingKey[InputStream => Watched.Action]("Function that is periodically invoked to determine if the continous build should be stopped or if a build should be triggered. It will usually read from stdin to respond to user commands.").withRank(BMinusSetting) - val watchOnEvent = taskKey[Event[Stamped] => Watched.Action]("Determines how to handle a file event").withRank(BMinusSetting) + val watchOnEvent = taskKey[Event[Stamp] => Watched.Action]("Determines how to handle a file event").withRank(BMinusSetting) val watchOnTermination = taskKey[(Watched.Action, String, State) => State]("Transforms the input state after the continuous build completes.").withRank(BMinusSetting) val watchService = settingKey[() => WatchService]("Service to use to monitor file system changes.").withRank(BMinusSetting) val watchProjectSources = taskKey[Seq[Watched.WatchSource]]("Defines the sources for the sbt meta project to watch to trigger a reload.").withRank(CSetting) @@ -456,6 +457,11 @@ object Keys { val (executionRoots, dummyRoots) = Def.dummy[Seq[ScopedKey[_]]]("executionRoots", "The list of root tasks for this task execution. Roots are the top-level tasks that were directly requested to be run.") val state = Def.stateKey val streamsManager = Def.streamsManagerKey + private[sbt] val globalFileTreeView = AttributeKey[FileTreeDataView[Stamp]]( + "globalFileTreeView", + "Provides a view into the file system that may or may not cache the tree in memory", + 1000 + ) val stateStreams = AttributeKey[Streams]("stateStreams", "Streams manager, which provides streams for different contexts. Setting this on State will override the default Streams implementation.") val resolvedScoped = Def.resolvedScoped diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 10fd4951f..69ad7f25a 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -25,6 +25,7 @@ import sbt.io.syntax._ import sbt.io.{ FileTreeDataView, IO } import sbt.util.{ Level, Logger, Show } import xsbti.compile.CompilerCache +import xsbti.compile.analysis.Stamp import scala.annotation.tailrec import scala.concurrent.ExecutionContext @@ -855,18 +856,18 @@ object BuiltinCommands { val extracted = Project.extract(s) try { def cleanup(): Unit = { - s.get(BasicKeys.globalFileTreeView).foreach(_.close()) - s.attributes.remove(BasicKeys.globalFileTreeView) + s.get(Keys.globalFileTreeView).foreach(_.close()) + s.attributes.remove(Keys.globalFileTreeView) s.get(Keys.taskRepository).foreach(_.close()) s.attributes.remove(Keys.taskRepository) () } val (_, config: FileTreeViewConfig) = extracted.runTask(Keys.fileTreeViewConfig, s) - val view: FileTreeDataView[Stamped] = config.newDataView() + val view: FileTreeDataView[Stamp] = config.newDataView() val newState = s.addExitHook(cleanup()) cleanup() newState - .put(BasicKeys.globalFileTreeView, view) + .put(Keys.globalFileTreeView, view) .put(Keys.taskRepository, new TaskRepository.Repr) } catch { case NonFatal(_) => s diff --git a/main/src/main/scala/sbt/internal/ExternalHooks.scala b/main/src/main/scala/sbt/internal/ExternalHooks.scala index c6fe0302e..177591411 100644 --- a/main/src/main/scala/sbt/internal/ExternalHooks.scala +++ b/main/src/main/scala/sbt/internal/ExternalHooks.scala @@ -20,17 +20,17 @@ import scala.collection.mutable private[sbt] object ExternalHooks { private val javaHome = Option(System.getProperty("java.home")).map(Paths.get(_)) - def apply(options: CompileOptions, view: FileTreeDataView[Stamped]): DefaultExternalHooks = { + def apply(options: CompileOptions, view: FileTreeDataView[Stamp]): DefaultExternalHooks = { import scala.collection.JavaConverters._ val sources = options.sources() val cachedSources = new java.util.HashMap[File, Stamp] - val converter: File => Stamp = f => Stamped.sourceConverter(TypedPath(f.toPath)).stamp + val converter: File => Stamp = f => Stamped.sourceConverter(TypedPath(f.toPath)) sources.foreach { case sf: Stamped => cachedSources.put(sf, sf.stamp) case f: File => cachedSources.put(f, converter(f)) } view match { - case r: FileTreeRepository[Stamped] => + case r: FileTreeRepository[Stamp] => r.register(options.classesDirectory.toPath, Integer.MAX_VALUE) options.classpath.foreach { f => r.register(f.toPath, Integer.MAX_VALUE) @@ -41,7 +41,7 @@ private[sbt] object ExternalHooks { options.classpath.foreach { f => view.listEntries(f.toPath, Integer.MAX_VALUE, _ => true) foreach { e => e.value match { - case Right(value) => allBinaries.put(e.typedPath.toPath.toFile, value.stamp) + case Right(value) => allBinaries.put(e.typedPath.toPath.toFile, value) case _ => } } @@ -49,7 +49,7 @@ private[sbt] object ExternalHooks { // rather than a directory. view.listEntries(f.toPath, -1, _ => true) foreach { e => e.value match { - case Right(value) => allBinaries.put(e.typedPath.toPath.toFile, value.stamp) + case Right(value) => allBinaries.put(e.typedPath.toPath.toFile, value) case _ => } } diff --git a/main/src/main/scala/sbt/internal/FileManagement.scala b/main/src/main/scala/sbt/internal/FileManagement.scala index 7932d0124..4a3bb7451 100644 --- a/main/src/main/scala/sbt/internal/FileManagement.scala +++ b/main/src/main/scala/sbt/internal/FileManagement.scala @@ -16,6 +16,7 @@ import sbt.io.syntax.File import sbt.io.{ FileFilter, FileTreeDataView, FileTreeRepository } import sbt._ import BasicCommandStrings.ContinuousExecutePrefix +import xsbti.compile.analysis.Stamp private[sbt] object FileManagement { private[sbt] def defaultFileTreeView: Def.Initialize[Task[FileTreeViewConfig]] = Def.task { @@ -51,17 +52,19 @@ private[sbt] object FileManagement { val view = fileTreeView.value val include = filter.toTask.value val ex = excludes.toTask.value - val sourceFilter: Entry[Stamped] => Boolean = (entry: Entry[Stamped]) => { - entry.value match { - case Right(sf) => include.accept(sf) && !ex.accept(sf) - case _ => false + val sourceFilter: Entry[Stamp] => Boolean = (entry: Entry[Stamp]) => { + val typedPath = entry.typedPath + val file = new java.io.File(typedPath.toPath.toString) { + override def isDirectory: Boolean = typedPath.isDirectory + override def isFile: Boolean = typedPath.isFile } + include.accept(file) && !ex.accept(file) } sourceDirs.flatMap { dir => view.register(dir.toPath, maxDepth = Integer.MAX_VALUE) view .listEntries(dir.toPath, maxDepth = Integer.MAX_VALUE, sourceFilter) - .map(e => e.value.getOrElse(e.typedPath.toPath.toFile)) + .flatMap(e => e.value.toOption.map(Stamped.file(e.typedPath, _))) } } @@ -93,7 +96,7 @@ private[sbt] object FileManagement { f.accept(file) && !excl.accept(file) } ) - .flatMap(_.value.toOption) + .flatMap(e => e.value.toOption.map(Stamped.file(e.typedPath, _))) } else sources } ) From e8af828c7307e6fd71a0d037429aca67bf807209 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Tue, 4 Dec 2018 11:04:25 -0800 Subject: [PATCH 5/5] Add FileCacheEntry Previously, we were leaking the internal details of incremental compilation to users by defining FileTree(DataView|Repository)[Stamp]. To avoid this, I introduce the new class FileCacheEntry that is quite similar to Stamp except defined using scala Options rather than java Optionals. The implementation class just delegates to an actual Stamp and I provided a private[sbt] ops class that adds a method `stamp` to FileCacheEntry. This will usually just extract the stamp from the implementation class. This allows us to use FileCacheEntry almost interchangeably with Stamp while still avoiding exposing users to Stamp. --- .../src/main/scala/sbt/BasicKeys.scala | 4 +- .../main/scala/sbt/FileTreeViewConfig.scala | 44 +++++++------ main-command/src/main/scala/sbt/Stamped.scala | 22 ++++--- main-command/src/main/scala/sbt/Watched.scala | 17 +++-- .../scala/sbt/internal/FileCacheEntry.scala | 62 +++++++++++++++++++ .../src/test/scala/sbt/WatchedSpec.scala | 6 +- main/src/main/scala/sbt/Defaults.scala | 2 +- main/src/main/scala/sbt/Keys.scala | 7 +-- main/src/main/scala/sbt/Main.scala | 3 +- .../scala/sbt/internal/ExternalHooks.scala | 11 ++-- .../scala/sbt/internal/FileManagement.scala | 7 +-- 11 files changed, 126 insertions(+), 59 deletions(-) create mode 100644 main-command/src/main/scala/sbt/internal/FileCacheEntry.scala diff --git a/main-command/src/main/scala/sbt/BasicKeys.scala b/main-command/src/main/scala/sbt/BasicKeys.scala index 153ca1747..b4ab63054 100644 --- a/main-command/src/main/scala/sbt/BasicKeys.scala +++ b/main-command/src/main/scala/sbt/BasicKeys.scala @@ -9,13 +9,11 @@ package sbt import java.io.File -import sbt.internal.util.AttributeKey import sbt.internal.inc.classpath.ClassLoaderCache import sbt.internal.server.ServerHandler -import sbt.io.FileTreeDataView +import sbt.internal.util.AttributeKey import sbt.librarymanagement.ModuleID import sbt.util.Level -import xsbti.compile.analysis.Stamp object BasicKeys { val historyPath = AttributeKey[Option[File]]( diff --git a/main-command/src/main/scala/sbt/FileTreeViewConfig.scala b/main-command/src/main/scala/sbt/FileTreeViewConfig.scala index c79311345..4ec9359c4 100644 --- a/main-command/src/main/scala/sbt/FileTreeViewConfig.scala +++ b/main-command/src/main/scala/sbt/FileTreeViewConfig.scala @@ -7,11 +7,11 @@ package sbt import sbt.Watched.WatchSource +import sbt.internal.FileCacheEntry import sbt.internal.io.{ HybridPollingFileTreeRepository, WatchServiceBackedObservable, WatchState } +import sbt.io.FileTreeDataView.{ Observable, Observer } import sbt.io._ -import FileTreeDataView.{ Observable, Observer } import sbt.util.Logger -import xsbti.compile.analysis.Stamp import scala.concurrent.duration._ @@ -19,15 +19,15 @@ import scala.concurrent.duration._ * Configuration for viewing and monitoring the file system. */ final class FileTreeViewConfig private ( - val newDataView: () => FileTreeDataView[Stamp], + val newDataView: () => FileTreeDataView[FileCacheEntry], val newMonitor: ( - FileTreeDataView[Stamp], + FileTreeDataView[FileCacheEntry], Seq[WatchSource], Logger - ) => FileEventMonitor[Stamp] + ) => FileEventMonitor[FileCacheEntry] ) object FileTreeViewConfig { - private implicit class RepositoryOps(val repository: FileTreeRepository[Stamp]) { + private implicit class RepositoryOps(val repository: FileTreeRepository[FileCacheEntry]) { def register(sources: Seq[WatchSource]): Unit = sources foreach { s => repository.register(s.base.toPath, if (s.recursive) Integer.MAX_VALUE else 0) } @@ -35,10 +35,10 @@ object FileTreeViewConfig { /** * Create a new FileTreeViewConfig. This factory takes a generic parameter, T, that is bounded - * by {{{sbt.io.FileTreeDataView[Stamp]}}}. The reason for this is to ensure that a + * by {{{sbt.io.FileTreeDataView[FileCacheEntry]}}}. The reason for this is to ensure that a * sbt.io.FileTreeDataView that is instantiated by [[FileTreeViewConfig.newDataView]] can be * passed into [[FileTreeViewConfig.newMonitor]] without constraining the type of view to be - * {{{sbt.io.FileTreeDataView[Stamp]}}}. + * {{{sbt.io.FileTreeDataView[FileCacheEntry]}}}. * @param newDataView create a new sbt.io.FileTreeDataView. This value may be cached in a global * attribute * @param newMonitor create a new sbt.io.FileEventMonitor using the sbt.io.FileTreeDataView @@ -46,13 +46,13 @@ object FileTreeViewConfig { * @tparam T the subtype of sbt.io.FileTreeDataView that is returned by [[FileTreeViewConfig.newDataView]] * @return a [[FileTreeViewConfig]] instance. */ - def apply[T <: FileTreeDataView[Stamp]]( + def apply[T <: FileTreeDataView[FileCacheEntry]]( newDataView: () => T, - newMonitor: (T, Seq[WatchSource], Logger) => FileEventMonitor[Stamp] + newMonitor: (T, Seq[WatchSource], Logger) => FileEventMonitor[FileCacheEntry] ): FileTreeViewConfig = new FileTreeViewConfig( newDataView, - (view: FileTreeDataView[Stamp], sources: Seq[WatchSource], logger: Logger) => + (view: FileTreeDataView[FileCacheEntry], sources: Seq[WatchSource], logger: Logger) => newMonitor(view.asInstanceOf[T], sources, logger) ) @@ -71,14 +71,14 @@ object FileTreeViewConfig { antiEntropy: FiniteDuration ): FileTreeViewConfig = FileTreeViewConfig( - () => FileTreeView.DEFAULT.asDataView(Stamped.converter), - (_: FileTreeDataView[Stamp], sources, logger) => { + () => FileTreeView.DEFAULT.asDataView(FileCacheEntry.default), + (_: FileTreeDataView[FileCacheEntry], sources, logger) => { val ioLogger: sbt.io.WatchLogger = msg => logger.debug(msg.toString) FileEventMonitor.antiEntropy( new WatchServiceBackedObservable( WatchState.empty(Watched.createWatchService(), sources), delay, - Stamped.converter, + FileCacheEntry.default, closeService = true, ioLogger ), @@ -98,11 +98,15 @@ object FileTreeViewConfig { */ def default(antiEntropy: FiniteDuration): FileTreeViewConfig = FileTreeViewConfig( - () => FileTreeRepository.default(Stamped.converter), - (repository: FileTreeRepository[Stamp], sources: Seq[WatchSource], logger: Logger) => { + () => FileTreeRepository.default(FileCacheEntry.default), + ( + repository: FileTreeRepository[FileCacheEntry], + sources: Seq[WatchSource], + logger: Logger + ) => { repository.register(sources) - val copied = new Observable[Stamp] { - override def addObserver(observer: Observer[Stamp]): Int = + val copied = new Observable[FileCacheEntry] { + override def addObserver(observer: Observer[FileCacheEntry]): Int = repository.addObserver(observer) override def removeObserver(handle: Int): Unit = repository.removeObserver(handle) override def close(): Unit = {} // Don't close the underlying observable @@ -155,9 +159,9 @@ object FileTreeViewConfig { pollingInterval: FiniteDuration, pollingSources: Seq[WatchSource], ): FileTreeViewConfig = FileTreeViewConfig( - () => FileTreeRepository.hybrid(Stamped.converter, pollingSources: _*), + () => FileTreeRepository.hybrid(FileCacheEntry.default, pollingSources: _*), ( - repository: HybridPollingFileTreeRepository[Stamp], + repository: HybridPollingFileTreeRepository[FileCacheEntry], sources: Seq[WatchSource], logger: Logger ) => { diff --git a/main-command/src/main/scala/sbt/Stamped.scala b/main-command/src/main/scala/sbt/Stamped.scala index e7a03e2b9..cf8f63e55 100644 --- a/main-command/src/main/scala/sbt/Stamped.scala +++ b/main-command/src/main/scala/sbt/Stamped.scala @@ -10,6 +10,7 @@ package sbt import java.io.{ File => JFile } import java.nio.file.Path +import sbt.internal.FileCacheEntry import sbt.internal.inc.Stamper import sbt.io.TypedPath import xsbti.compile.analysis.Stamp @@ -20,17 +21,17 @@ import xsbti.compile.analysis.Stamp * performance anywhere where we need to check if files have changed before doing potentially * expensive work. */ -trait Stamped { - def stamp: Stamp +private[sbt] trait Stamped { + private[sbt] def stamp: Stamp } /** * Provides converter functions from TypedPath to [[Stamped]]. */ -object Stamped { +private[sbt] object Stamped { type File = JFile with Stamped with TypedPath - def file(typedPath: TypedPath, stamp: Stamp): JFile with Stamped with TypedPath = - new StampedFileImpl(typedPath, stamp) + def file(typedPath: TypedPath, entry: FileCacheEntry): JFile with Stamped with TypedPath = + new StampedFileImpl(typedPath, entry.stamp) /** * Converts a TypedPath instance to a [[Stamped]] by calculating the file hash. @@ -47,10 +48,13 @@ object Stamped { * using the last modified time and all other files using the file hash. */ val converter: TypedPath => Stamp = (tp: TypedPath) => - tp.toPath.toString match { - case s if s.endsWith(".jar") => binaryConverter(tp) - case s if s.endsWith(".class") => binaryConverter(tp) - case _ => sourceConverter(tp) + if (tp.isDirectory) binaryConverter(tp) + else { + tp.toPath.toString match { + case s if s.endsWith(".jar") => binaryConverter(tp) + case s if s.endsWith(".class") => binaryConverter(tp) + case _ => sourceConverter(tp) + } } /** diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index 3afbacb31..b53f1c3d0 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -18,15 +18,14 @@ import sbt.BasicCommandStrings.{ } import sbt.BasicCommands.otherCommandParser import sbt.internal.LabeledFunctions._ -import sbt.internal.LegacyWatched import sbt.internal.io.{ EventMonitor, Source, WatchState } import sbt.internal.util.Types.const import sbt.internal.util.complete.{ DefaultParsers, Parser } import sbt.internal.util.{ AttributeKey, JLine } +import sbt.internal.{ FileCacheEntry, LegacyWatched } import sbt.io.FileEventMonitor.{ Creation, Deletion, Event, Update } import sbt.io._ import sbt.util.{ Level, Logger } -import xsbti.compile.analysis.Stamp import scala.annotation.tailrec import scala.concurrent.duration._ @@ -147,7 +146,7 @@ object Watched { private[sbt] def onEvent( sources: Seq[WatchSource], projectSources: Seq[WatchSource] - ): Event[Stamp] => Watched.Action = + ): Event[FileCacheEntry] => Watched.Action = event => if (sources.exists(_.accept(event.entry.typedPath.toPath))) Watched.Trigger else if (projectSources.exists(_.accept(event.entry.typedPath.toPath))) event match { @@ -459,7 +458,7 @@ trait WatchConfig { * * @return an sbt.io.FileEventMonitor instance. */ - def fileEventMonitor: FileEventMonitor[Stamp] + def fileEventMonitor: FileEventMonitor[FileCacheEntry] /** * A function that is periodically invoked to determine whether the watch should stop or @@ -482,7 +481,7 @@ trait WatchConfig { * @param event the detected sbt.io.FileEventMonitor.Event. * @return the next [[Watched.Action Action]] to run. */ - def onWatchEvent(event: Event[Stamp]): Watched.Action + def onWatchEvent(event: Event[FileCacheEntry]): Watched.Action /** * Transforms the state after the watch terminates. @@ -538,10 +537,10 @@ object WatchConfig { */ def default( logger: Logger, - fileEventMonitor: FileEventMonitor[Stamp], + fileEventMonitor: FileEventMonitor[FileCacheEntry], handleInput: InputStream => Watched.Action, preWatch: (Int, Boolean) => Watched.Action, - onWatchEvent: Event[Stamp] => Watched.Action, + onWatchEvent: Event[FileCacheEntry] => Watched.Action, onWatchTerminated: (Watched.Action, String, State) => State, triggeredMessage: (TypedPath, Int) => Option[String], watchingMessage: Int => Option[String] @@ -556,11 +555,11 @@ object WatchConfig { val wm = watchingMessage new WatchConfig { override def logger: Logger = l - override def fileEventMonitor: FileEventMonitor[Stamp] = fem + override def fileEventMonitor: FileEventMonitor[FileCacheEntry] = fem override def handleInput(inputStream: InputStream): Watched.Action = hi(inputStream) override def preWatch(count: Int, lastResult: Boolean): Watched.Action = pw(count, lastResult) - override def onWatchEvent(event: Event[Stamp]): Watched.Action = owe(event) + override def onWatchEvent(event: Event[FileCacheEntry]): Watched.Action = owe(event) override def onWatchTerminated(action: Watched.Action, command: String, state: State): State = owt(action, command, state) override def triggeredMessage(typedPath: TypedPath, count: Int): Option[String] = diff --git a/main-command/src/main/scala/sbt/internal/FileCacheEntry.scala b/main-command/src/main/scala/sbt/internal/FileCacheEntry.scala new file mode 100644 index 000000000..417f49136 --- /dev/null +++ b/main-command/src/main/scala/sbt/internal/FileCacheEntry.scala @@ -0,0 +1,62 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt +package internal +import java.lang +import java.util.Optional + +import sbt.internal.inc.{ EmptyStamp, LastModified, Stamp } +import sbt.io.TypedPath +import xsbti.compile.analysis.{ Stamp => XStamp } + +/** + * Represents a cache entry for a FileTreeRepository. It can be extended to add user defined + * data to the FileTreeRepository cache. + */ +trait FileCacheEntry { + def hash: Option[String] + def lastModified: Option[Long] +} +object FileCacheEntry { + def default(typedPath: TypedPath): FileCacheEntry = + DelegateFileCacheEntry(Stamped.converter(typedPath)) + private[sbt] implicit class FileCacheEntryOps(val e: FileCacheEntry) extends AnyVal { + private[sbt] def stamp: XStamp = e match { + case DelegateFileCacheEntry(s) => s + case _ => + e.hash + .map(Stamp.fromString) + .orElse(e.lastModified.map(new LastModified(_))) + .getOrElse(EmptyStamp) + } + } + + private case class DelegateFileCacheEntry(private val stamp: XStamp) + extends FileCacheEntry + with XStamp { + override def getValueId: Int = stamp.getValueId + override def writeStamp(): String = stamp.writeStamp() + override def getHash: Optional[String] = stamp.getHash + override def getLastModified: Optional[lang.Long] = stamp.getLastModified + override def hash: Option[String] = getHash match { + case h if h.isPresent => Some(h.get) + case _ => None + } + override def lastModified: Option[Long] = getLastModified match { + case l if l.isPresent => Some(l.get) + case _ => None + } + override def equals(o: Any): Boolean = o match { + case that: DelegateFileCacheEntry => this.stamp == that.stamp + case that: XStamp => this.stamp == that + case _ => false + } + override def hashCode: Int = stamp.hashCode + override def toString: String = s"FileCacheEntry(hash = $hash, lastModified = $lastModified)" + } +} diff --git a/main-command/src/test/scala/sbt/WatchedSpec.scala b/main-command/src/test/scala/sbt/WatchedSpec.scala index 3f41ecea7..7fac652e0 100644 --- a/main-command/src/test/scala/sbt/WatchedSpec.scala +++ b/main-command/src/test/scala/sbt/WatchedSpec.scala @@ -14,10 +14,10 @@ import java.util.concurrent.atomic.AtomicBoolean import org.scalatest.{ FlatSpec, Matchers } import sbt.Watched._ import sbt.WatchedSpec._ +import sbt.internal.FileCacheEntry import sbt.io.FileEventMonitor.Event import sbt.io.{ FileEventMonitor, IO, TypedPath } import sbt.util.Logger -import xsbti.compile.analysis.Stamp import scala.collection.mutable import scala.concurrent.duration._ @@ -27,11 +27,11 @@ class WatchedSpec extends FlatSpec with Matchers { private val fileTreeViewConfig = FileTreeViewConfig.default(50.millis) def config( sources: Seq[WatchSource], - fileEventMonitor: Option[FileEventMonitor[Stamp]] = None, + fileEventMonitor: Option[FileEventMonitor[FileCacheEntry]] = None, logger: Logger = NullLogger, handleInput: InputStream => Action = _ => Ignore, preWatch: (Int, Boolean) => Action = (_, _) => CancelWatch, - onWatchEvent: Event[Stamp] => Action = _ => Ignore, + onWatchEvent: Event[FileCacheEntry] => Action = _ => Ignore, triggeredMessage: (TypedPath, Int) => Option[String] = (_, _) => None, watchingMessage: Int => Option[String] = _ => None ): WatchConfig = { diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index c9679e35f..2e5fa9347 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -274,7 +274,7 @@ object Defaults extends BuildCommon { fileTreeViewConfig := FileManagement.defaultFileTreeView.value, fileTreeView := state.value .get(Keys.globalFileTreeView) - .getOrElse(FileTreeView.DEFAULT.asDataView(Stamped.converter)), + .getOrElse(FileTreeView.DEFAULT.asDataView(FileCacheEntry.default)), externalHooks := { val view = fileTreeView.value compileOptions => diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 58339fcee..1611025bb 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -31,7 +31,6 @@ import sbt.librarymanagement.ivy.{ Credentials, IvyConfiguration, IvyPaths, Upda import sbt.testing.Framework import sbt.util.{ Level, Logger } import xsbti.compile._ -import xsbti.compile.analysis.Stamp import scala.concurrent.duration.{ Duration, FiniteDuration } import scala.xml.{ NodeSeq, Node => XNode } @@ -94,14 +93,14 @@ object Keys { @deprecated("This is no longer used for continuous execution", "1.3.0") val watch = SettingKey(BasicKeys.watch) val suppressSbtShellNotification = settingKey[Boolean]("""True to suppress the "Executing in batch mode.." message.""").withRank(CSetting) - val fileTreeView = taskKey[FileTreeDataView[Stamp]]("A view of the file system") + val fileTreeView = taskKey[FileTreeDataView[FileCacheEntry]]("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) val watchHandleInput = settingKey[InputStream => Watched.Action]("Function that is periodically invoked to determine if the continous build should be stopped or if a build should be triggered. It will usually read from stdin to respond to user commands.").withRank(BMinusSetting) - val watchOnEvent = taskKey[Event[Stamp] => Watched.Action]("Determines how to handle a file event").withRank(BMinusSetting) + val watchOnEvent = taskKey[Event[FileCacheEntry] => Watched.Action]("Determines how to handle a file event").withRank(BMinusSetting) val watchOnTermination = taskKey[(Watched.Action, String, State) => State]("Transforms the input state after the continuous build completes.").withRank(BMinusSetting) val watchService = settingKey[() => WatchService]("Service to use to monitor file system changes.").withRank(BMinusSetting) val watchProjectSources = taskKey[Seq[Watched.WatchSource]]("Defines the sources for the sbt meta project to watch to trigger a reload.").withRank(CSetting) @@ -457,7 +456,7 @@ object Keys { val (executionRoots, dummyRoots) = Def.dummy[Seq[ScopedKey[_]]]("executionRoots", "The list of root tasks for this task execution. Roots are the top-level tasks that were directly requested to be run.") val state = Def.stateKey val streamsManager = Def.streamsManagerKey - private[sbt] val globalFileTreeView = AttributeKey[FileTreeDataView[Stamp]]( + private[sbt] val globalFileTreeView = AttributeKey[FileTreeDataView[FileCacheEntry]]( "globalFileTreeView", "Provides a view into the file system that may or may not cache the tree in memory", 1000 diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 69ad7f25a..6fecdd1f0 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -25,7 +25,6 @@ import sbt.io.syntax._ import sbt.io.{ FileTreeDataView, IO } import sbt.util.{ Level, Logger, Show } import xsbti.compile.CompilerCache -import xsbti.compile.analysis.Stamp import scala.annotation.tailrec import scala.concurrent.ExecutionContext @@ -863,7 +862,7 @@ object BuiltinCommands { () } val (_, config: FileTreeViewConfig) = extracted.runTask(Keys.fileTreeViewConfig, s) - val view: FileTreeDataView[Stamp] = config.newDataView() + val view: FileTreeDataView[FileCacheEntry] = config.newDataView() val newState = s.addExitHook(cleanup()) cleanup() newState diff --git a/main/src/main/scala/sbt/internal/ExternalHooks.scala b/main/src/main/scala/sbt/internal/ExternalHooks.scala index 177591411..0ef876341 100644 --- a/main/src/main/scala/sbt/internal/ExternalHooks.scala +++ b/main/src/main/scala/sbt/internal/ExternalHooks.scala @@ -20,7 +20,10 @@ import scala.collection.mutable private[sbt] object ExternalHooks { private val javaHome = Option(System.getProperty("java.home")).map(Paths.get(_)) - def apply(options: CompileOptions, view: FileTreeDataView[Stamp]): DefaultExternalHooks = { + def apply( + options: CompileOptions, + view: FileTreeDataView[FileCacheEntry] + ): DefaultExternalHooks = { import scala.collection.JavaConverters._ val sources = options.sources() val cachedSources = new java.util.HashMap[File, Stamp] @@ -30,7 +33,7 @@ private[sbt] object ExternalHooks { case f: File => cachedSources.put(f, converter(f)) } view match { - case r: FileTreeRepository[Stamp] => + case r: FileTreeRepository[FileCacheEntry] => r.register(options.classesDirectory.toPath, Integer.MAX_VALUE) options.classpath.foreach { f => r.register(f.toPath, Integer.MAX_VALUE) @@ -41,7 +44,7 @@ private[sbt] object ExternalHooks { options.classpath.foreach { f => view.listEntries(f.toPath, Integer.MAX_VALUE, _ => true) foreach { e => e.value match { - case Right(value) => allBinaries.put(e.typedPath.toPath.toFile, value) + case Right(value) => allBinaries.put(e.typedPath.toPath.toFile, value.stamp) case _ => } } @@ -49,7 +52,7 @@ private[sbt] object ExternalHooks { // rather than a directory. view.listEntries(f.toPath, -1, _ => true) foreach { e => e.value match { - case Right(value) => allBinaries.put(e.typedPath.toPath.toFile, value) + case Right(value) => allBinaries.put(e.typedPath.toPath.toFile, value.stamp) case _ => } } diff --git a/main/src/main/scala/sbt/internal/FileManagement.scala b/main/src/main/scala/sbt/internal/FileManagement.scala index 4a3bb7451..fdf9c1988 100644 --- a/main/src/main/scala/sbt/internal/FileManagement.scala +++ b/main/src/main/scala/sbt/internal/FileManagement.scala @@ -10,13 +10,12 @@ package sbt.internal import java.io.IOException import java.nio.file.Path +import sbt.BasicCommandStrings.ContinuousExecutePrefix import sbt.Keys._ +import sbt._ import sbt.io.FileTreeDataView.Entry import sbt.io.syntax.File import sbt.io.{ FileFilter, FileTreeDataView, FileTreeRepository } -import sbt._ -import BasicCommandStrings.ContinuousExecutePrefix -import xsbti.compile.analysis.Stamp private[sbt] object FileManagement { private[sbt] def defaultFileTreeView: Def.Initialize[Task[FileTreeViewConfig]] = Def.task { @@ -52,7 +51,7 @@ private[sbt] object FileManagement { val view = fileTreeView.value val include = filter.toTask.value val ex = excludes.toTask.value - val sourceFilter: Entry[Stamp] => Boolean = (entry: Entry[Stamp]) => { + val sourceFilter: Entry[FileCacheEntry] => Boolean = (entry: Entry[FileCacheEntry]) => { val typedPath = entry.typedPath val file = new java.io.File(typedPath.toPath.toString) { override def isDirectory: Boolean = typedPath.isDirectory