From ed06e18fab7890c9c509223a7b1925a7a8f9bc1b Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Thu, 20 Dec 2018 10:03:40 -0800 Subject: [PATCH] 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). --- main-settings/src/main/scala/sbt/Def.scala | 21 +- main/src/main/scala/sbt/Defaults.scala | 23 +- main/src/main/scala/sbt/EvaluateTask.scala | 71 +++--- main/src/main/scala/sbt/Keys.scala | 2 + .../main/scala/sbt/internal/FileTree.scala | 2 +- .../main/scala/sbt/internal/InputGraph.scala | 216 ++++++++++++++++++ main/src/main/scala/sbt/internal/Load.scala | 4 +- sbt/src/sbt-test/tests/glob-dsl/build.sbt | 20 +- sbt/src/sbt-test/tests/inputs/build.sbt | 12 +- .../tests/transitive-inputs/build.sbt | 46 ++++ .../src/main/scala/bar/Bar.scala | 3 + .../src/main/scala/foo/Foo.scala | 3 + sbt/src/sbt-test/tests/transitive-inputs/test | 5 + 13 files changed, 366 insertions(+), 62 deletions(-) create mode 100644 main/src/main/scala/sbt/internal/InputGraph.scala create mode 100644 sbt/src/sbt-test/tests/transitive-inputs/build.sbt create mode 100644 sbt/src/sbt-test/tests/transitive-inputs/src/main/scala/bar/Bar.scala create mode 100644 sbt/src/sbt-test/tests/transitive-inputs/src/main/scala/foo/Foo.scala create mode 100644 sbt/src/sbt-test/tests/transitive-inputs/test diff --git a/main-settings/src/main/scala/sbt/Def.scala b/main-settings/src/main/scala/sbt/Def.scala index 2baf38e33..1451b5ffc 100644 --- a/main-settings/src/main/scala/sbt/Def.scala +++ b/main-settings/src/main/scala/sbt/Def.scala @@ -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] diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index d7044ecfd..4b0a9e030 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -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, diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index ddbcce356..ada880ef8 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -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 + } } diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 1b276dea8..05e1d1d8a 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -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 diff --git a/main/src/main/scala/sbt/internal/FileTree.scala b/main/src/main/scala/sbt/internal/FileTree.scala index a26bc0bec..7b0919056 100644 --- a/main/src/main/scala/sbt/internal/FileTree.scala +++ b/main/src/main/scala/sbt/internal/FileTree.scala @@ -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)] diff --git a/main/src/main/scala/sbt/internal/InputGraph.scala b/main/src/main/scala/sbt/internal/InputGraph.scala new file mode 100644 index 000000000..0620a5d00 --- /dev/null +++ b/main/src/main/scala/sbt/internal/InputGraph.scala @@ -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 + } +} diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 1ca8513f7..59c381326 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -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) => diff --git a/sbt/src/sbt-test/tests/glob-dsl/build.sbt b/sbt/src/sbt-test/tests/glob-dsl/build.sbt index e94925bfb..16b161d89 100644 --- a/sbt/src/sbt-test/tests/glob-dsl/build.sbt +++ b/sbt/src/sbt-test/tests/glob-dsl/build.sbt @@ -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) } diff --git a/sbt/src/sbt-test/tests/inputs/build.sbt b/sbt/src/sbt-test/tests/inputs/build.sbt index c242467e2..88cc5a636 100644 --- a/sbt/src/sbt-test/tests/inputs/build.sbt +++ b/sbt/src/sbt-test/tests/inputs/build.sbt @@ -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 } diff --git a/sbt/src/sbt-test/tests/transitive-inputs/build.sbt b/sbt/src/sbt-test/tests/transitive-inputs/build.sbt new file mode 100644 index 000000000..f3151f5c5 --- /dev/null +++ b/sbt/src/sbt-test/tests/transitive-inputs/build.sbt @@ -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) +} diff --git a/sbt/src/sbt-test/tests/transitive-inputs/src/main/scala/bar/Bar.scala b/sbt/src/sbt-test/tests/transitive-inputs/src/main/scala/bar/Bar.scala new file mode 100644 index 000000000..f51e51890 --- /dev/null +++ b/sbt/src/sbt-test/tests/transitive-inputs/src/main/scala/bar/Bar.scala @@ -0,0 +1,3 @@ +package bar + +object Bar \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/transitive-inputs/src/main/scala/foo/Foo.scala b/sbt/src/sbt-test/tests/transitive-inputs/src/main/scala/foo/Foo.scala new file mode 100644 index 000000000..5c464310a --- /dev/null +++ b/sbt/src/sbt-test/tests/transitive-inputs/src/main/scala/foo/Foo.scala @@ -0,0 +1,3 @@ +package foo + +object Foo diff --git a/sbt/src/sbt-test/tests/transitive-inputs/test b/sbt/src/sbt-test/tests/transitive-inputs/test new file mode 100644 index 000000000..24a3714e8 --- /dev/null +++ b/sbt/src/sbt-test/tests/transitive-inputs/test @@ -0,0 +1,5 @@ +#> checkFoo + +#> checkBar + +> checkBaz