mirror of https://github.com/sbt/sbt.git
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:
parent
e910a13d7f
commit
ed06e18fab
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package bar
|
||||
|
||||
object Bar
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package foo
|
||||
|
||||
object Foo
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#> checkFoo
|
||||
|
||||
#> checkBar
|
||||
|
||||
> checkBaz
|
||||
Loading…
Reference in New Issue