Add InputGraph

This commit adds functionality to traverse the settings graph to find
all of the Inputs settings values for the transitive dependencies of the
task. We can use this to build up the list of globs that we must watch
when we are in a continuous build. Because the Inputs key is a setting,
it is actually quite fast to fetch all the values once the compiled map
is generated (O(2ms) in the scripted tests, though I did find that it
took O(20ms) to generate the compiled map).

One complicating factor is that dynamic tasks do not track any of
their dynamic dependencies. To work around this, I added the
transitiveDependencies key. If one does something like:
foo := {
  val _ = bar / transitiveDependencies
  val _ = baz / transitiveDependencies
  if (System.getProperty("some.prop", "false") == "true") Def.task(bar.value)
  else Def.task(baz.value)
}
then (foo / transitiveDependencies).value will return all of the inputs
and triggers for bar and baz as well as for foo.

To implement transitiveDependencies, I did something fairly similar to
streams where if the setting is referenced, I add a default
implementation. If the default implementation is not present, I fall
back on trying to extract the key from the commandLine. This allows the
user to run `show bar / transitiveDependencies` from the command line
even if `bar / transitiveDependencies` is not defined in the project.

It might be possible to coax transitiveDependencies into a setting, but
then it would have to be eagerly evaluated at project definition time
which might increase start up time too much.  Alternatively, we could
just define this task for every task in the build, but I'm not sure how
expensive that would be. At any rate, it should be straightforward to
make that change without breaking binary compatibility if need be. This
is something to possibly explore before the 1.3 release if there is any
spare time (unlikely).
This commit is contained in:
Ethan Atkins 2018-12-20 10:03:40 -08:00
parent e910a13d7f
commit ed06e18fab
13 changed files with 366 additions and 62 deletions

View File

