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