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.
This commit is contained in:
Ethan Atkins 2018-12-04 11:04:25 -08:00
parent ba0494df14
commit e8af828c73
11 changed files with 126 additions and 59 deletions

View File

@ -9,13 +9,11 @@ package sbt
import java.io.File import java.io.File
import sbt.internal.util.AttributeKey
import sbt.internal.inc.classpath.ClassLoaderCache import sbt.internal.inc.classpath.ClassLoaderCache
import sbt.internal.server.ServerHandler import sbt.internal.server.ServerHandler
import sbt.io.FileTreeDataView import sbt.internal.util.AttributeKey
import sbt.librarymanagement.ModuleID import sbt.librarymanagement.ModuleID
import sbt.util.Level import sbt.util.Level
import xsbti.compile.analysis.Stamp
object BasicKeys { object BasicKeys {
val historyPath = AttributeKey[Option[File]]( val historyPath = AttributeKey[Option[File]](

View File

@ -7,11 +7,11 @@
package sbt package sbt
import sbt.Watched.WatchSource import sbt.Watched.WatchSource
import sbt.internal.FileCacheEntry
import sbt.internal.io.{ HybridPollingFileTreeRepository, WatchServiceBackedObservable, WatchState } import sbt.internal.io.{ HybridPollingFileTreeRepository, WatchServiceBackedObservable, WatchState }
import sbt.io.FileTreeDataView.{ Observable, Observer }
import sbt.io._ import sbt.io._
import FileTreeDataView.{ Observable, Observer }
import sbt.util.Logger import sbt.util.Logger
import xsbti.compile.analysis.Stamp
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -19,15 +19,15 @@ import scala.concurrent.duration._
* Configuration for viewing and monitoring the file system. * Configuration for viewing and monitoring the file system.
*/ */
final class FileTreeViewConfig private ( final class FileTreeViewConfig private (
val newDataView: () => FileTreeDataView[Stamp], val newDataView: () => FileTreeDataView[FileCacheEntry],
val newMonitor: ( val newMonitor: (
FileTreeDataView[Stamp], FileTreeDataView[FileCacheEntry],
Seq[WatchSource], Seq[WatchSource],
Logger Logger
) => FileEventMonitor[Stamp] ) => FileEventMonitor[FileCacheEntry]
) )
object FileTreeViewConfig { 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 => def register(sources: Seq[WatchSource]): Unit = sources foreach { s =>
repository.register(s.base.toPath, if (s.recursive) Integer.MAX_VALUE else 0) 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 * 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 * sbt.io.FileTreeDataView that is instantiated by [[FileTreeViewConfig.newDataView]] can be
* passed into [[FileTreeViewConfig.newMonitor]] without constraining the type of view to 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 * @param newDataView create a new sbt.io.FileTreeDataView. This value may be cached in a global
* attribute * attribute
* @param newMonitor create a new sbt.io.FileEventMonitor using the sbt.io.FileTreeDataView * @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]] * @tparam T the subtype of sbt.io.FileTreeDataView that is returned by [[FileTreeViewConfig.newDataView]]
* @return a [[FileTreeViewConfig]] instance. * @return a [[FileTreeViewConfig]] instance.
*/ */
def apply[T <: FileTreeDataView[Stamp]]( def apply[T <: FileTreeDataView[FileCacheEntry]](
newDataView: () => T, newDataView: () => T,
newMonitor: (T, Seq[WatchSource], Logger) => FileEventMonitor[Stamp] newMonitor: (T, Seq[WatchSource], Logger) => FileEventMonitor[FileCacheEntry]
): FileTreeViewConfig = ): FileTreeViewConfig =
new FileTreeViewConfig( new FileTreeViewConfig(
newDataView, newDataView,
(view: FileTreeDataView[Stamp], sources: Seq[WatchSource], logger: Logger) => (view: FileTreeDataView[FileCacheEntry], sources: Seq[WatchSource], logger: Logger) =>
newMonitor(view.asInstanceOf[T], sources, logger) newMonitor(view.asInstanceOf[T], sources, logger)
) )
@ -71,14 +71,14 @@ object FileTreeViewConfig {
antiEntropy: FiniteDuration antiEntropy: FiniteDuration
): FileTreeViewConfig = ): FileTreeViewConfig =
FileTreeViewConfig( FileTreeViewConfig(
() => FileTreeView.DEFAULT.asDataView(Stamped.converter), () => FileTreeView.DEFAULT.asDataView(FileCacheEntry.default),
(_: FileTreeDataView[Stamp], sources, logger) => { (_: FileTreeDataView[FileCacheEntry], sources, logger) => {
val ioLogger: sbt.io.WatchLogger = msg => logger.debug(msg.toString) val ioLogger: sbt.io.WatchLogger = msg => logger.debug(msg.toString)
FileEventMonitor.antiEntropy( FileEventMonitor.antiEntropy(
new WatchServiceBackedObservable( new WatchServiceBackedObservable(
WatchState.empty(Watched.createWatchService(), sources), WatchState.empty(Watched.createWatchService(), sources),
delay, delay,
Stamped.converter, FileCacheEntry.default,
closeService = true, closeService = true,
ioLogger ioLogger
), ),
@ -98,11 +98,15 @@ object FileTreeViewConfig {
*/ */
def default(antiEntropy: FiniteDuration): FileTreeViewConfig = def default(antiEntropy: FiniteDuration): FileTreeViewConfig =
FileTreeViewConfig( FileTreeViewConfig(
() => FileTreeRepository.default(Stamped.converter), () => FileTreeRepository.default(FileCacheEntry.default),
(repository: FileTreeRepository[Stamp], sources: Seq[WatchSource], logger: Logger) => { (
repository: FileTreeRepository[FileCacheEntry],
sources: Seq[WatchSource],
logger: Logger
) => {
repository.register(sources) repository.register(sources)
val copied = new Observable[Stamp] { val copied = new Observable[FileCacheEntry] {
override def addObserver(observer: Observer[Stamp]): Int = override def addObserver(observer: Observer[FileCacheEntry]): Int =
repository.addObserver(observer) repository.addObserver(observer)
override def removeObserver(handle: Int): Unit = repository.removeObserver(handle) override def removeObserver(handle: Int): Unit = repository.removeObserver(handle)
override def close(): Unit = {} // Don't close the underlying observable override def close(): Unit = {} // Don't close the underlying observable
@ -155,9 +159,9 @@ object FileTreeViewConfig {
pollingInterval: FiniteDuration, pollingInterval: FiniteDuration,
pollingSources: Seq[WatchSource], pollingSources: Seq[WatchSource],
): FileTreeViewConfig = FileTreeViewConfig( ): FileTreeViewConfig = FileTreeViewConfig(
() => FileTreeRepository.hybrid(Stamped.converter, pollingSources: _*), () => FileTreeRepository.hybrid(FileCacheEntry.default, pollingSources: _*),
( (
repository: HybridPollingFileTreeRepository[Stamp], repository: HybridPollingFileTreeRepository[FileCacheEntry],
sources: Seq[WatchSource], sources: Seq[WatchSource],
logger: Logger logger: Logger
) => { ) => {

View File

@ -10,6 +10,7 @@ package sbt
import java.io.{ File => JFile } import java.io.{ File => JFile }
import java.nio.file.Path import java.nio.file.Path
import sbt.internal.FileCacheEntry
import sbt.internal.inc.Stamper import sbt.internal.inc.Stamper
import sbt.io.TypedPath import sbt.io.TypedPath
import xsbti.compile.analysis.Stamp 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 * performance anywhere where we need to check if files have changed before doing potentially
* expensive work. * expensive work.
*/ */
trait Stamped { private[sbt] trait Stamped {
def stamp: Stamp private[sbt] def stamp: Stamp
} }
/** /**
* Provides converter functions from TypedPath to [[Stamped]]. * Provides converter functions from TypedPath to [[Stamped]].
*/ */
object Stamped { private[sbt] object Stamped {
type File = JFile with Stamped with TypedPath type File = JFile with Stamped with TypedPath
def file(typedPath: TypedPath, stamp: Stamp): JFile with Stamped with TypedPath = def file(typedPath: TypedPath, entry: FileCacheEntry): JFile with Stamped with TypedPath =
new StampedFileImpl(typedPath, stamp) new StampedFileImpl(typedPath, entry.stamp)
/** /**
* Converts a TypedPath instance to a [[Stamped]] by calculating the file hash. * Converts a TypedPath instance to a [[Stamped]] by calculating the file hash.
@ -47,11 +48,14 @@ object Stamped {
* using the last modified time and all other files using the file hash. * using the last modified time and all other files using the file hash.
*/ */
val converter: TypedPath => Stamp = (tp: TypedPath) => val converter: TypedPath => Stamp = (tp: TypedPath) =>
if (tp.isDirectory) binaryConverter(tp)
else {
tp.toPath.toString match { tp.toPath.toString match {
case s if s.endsWith(".jar") => binaryConverter(tp) case s if s.endsWith(".jar") => binaryConverter(tp)
case s if s.endsWith(".class") => binaryConverter(tp) case s if s.endsWith(".class") => binaryConverter(tp)
case _ => sourceConverter(tp) case _ => sourceConverter(tp)
} }
}
/** /**
* Adds a default ordering that just delegates to the java.io.File.compareTo method. * Adds a default ordering that just delegates to the java.io.File.compareTo method.

View File

@ -18,15 +18,14 @@ import sbt.BasicCommandStrings.{
} }
import sbt.BasicCommands.otherCommandParser import sbt.BasicCommands.otherCommandParser
import sbt.internal.LabeledFunctions._ import sbt.internal.LabeledFunctions._
import sbt.internal.LegacyWatched
import sbt.internal.io.{ EventMonitor, Source, WatchState } import sbt.internal.io.{ EventMonitor, Source, WatchState }
import sbt.internal.util.Types.const import sbt.internal.util.Types.const
import sbt.internal.util.complete.{ DefaultParsers, Parser } import sbt.internal.util.complete.{ DefaultParsers, Parser }
import sbt.internal.util.{ AttributeKey, JLine } import sbt.internal.util.{ AttributeKey, JLine }
import sbt.internal.{ FileCacheEntry, LegacyWatched }
import sbt.io.FileEventMonitor.{ Creation, Deletion, Event, Update } import sbt.io.FileEventMonitor.{ Creation, Deletion, Event, Update }
import sbt.io._ import sbt.io._
import sbt.util.{ Level, Logger } import sbt.util.{ Level, Logger }
import xsbti.compile.analysis.Stamp
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -147,7 +146,7 @@ object Watched {
private[sbt] def onEvent( private[sbt] def onEvent(
sources: Seq[WatchSource], sources: Seq[WatchSource],
projectSources: Seq[WatchSource] projectSources: Seq[WatchSource]
): Event[Stamp] => Watched.Action = ): Event[FileCacheEntry] => Watched.Action =
event => event =>
if (sources.exists(_.accept(event.entry.typedPath.toPath))) Watched.Trigger if (sources.exists(_.accept(event.entry.typedPath.toPath))) Watched.Trigger
else if (projectSources.exists(_.accept(event.entry.typedPath.toPath))) event match { else if (projectSources.exists(_.accept(event.entry.typedPath.toPath))) event match {
@ -459,7 +458,7 @@ trait WatchConfig {
* *
* @return an sbt.io.FileEventMonitor instance. * @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 * 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. * @param event the detected sbt.io.FileEventMonitor.Event.
* @return the next [[Watched.Action Action]] to run. * @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. * Transforms the state after the watch terminates.
@ -538,10 +537,10 @@ object WatchConfig {
*/ */
def default( def default(
logger: Logger, logger: Logger,
fileEventMonitor: FileEventMonitor[Stamp], fileEventMonitor: FileEventMonitor[FileCacheEntry],
handleInput: InputStream => Watched.Action, handleInput: InputStream => Watched.Action,
preWatch: (Int, Boolean) => Watched.Action, preWatch: (Int, Boolean) => Watched.Action,
onWatchEvent: Event[Stamp] => Watched.Action, onWatchEvent: Event[FileCacheEntry] => Watched.Action,
onWatchTerminated: (Watched.Action, String, State) => State, onWatchTerminated: (Watched.Action, String, State) => State,
triggeredMessage: (TypedPath, Int) => Option[String], triggeredMessage: (TypedPath, Int) => Option[String],
watchingMessage: Int => Option[String] watchingMessage: Int => Option[String]
@ -556,11 +555,11 @@ object WatchConfig {
val wm = watchingMessage val wm = watchingMessage
new WatchConfig { new WatchConfig {
override def logger: Logger = l 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 handleInput(inputStream: InputStream): Watched.Action = hi(inputStream)
override def preWatch(count: Int, lastResult: Boolean): Watched.Action = override def preWatch(count: Int, lastResult: Boolean): Watched.Action =
pw(count, lastResult) 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 = override def onWatchTerminated(action: Watched.Action, command: String, state: State): State =
owt(action, command, state) owt(action, command, state)
override def triggeredMessage(typedPath: TypedPath, count: Int): Option[String] = override def triggeredMessage(typedPath: TypedPath, count: Int): Option[String] =

View File

@ -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)"
}
}

View File

@ -14,10 +14,10 @@ import java.util.concurrent.atomic.AtomicBoolean
import org.scalatest.{ FlatSpec, Matchers } import org.scalatest.{ FlatSpec, Matchers }
import sbt.Watched._ import sbt.Watched._
import sbt.WatchedSpec._ import sbt.WatchedSpec._
import sbt.internal.FileCacheEntry
import sbt.io.FileEventMonitor.Event import sbt.io.FileEventMonitor.Event
import sbt.io.{ FileEventMonitor, IO, TypedPath } import sbt.io.{ FileEventMonitor, IO, TypedPath }
import sbt.util.Logger import sbt.util.Logger
import xsbti.compile.analysis.Stamp
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -27,11 +27,11 @@ class WatchedSpec extends FlatSpec with Matchers {
private val fileTreeViewConfig = FileTreeViewConfig.default(50.millis) private val fileTreeViewConfig = FileTreeViewConfig.default(50.millis)
def config( def config(
sources: Seq[WatchSource], sources: Seq[WatchSource],
fileEventMonitor: Option[FileEventMonitor[Stamp]] = None, fileEventMonitor: Option[FileEventMonitor[FileCacheEntry]] = None,
logger: Logger = NullLogger, logger: Logger = NullLogger,
handleInput: InputStream => Action = _ => Ignore, handleInput: InputStream => Action = _ => Ignore,
preWatch: (Int, Boolean) => Action = (_, _) => CancelWatch, preWatch: (Int, Boolean) => Action = (_, _) => CancelWatch,
onWatchEvent: Event[Stamp] => Action = _ => Ignore, onWatchEvent: Event[FileCacheEntry] => Action = _ => Ignore,
triggeredMessage: (TypedPath, Int) => Option[String] = (_, _) => None, triggeredMessage: (TypedPath, Int) => Option[String] = (_, _) => None,
watchingMessage: Int => Option[String] = _ => None watchingMessage: Int => Option[String] = _ => None
): WatchConfig = { ): WatchConfig = {

View File

@ -274,7 +274,7 @@ object Defaults extends BuildCommon {
fileTreeViewConfig := FileManagement.defaultFileTreeView.value, fileTreeViewConfig := FileManagement.defaultFileTreeView.value,
fileTreeView := state.value fileTreeView := state.value
.get(Keys.globalFileTreeView) .get(Keys.globalFileTreeView)
.getOrElse(FileTreeView.DEFAULT.asDataView(Stamped.converter)), .getOrElse(FileTreeView.DEFAULT.asDataView(FileCacheEntry.default)),
externalHooks := { externalHooks := {
val view = fileTreeView.value val view = fileTreeView.value
compileOptions => compileOptions =>

View File

@ -31,7 +31,6 @@ import sbt.librarymanagement.ivy.{ Credentials, IvyConfiguration, IvyPaths, Upda
import sbt.testing.Framework import sbt.testing.Framework
import sbt.util.{ Level, Logger } import sbt.util.{ Level, Logger }
import xsbti.compile._ import xsbti.compile._
import xsbti.compile.analysis.Stamp
import scala.concurrent.duration.{ Duration, FiniteDuration } import scala.concurrent.duration.{ Duration, FiniteDuration }
import scala.xml.{ NodeSeq, Node => XNode } import scala.xml.{ NodeSeq, Node => XNode }
@ -94,14 +93,14 @@ object Keys {
@deprecated("This is no longer used for continuous execution", "1.3.0") @deprecated("This is no longer used for continuous execution", "1.3.0")
val watch = SettingKey(BasicKeys.watch) val watch = SettingKey(BasicKeys.watch)
val suppressSbtShellNotification = settingKey[Boolean]("""True to suppress the "Executing in batch mode.." message.""").withRank(CSetting) 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 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 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 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 watchConfig = taskKey[WatchConfig]("The configuration for continuous execution.").withRank(BMinusSetting)
val watchLogger = taskKey[Logger]("A logger that reports watch events.").withRank(DSetting) 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 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 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 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) 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 (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 state = Def.stateKey
val streamsManager = Def.streamsManagerKey val streamsManager = Def.streamsManagerKey
private[sbt] val globalFileTreeView = AttributeKey[FileTreeDataView[Stamp]]( private[sbt] val globalFileTreeView = AttributeKey[FileTreeDataView[FileCacheEntry]](
"globalFileTreeView", "globalFileTreeView",
"Provides a view into the file system that may or may not cache the tree in memory", "Provides a view into the file system that may or may not cache the tree in memory",
1000 1000

View File

@ -25,7 +25,6 @@ import sbt.io.syntax._
import sbt.io.{ FileTreeDataView, IO } import sbt.io.{ FileTreeDataView, IO }
import sbt.util.{ Level, Logger, Show } import sbt.util.{ Level, Logger, Show }
import xsbti.compile.CompilerCache import xsbti.compile.CompilerCache
import xsbti.compile.analysis.Stamp
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.ExecutionContext import scala.concurrent.ExecutionContext
@ -863,7 +862,7 @@ object BuiltinCommands {
() ()
} }
val (_, config: FileTreeViewConfig) = extracted.runTask(Keys.fileTreeViewConfig, s) 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()) val newState = s.addExitHook(cleanup())
cleanup() cleanup()
newState newState

View File

@ -20,7 +20,10 @@ import scala.collection.mutable
private[sbt] object ExternalHooks { private[sbt] object ExternalHooks {
private val javaHome = Option(System.getProperty("java.home")).map(Paths.get(_)) 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._ import scala.collection.JavaConverters._
val sources = options.sources() val sources = options.sources()
val cachedSources = new java.util.HashMap[File, Stamp] val cachedSources = new java.util.HashMap[File, Stamp]
@ -30,7 +33,7 @@ private[sbt] object ExternalHooks {
case f: File => cachedSources.put(f, converter(f)) case f: File => cachedSources.put(f, converter(f))
} }
view match { view match {
case r: FileTreeRepository[Stamp] => case r: FileTreeRepository[FileCacheEntry] =>
r.register(options.classesDirectory.toPath, Integer.MAX_VALUE) r.register(options.classesDirectory.toPath, Integer.MAX_VALUE)
options.classpath.foreach { f => options.classpath.foreach { f =>
r.register(f.toPath, Integer.MAX_VALUE) r.register(f.toPath, Integer.MAX_VALUE)
@ -41,7 +44,7 @@ private[sbt] object ExternalHooks {
options.classpath.foreach { f => options.classpath.foreach { f =>
view.listEntries(f.toPath, Integer.MAX_VALUE, _ => true) foreach { e => view.listEntries(f.toPath, Integer.MAX_VALUE, _ => true) foreach { e =>
e.value match { 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 _ => case _ =>
} }
} }
@ -49,7 +52,7 @@ private[sbt] object ExternalHooks {
// rather than a directory. // rather than a directory.
view.listEntries(f.toPath, -1, _ => true) foreach { e => view.listEntries(f.toPath, -1, _ => true) foreach { e =>
e.value match { 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 _ => case _ =>
} }
} }

View File

@ -10,13 +10,12 @@ package sbt.internal
import java.io.IOException import java.io.IOException
import java.nio.file.Path import java.nio.file.Path
import sbt.BasicCommandStrings.ContinuousExecutePrefix
import sbt.Keys._ import sbt.Keys._
import sbt._
import sbt.io.FileTreeDataView.Entry import sbt.io.FileTreeDataView.Entry
import sbt.io.syntax.File import sbt.io.syntax.File
import sbt.io.{ FileFilter, FileTreeDataView, FileTreeRepository } import sbt.io.{ FileFilter, FileTreeDataView, FileTreeRepository }
import sbt._
import BasicCommandStrings.ContinuousExecutePrefix
import xsbti.compile.analysis.Stamp
private[sbt] object FileManagement { private[sbt] object FileManagement {
private[sbt] def defaultFileTreeView: Def.Initialize[Task[FileTreeViewConfig]] = Def.task { private[sbt] def defaultFileTreeView: Def.Initialize[Task[FileTreeViewConfig]] = Def.task {
@ -52,7 +51,7 @@ private[sbt] object FileManagement {
val view = fileTreeView.value val view = fileTreeView.value
val include = filter.toTask.value val include = filter.toTask.value
val ex = excludes.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 typedPath = entry.typedPath
val file = new java.io.File(typedPath.toPath.toString) { val file = new java.io.File(typedPath.toPath.toString) {
override def isDirectory: Boolean = typedPath.isDirectory override def isDirectory: Boolean = typedPath.isDirectory