@ -7,15 +7,15 @@
package sbt
import sbt.internal.util.Types.const
import sbt.internal.util.{ AttributeKey, Attributed, ConsoleAppender, Init }
import sbt.util.Show
import sbt.internal.util.complete.Parser
import java.io.File
import java.net.URI
import Scope.{ GlobalScope, ThisScope }
import KeyRanks.{ DTask, Invisible }
import sbt.KeyRanks.{ DTask, Invisible }
import sbt.Scope.{ GlobalScope, ThisScope }
import sbt.internal.util.Types.const
import sbt.internal.util.complete.Parser
import sbt.internal.util.{ AttributeKey, Attributed, ConsoleAppender, Init }
import sbt.util.Show
/** A concrete settings system that uses `sbt.Scope` for the scope type. */
object Def extends Init[Scope] with TaskMacroExtra {
@ -206,15 +206,16 @@ object Def extends Init[Scope] with TaskMacroExtra {
def toISParser[T](p: Initialize[Parser[T]]): Initialize[State => Parser[T]] = p(toSParser)
def toIParser[T](p: Initialize[InputTask[T]]): Initialize[State => Parser[Task[T]]] = p(_.parser)
import language.experimental.macros
import std.SettingMacro.{ settingDynMacroImpl, settingMacroImpl }
import std.TaskMacro.{
inputTaskMacroImpl,
inputTaskDynMacroImpl,
inputTaskMacroImpl,
taskDynMacroImpl,
taskMacroImpl
}
import std.SettingMacro.{ settingDynMacroImpl, settingMacroImpl }
import std.{ InputEvaluated, MacroPrevious, MacroValue, MacroTaskValue, ParserInput }
import std._
import language.experimental.macros
def task[T](t: T): Def.Initialize[Task[T]] = macro taskMacroImpl[T]
def taskDyn[T](t: Def.Initialize[Task[T]]): Def.Initialize[Task[T]] = macro taskDynMacroImpl[T]

View File

@ -43,6 +43,7 @@ import sbt.internal.server.{
ServerHandler
}
import sbt.internal.testing.TestLogger
import sbt.internal.TransitiveGlobs._
import sbt.internal.util.Attributed.data
import sbt.internal.util.Types._
import sbt.internal.util._
@ -144,6 +145,7 @@ object Defaults extends BuildCommon {
excludeFilter :== HiddenFileFilter,
classLoaderCache := ClassLoaderCache(4),
fileInputs :== Nil,
watchTriggers :== Nil,
) ++ TaskRepository
.proxy(GlobalScope / classLoaderCache, ClassLoaderCache(4)) ++ globalIvyCore ++ globalJvmCore
) ++ globalSbtCore
@ -669,6 +671,9 @@ object Defaults extends BuildCommon {
watchStartMessage := Watched.projectOnWatchMessage(thisProjectRef.value.project),
watch := watchSetting.value,
fileOutputs += target.value ** AllPassFilter,
transitiveGlobs := InputGraph.task.value,
transitiveInputs := InputGraph.inputsTask.value,
transitiveTriggers := InputGraph.triggersTask.value,
)
def generate(generators: SettingKey[Seq[Task[Seq[File]]]]): Initialize[Task[Seq[File]]] =
@ -2058,7 +2063,12 @@ object Classpaths {
val base = ModuleID(id.groupID, id.name, sbtVersion.value).withCrossVersion(cross)
CrossVersion(scalaVersion, binVersion)(base).withCrossVersion(Disabled())
},
shellPrompt := shellPromptFromState
shellPrompt := shellPromptFromState,
dynamicDependency := { (): Unit },
transitiveClasspathDependency := { (): Unit },
transitiveGlobs := { (Nil: Seq[Glob], Nil: Seq[Glob]) },
transitiveInputs := Nil,
transitiveTriggers := Nil,
)
)
@ -2897,6 +2907,7 @@ object Classpaths {
}
private[sbt] def trackedExportedProducts(track: TrackLevel): Initialize[Task[Classpath]] =
Def.task {
val _ = (packageBin / dynamicDependency).value
val art = (artifact in packageBin).value
val module = projectID.value
val config = configuration.value
@ -2909,6 +2920,7 @@ object Classpaths {
}
private[sbt] def trackedExportedJarProducts(track: TrackLevel): Initialize[Task[Classpath]] =
Def.task {
val _ = (packageBin / dynamicDependency).value
val art = (artifact in packageBin).value
val module = projectID.value
val config = configuration.value
@ -2923,6 +2935,7 @@ object Classpaths {
track: TrackLevel
): Initialize[Task[Seq[(File, CompileAnalysis)]]] =
Def.taskDyn {
val _ = (packageBin / dynamicDependency).value
val useJars = exportJars.value
if (useJars) trackedJarProductsImplTask(track)
else trackedNonJarProductsImplTask(track)
@ -2993,6 +3006,14 @@ object Classpaths {
def internalDependencies: Initialize[Task[Classpath]] =
Def.taskDyn {
val _ = (
(exportedProductsNoTracking / transitiveClasspathDependency).value,
(exportedProductsIfMissing / transitiveClasspathDependency).value,
(exportedProducts / transitiveClasspathDependency).value,
(exportedProductJarsNoTracking / transitiveClasspathDependency).value,
(exportedProductJarsIfMissing / transitiveClasspathDependency).value,
(exportedProductJars / transitiveClasspathDependency).value
)
internalDependenciesImplTask(
thisProjectRef.value,
classpathConfiguration.value,

View File

@ -7,38 +7,23 @@
package sbt
import sbt.internal.{
Load,
BuildStructure,
TaskTimings,
TaskName,
GCUtil,
TaskProgress,
TaskTraceEvent
}
import sbt.internal.util.{ Attributed, ConsoleAppender, ErrorHandling, HList, RMap, Signals, Types }
import sbt.util.{ Logger, Show }
import sbt.librarymanagement.{ Resolver, UpdateReport }
import scala.concurrent.duration.Duration
import java.io.File
import java.util.concurrent.atomic.AtomicReference
import Def.{ dummyState, ScopedKey, Setting }
import Keys.{
Streams,
TaskStreams,
dummyRoots,
executionRoots,
pluginData,
streams,
streamsManager,
transformState
}
import Project.richInitializeTask
import Scope.Global
import sbt.Def.{ ScopedKey, Setting, dummyState }
import sbt.Keys.{ TaskProgress => _, name => _, _ }
import sbt.Project.richInitializeTask
import sbt.Scope.Global
import sbt.internal.TaskName._
import sbt.internal.TransitiveGlobs._
import sbt.internal.util._
import sbt.internal.{ BuildStructure, GCUtil, Load, TaskProgress, TaskTimings, TaskTraceEvent, _ }
import sbt.librarymanagement.{ Resolver, UpdateReport }
import sbt.std.Transform.DummyTaskMap
import sbt.util.{ Logger, Show }
import scala.Console.RED
import std.Transform.DummyTaskMap
import TaskName._
import scala.concurrent.duration.Duration
/**
* An API that allows you to cancel executing tasks upon some signal.
@ -166,8 +151,8 @@ object PluginData {
}
object EvaluateTask {
import std.Transform
import Keys.state
import std.Transform
lazy private val sharedProgress = new TaskTimings(reportOnShutdown = true)
def taskTimingProgress: Option[ExecuteProgress[Task]] =
@ -565,7 +550,7 @@ object EvaluateTask {
// if the return type Seq[Setting[_]] is not explicitly given, scalac hangs
val injectStreams: ScopedKey[_] => Seq[Setting[_]] = scoped =>
if (scoped.key == streams.key)
if (scoped.key == streams.key) {
Seq(streams in scoped.scope := {
(streamsManager map { mgr =>
val stream = mgr(scoped)
@ -573,6 +558,26 @@ object EvaluateTask {
stream
}).value
})
else
Nil
} else if (scoped.key == transitiveInputs.key) {
scoped.scope.task.toOption.toSeq.map { key =>
val updatedKey = ScopedKey(scoped.scope.copy(task = Zero), key)
transitiveInputs in scoped.scope := InputGraph.inputsTask(updatedKey).value
}
} else if (scoped.key == transitiveTriggers.key) {
scoped.scope.task.toOption.toSeq.map { key =>
val updatedKey = ScopedKey(scoped.scope.copy(task = Zero), key)
transitiveTriggers in scoped.scope := InputGraph.triggersTask(updatedKey).value
}
} else if (scoped.key == transitiveGlobs.key) {
scoped.scope.task.toOption.toSeq.map { key =>
val updatedKey = ScopedKey(scoped.scope.copy(task = Zero), key)
transitiveGlobs in scoped.scope := InputGraph.task(updatedKey).value
}
} else if (scoped.key == dynamicDependency.key) {
(dynamicDependency in scoped.scope := { () }) :: Nil
} else if (scoped.key == transitiveClasspathDependency.key) {
(transitiveClasspathDependency in scoped.scope := { () }) :: Nil
} else {
Nil
}
}

View File

@ -481,6 +481,8 @@ object Keys {
"Provides a view into the file system that may or may not cache the tree in memory",
1000
)
private[sbt] val dynamicDependency = settingKey[Unit]("Leaves a breadcrumb that the scoped task is evaluated inside of a dynamic task")
private[sbt] val transitiveClasspathDependency = settingKey[Unit]("Leaves a breadcrumb that the scoped task has transitive classpath dependencies")
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

View File

@ -16,7 +16,7 @@ import sbt.io._
import scala.language.experimental.macros
private[sbt] object FileTree {
object FileTree {
private def toPair(e: Entry[FileAttributes]): Option[(Path, FileAttributes)] =
e.value.toOption.map(a => e.typedPath.toPath -> a)
trait Repository extends sbt.internal.Repository[Seq, Glob, (Path, FileAttributes)]

View File

@ -0,0 +1,216 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal
import sbt.Def._
import sbt.Keys._
import sbt.Project.richInitializeTask
import sbt._
import sbt.internal.io.Source
import sbt.internal.util.AttributeMap
import sbt.internal.util.complete.Parser
import sbt.io.Glob
import scala.annotation.tailrec
object TransitiveGlobs {
val transitiveTriggers = Def.taskKey[Seq[Glob]]("The transitive triggers for a key")
val transitiveInputs = Def.taskKey[Seq[Glob]]("The transitive inputs for a key")
val transitiveGlobs =
Def.taskKey[(Seq[Glob], Seq[Glob])]("The transitive inputs and triggers for a key")
}
private[sbt] object InputGraph {
@deprecated("Source is also deprecated.", "1.3.0")
private implicit class SourceOps(val source: Source) {
def toGlob: Glob =
Glob(
source.base,
source.includeFilter -- source.excludeFilter,
if (source.recursive) Int.MaxValue else 0
)
}
private[sbt] def inputsTask: Def.Initialize[Task[Seq[Glob]]] =
Def.task(transitiveGlobs(arguments.value)._1.sorted)
private[sbt] def inputsTask(key: ScopedKey[_]): Def.Initialize[Task[Seq[Glob]]] =
withParams((e, cm) => Def.task(transitiveGlobs(argumentsImpl(key, e, cm).value)._1.sorted))
private[sbt] def triggersTask: Def.Initialize[Task[Seq[Glob]]] =
Def.task(transitiveGlobs(arguments.value)._2.sorted)
private[sbt] def triggersTask(key: ScopedKey[_]): Def.Initialize[Task[Seq[Glob]]] =
withParams((e, cm) => Def.task(transitiveGlobs(argumentsImpl(key, e, cm).value)._2.sorted))
private[sbt] def task: Def.Initialize[Task[(Seq[Glob], Seq[Glob])]] =
Def.task(transitiveGlobs(arguments.value))
private[sbt] def task(key: ScopedKey[_]): Def.Initialize[Task[(Seq[Glob], Seq[Glob])]] =
withParams((e, cm) => Def.task(transitiveGlobs(argumentsImpl(key, e, cm).value)))
private def withParams[R](
f: (Extracted, CompiledMap) => Def.Initialize[Task[R]]
): Def.Initialize[Task[R]] = Def.taskDyn {
val extracted = Project.extract(state.value)
f(extracted, compile(extracted.structure))
}
private[sbt] def compile(structure: BuildStructure): CompiledMap =
compiled(structure.settings)(structure.delegates, structure.scopeLocal, (_: ScopedKey[_]) => "")
private[sbt] final class Arguments(
val scopedKey: ScopedKey[_],
val extracted: Extracted,
val compiledMap: CompiledMap,
val log: sbt.util.Logger,
val dependencyConfigurations: Seq[(ProjectRef, Set[String])],
val state: State
) {
def structure: BuildStructure = extracted.structure
def data: Map[Scope, AttributeMap] = extracted.structure.data.data
}
private def argumentsImpl(
scopedKey: ScopedKey[_],
extracted: Extracted,
compiledMap: CompiledMap
): Def.Initialize[Task[Arguments]] = Def.task {
val log = (streamsManager map { mgr =>
val stream = mgr(scopedKey)
stream.open()
stream
}).value.log
val configs = (internalDependencyConfigurations in scopedKey.scope).value
new Arguments(
scopedKey,
extracted,
compiledMap,
log,
configs,
state.value
)
}
private val ShowTransitive = "(?:show)?(?:[ ]*)(.*)/(?:[ ]*)transitive(?:Inputs|Globs|Triggers)".r
private def arguments: Def.Initialize[Task[Arguments]] = Def.taskDyn {
Def.task {
val extracted = Project.extract(state.value)
val compiledMap = compile(extracted.structure)
state.value.currentCommand.map(_.commandLine) match {
case Some(ShowTransitive(key)) =>
Parser.parse(key.trim, Act.scopedKeyParser(state.value)) match {
case Right(scopedKey) => argumentsImpl(scopedKey, extracted, compiledMap)
case _ => argumentsImpl(Keys.resolvedScoped.value, extracted, compiledMap)
}
case Some(_) => argumentsImpl(Keys.resolvedScoped.value, extracted, compiledMap)
}
}.value
}
private[sbt] def transitiveGlobs(args: Arguments): (Seq[Glob], Seq[Glob]) = {
import args._
val taskScope = Project.fillTaskAxis(scopedKey).scope
def delegates(sk: ScopedKey[_]): Seq[ScopedKey[_]] =
Project.delegates(structure, sk.scope, sk.key)
// We add the triggers to the delegate scopes to make it possible for the user to do something
// like: Compile / compile / watchTriggers += baseDirectory.value ** "*.proto". We do not do the
// same for inputs because inputs are expected to be explicitly used as part of the task.
val allKeys: Seq[ScopedKey[_]] =
(delegates(scopedKey).toSet ++ delegates(ScopedKey(taskScope, watchTriggers.key))).toSeq
val keys = collectKeys(args, allKeys, Set.empty, Set.empty)
def getGlobs(scopedKey: ScopedKey[Seq[Glob]]): Seq[Glob] =
data.get(scopedKey.scope).flatMap(_.get(scopedKey.key)).getOrElse(Nil)
val (inputGlobs, triggerGlobs) = keys.partition(_.key == fileInputs.key) match {
case (i, t) => (i.flatMap(getGlobs), t.flatMap(getGlobs))
}
(inputGlobs.distinct, (triggerGlobs ++ legacy(keys :+ scopedKey, args)).distinct)
}
private def legacy(keys: Seq[ScopedKey[_]], args: Arguments): Seq[Glob] = {
import args._
val projectScopes =
keys.view
.map(_.scope.copy(task = Zero, extra = Zero))
.distinct
.toIndexedSeq
val projects = projectScopes.flatMap(_.project.toOption).distinct.toSet
val scopes: Seq[Either[Scope, Seq[Glob]]] =
data.flatMap {
case (s, am) =>
if (s == Scope.Global || s.project.toOption.exists(projects.contains))
am.get(Keys.watchSources.key) match {
case Some(k) =>
k.work match {
// Avoid extracted.runTask if possible.
case Pure(w, _) => Some(Right(w().map(_.toGlob)))
case _ => Some(Left(s))
}
case _ => None
} else {
None
}
}.toSeq
scopes.flatMap {
case Left(scope) =>
extracted.runTask(Keys.watchSources in scope, state)._2.map(_.toGlob)
case Right(globs) => globs
}
}
@tailrec
private def collectKeys(
arguments: Arguments,
dependencies: Seq[ScopedKey[_]],
accumulator: Set[ScopedKey[Seq[Glob]]],
visited: Set[ScopedKey[_]]
): Seq[ScopedKey[Seq[Glob]]] = dependencies match {
// Iterates until the dependency list is empty. The visited parameter prevents the graph
// traversal from getting stuck in a cycle.
case Seq(dependency, rest @ _*) =>
(if (!visited(dependency)) arguments.compiledMap.get(dependency) else None) match {
case Some(compiled) =>
val newVisited = visited + compiled.key
val baseGlobs: Seq[ScopedKey[Seq[Glob]]] = compiled.key match {
case key: ScopedKey[Seq[Glob]] @unchecked if isGlobKey(key) => key :: Nil
case _ => Nil
}
val base: (Seq[ScopedKey[_]], Seq[ScopedKey[Seq[Glob]]]) = (Nil, baseGlobs)
val (newDependencies, newScopes) =
(compiled.dependencies.filterNot(newVisited) ++ compiled.settings.map(_.key))
.foldLeft(base) {
case ((d, s), key: ScopedKey[Seq[Glob]] @unchecked)
if isGlobKey(key) && !newVisited(key) =>
(d, s :+ key)
case ((d, s), key) if key.key == dynamicDependency.key =>
key.scope.task.toOption
.map { k =>
val newKey = ScopedKey(key.scope.copy(task = Zero), k)
if (newVisited(newKey)) (d, s) else (d :+ newKey, s)
}
.getOrElse((d, s))
case ((d, s), key) if key.key == transitiveClasspathDependency.key =>
key.scope.task.toOption
.map { task =>
val zeroedTaskScope = key.scope.copy(task = Zero)
val transitiveKeys = arguments.dependencyConfigurations.flatMap {
case (p, configs) =>
configs.map(c => ScopedKey(zeroedTaskScope in (p, ConfigKey(c)), task))
}
(d ++ transitiveKeys.filterNot(newVisited), s)
}
.getOrElse((d, s))
case ((d, s), key) =>
(d ++ (if (!newVisited(key)) Some(key) else None), s)
}
// Append the Keys.triggers key in case there are no other references to Keys.triggers.
val transitiveTrigger = compiled.key.scope.task.toOption match {
case _: Some[_] => ScopedKey(compiled.key.scope, watchTriggers.key)
case None => ScopedKey(Project.fillTaskAxis(compiled.key).scope, watchTriggers.key)
}
val newRest = rest ++ newDependencies ++ (if (newVisited(transitiveTrigger)) Nil
else Some(transitiveTrigger))
collectKeys(arguments, newRest, accumulator ++ newScopes, newVisited)
case _ if rest.nonEmpty => collectKeys(arguments, rest, accumulator, visited)
case _ => accumulator.toIndexedSeq
}
case _ => accumulator.toIndexedSeq
}
private[this] def isGlobKey(key: ScopedKey[_]): Boolean = key.key match {
case fileInputs.key | watchTriggers.key => true
case _ => false
}
}

View File

@ -293,9 +293,9 @@ private[sbt] object Load {
def finalTransforms(ss: Seq[Setting[_]]): Seq[Setting[_]] = {
def mapSpecial(to: ScopedKey[_]) = λ[ScopedKey ~> ScopedKey](
(key: ScopedKey[_]) =>
if (key.key == streams.key)
if (key.key == streams.key) {
ScopedKey(Scope.fillTaskAxis(Scope.replaceThis(to.scope)(key.scope), to.key), key.key)
else key
} else key
)
def setDefining[T] =
(key: ScopedKey[T], value: T) =>

View File

@ -3,9 +3,9 @@
// Check that we can correctly extract Foo.txt with a recursive source
val foo = taskKey[Seq[File]]("Retrieve Foo.txt")
foo / inputs += baseDirectory.value ** "*.txt"
foo / fileInputs += baseDirectory.value ** "*.txt"
foo := (foo / inputs).value.all
foo := (foo / fileInputs).value.all.map(_._1.toFile)
val checkFoo = taskKey[Unit]("Check that the Foo.txt file is retrieved")
@ -14,9 +14,9 @@ checkFoo := assert(foo.value == Seq(baseDirectory.value / "base/subdir/nested-su
// Check that we can correctly extract Bar.md with a non-recursive source
val bar = taskKey[Seq[File]]("Retrieve Bar.md")
bar / inputs += baseDirectory.value / "base/subdir/nested-subdir" * "*.md"
bar / fileInputs += baseDirectory.value / "base/subdir/nested-subdir" * "*.md"
bar := (bar / inputs).value.all
bar := (bar / fileInputs).value.all.map(_._1.toFile)
val checkBar = taskKey[Unit]("Check that the Bar.md file is retrieved")
@ -25,19 +25,19 @@ checkBar := assert(bar.value == Seq(baseDirectory.value / "base/subdir/nested-su
// Check that we can correctly extract Bar.md and Foo.md with a non-recursive source
val all = taskKey[Seq[File]]("Retrieve all files")
all / inputs += baseDirectory.value / "base" / "subdir" / "nested-subdir" * AllPassFilter
all / fileInputs += baseDirectory.value / "base" / "subdir" / "nested-subdir" * AllPassFilter
val checkAll = taskKey[Unit]("Check that the Bar.md file is retrieved")
checkAll := {
import sbt.dsl.LinterLevel.Ignore
val expected = Set("Foo.txt", "Bar.md").map(baseDirectory.value / "base/subdir/nested-subdir" / _)
assert((all / inputs).value.all.toSet == expected)
assert((all / fileInputs).value.all.map(_._1.toFile).toSet == expected)
}
val set = taskKey[Seq[File]]("Specify redundant sources in a set")
set / inputs ++= Seq(
set / fileInputs ++= Seq(
baseDirectory.value / "base" ** -DirectoryFilter,
baseDirectory.value / "base" / "subdir" / "nested-subdir" * -DirectoryFilter
)
@ -45,13 +45,13 @@ set / inputs ++= Seq(
val checkSet = taskKey[Unit]("Verify that redundant sources are handled")
checkSet := {
val redundant = (set / inputs).value.all
val redundant = (set / fileInputs).value.all.map(_._1.toFile)
assert(redundant.size == 4) // It should get Foo.txt and Bar.md twice
val deduped = (set / inputs).value.toSet[Glob].all
val deduped = (set / fileInputs).value.toSet[Glob].all.map(_._1.toFile)
val expected = Seq("Bar.md", "Foo.txt").map(baseDirectory.value / "base/subdir/nested-subdir" / _)
assert(deduped.sorted == expected)
val altDeduped = (set / inputs).value.unique
val altDeduped = (set / fileInputs).value.unique.map(_._1.toFile)
assert(altDeduped.sorted == expected)
}

View File

@ -1,4 +1,6 @@
import sbt.internal.FileTree
import java.nio.file.Path
import sbt.internal.{FileAttributes, FileTree}
import sbt.io.FileTreeDataView
import xsbti.compile.analysis.Stamp
@ -8,7 +10,7 @@ val allInputsExplicit = taskKey[Seq[File]]("")
val checkInputs = inputKey[Unit]("")
val checkInputsExplicit = inputKey[Unit]("")
allInputs := (Compile / unmanagedSources / inputs).value.all
allInputs := (Compile / unmanagedSources / fileInputs).value.all.map(_._1.toFile)
checkInputs := {
val res = allInputs.value
@ -22,15 +24,15 @@ allInputsExplicit := {
val files = scala.collection.mutable.Set.empty[File]
val underlying = implicitly[FileTree.Repository]
val repo = new FileTree.Repository {
override def get(glob: Glob): Seq[FileTreeDataView.Entry[Stamp]] = {
override def get(glob: Glob): Seq[(Path, FileAttributes)] = {
val res = underlying.get(glob)
files ++= res.map(_.typedPath.toPath.toFile)
files ++= res.map(_._1.toFile)
res
}
override def close(): Unit = {}
}
val include = (Compile / unmanagedSources / includeFilter).value
val _ = (Compile / unmanagedSources / inputs).value.all(repo).toSet
val _ = (Compile / unmanagedSources / fileInputs).value.all(repo).map(_._1.toFile).toSet
files.filter(include.accept).toSeq
}

View File

@ -0,0 +1,46 @@
val foo = taskKey[Int]("foo")
foo := {
val _ = (foo / fileInputs).value
1
}
foo / fileInputs += baseDirectory.value * "foo.txt"
val checkFoo = taskKey[Unit]("check foo inputs")
checkFoo := {
val actual = (foo / transitiveDependencies).value.toSet
val expected = (foo / fileInputs).value.toSet
assert(actual == expected)
}
val bar = taskKey[Int]("bar")
bar := {
val _ = (bar / fileInputs).value
foo.value + 1
}
bar / fileInputs += baseDirectory.value * "bar.txt"
val checkBar = taskKey[Unit]("check bar inputs")
checkBar := {
val actual = (bar / transitiveDependencies).value.toSet
val expected = ((bar / fileInputs).value ++ (foo / fileInputs).value).toSet
assert(actual == expected)
}
val baz = taskKey[Int]("baz")
baz / fileInputs += baseDirectory.value * "baz.txt"
baz := {
println(resolvedScoped.value)
val _ = (baz / fileInputs).value
bar.value + 1
}
baz := Def.taskDyn {
val _ = (bar / transitiveDependencies).value
val len = (baz / fileInputs).value.length
Def.task(bar.value + len)
}.value
val checkBaz = taskKey[Unit]("check bar inputs")
checkBaz := {
val actual = (baz / transitiveDependencies).value.toSet
val expected = ((bar / fileInputs).value ++ (foo / fileInputs).value ++ (baz / fileInputs).value).toSet
assert(actual == expected)
}

View File

@ -0,0 +1,3 @@
package bar
object Bar

View File

@ -0,0 +1,3 @@
package foo
object Foo

View File

@ -0,0 +1,5 @@
#> checkFoo
#> checkBar
> checkBaz