sbt.internal.WatchTransitiveDependencies.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of main_2.12 Show documentation
Show all versions of main_2.12 Show documentation
sbt is an interactive build tool
The newest version!
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, 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.nio.Globs
import sbt.internal.util.AttributeMap
import sbt.internal.util.complete.Parser
import sbt.nio.FileStamper
import sbt.nio.Keys._
import sbt.nio.file.Glob
import scala.annotation.{ nowarn, tailrec }
private[sbt] object WatchTransitiveDependencies {
private implicit class SourceOps(val source: Source) {
def toGlob: Glob = {
val filter = source.includeFilter -- source.excludeFilter
Globs.apply(source.base.toPath, source.recursive, filter)
}
}
private[sbt] def task: Def.Initialize[Task[Seq[DynamicInput]]] =
Def.task(transitiveDynamicInputs(arguments.value))
private[sbt] def task(
key: ScopedKey[_]
): Def.Initialize[Task[Seq[DynamicInput]]] =
withParams((e, cm) => Def.task(transitiveDynamicInputs(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 = structure.compiledMap
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
}
@nowarn
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 transitiveDynamicInputs(args: Arguments): Seq[DynamicInput] = {
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 getDynamicInputs(scopedKey: ScopedKey[Seq[Glob]], trigger: Boolean): Seq[DynamicInput] = {
data
.get(scopedKey.scope)
.map { am =>
am.get(scopedKey.key) match {
case Some(globs: Seq[Glob]) =>
if (!trigger) {
val stamper = am.get(inputFileStamper.key).getOrElse(FileStamper.Hash)
val forceTrigger = am.get(watchForceTriggerOnAnyChange.key).getOrElse(false)
globs.map(g => DynamicInput(g, stamper, forceTrigger))
} else {
globs.map(g => DynamicInput(g, FileStamper.LastModified, forceTrigger = true))
}
case None => Nil: Seq[DynamicInput]
}
}
.getOrElse(Nil)
}
val (inputGlobs, triggerGlobs) = keys.partition(_.key == fileInputs.key) match {
case (inputs, triggers) =>
(
inputs.flatMap(getDynamicInputs(_, trigger = false)),
triggers.flatMap(getDynamicInputs(_, trigger = true))
)
}
(inputGlobs ++ triggerGlobs ++ legacy(keys :+ scopedKey, args)).distinct.sorted
}
@nowarn
private def legacy(keys: Seq[ScopedKey[_]], args: Arguments): Seq[DynamicInput] = {
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
def toDynamicInput(glob: Glob): DynamicInput =
DynamicInput(glob, FileStamper.LastModified, forceTrigger = true)
scopes.flatMap {
case Left(scope) =>
extracted.runTask(Keys.watchSources in scope, state)._2.map(s => toDynamicInput(s.toGlob))
case Right(globs) => globs.map(toDynamicInput)
}
}
@nowarn
@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
}
}