mirror of https://github.com/sbt/sbt.git
Fix #5647: treat keys consumed via .all(ScopeFilter) as used by lintUnused
- Add Initialize.dynamicDependencies (Seq[Any]) in util-collection to carry (AttributeKey, ScopeFilter) from .all() without changing static dependency graph. - Add DynamicDepsInitialize wrapper and Def.withDynamicDependencies; use them in SettingKeyAll.all and TaskKeyAll.all when the init is a KeyedInitialize. - In LintUnused, collect init.dynamicDependencies from all settings, expand via ScopeFilter.expandDynamicDeps (build ScopeFilter.Data from structure, apply each filter to get scopes, add ScopedKey(scope, key) to used). - Add ScopeFilter.dataFromStructure and expandDynamicDeps with try/catch so bad filters or structure do not break the lint.
This commit is contained in:
parent
edd7061f15
commit
905862f60c
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
package sbt
|
||||
|
||||
import sbt.internal.{ Load, LoadedBuildUnit }
|
||||
import sbt.internal.{ BuildStructure, Load, LoadedBuildUnit }
|
||||
import sbt.internal.util.{ AttributeKey, Dag }
|
||||
import sbt.librarymanagement.{ ConfigRef, Configuration }
|
||||
import sbt.internal.util.Types.const
|
||||
|
|
@ -96,8 +96,14 @@ object ScopeFilter {
|
|||
* Evaluates the initialization in all scopes selected by the filter. These are dynamic dependencies, so
|
||||
* static inspections will not show them.
|
||||
*/
|
||||
def all(sfilter: => ScopeFilter): Initialize[Seq[A]] = Def.flatMap(getData) { data =>
|
||||
sfilter(data).toSeq.map(s => Project.inScope(s, i)).join
|
||||
def all(sfilter: => ScopeFilter): Initialize[Seq[A]] = {
|
||||
val inner = Def.flatMap(getData) { data =>
|
||||
sfilter(data).toSeq.map(s => Project.inScope(s, i)).join
|
||||
}
|
||||
val dynamicDeps = i match
|
||||
case k: Def.KeyedInitialize[?] => Seq((k.scopedKey.key, sfilter))
|
||||
case _ => Nil
|
||||
Def.withDynamicDependencies(inner, dynamicDeps)
|
||||
}
|
||||
|
||||
final class TaskKeyAll[A] private[sbt] (i: Initialize[Task[A]]):
|
||||
|
|
@ -105,9 +111,15 @@ object ScopeFilter {
|
|||
* Evaluates the task in all scopes selected by the filter. These are dynamic dependencies, so
|
||||
* static inspections will not show them.
|
||||
*/
|
||||
def all(sfilter: => ScopeFilter): Initialize[Task[Seq[A]]] = Def.flatMap(getData) { data =>
|
||||
import std.TaskExtra.*
|
||||
sfilter(data).toSeq.map(s => Project.inScope(s, i)).join(_.join)
|
||||
def all(sfilter: => ScopeFilter): Initialize[Task[Seq[A]]] = {
|
||||
val inner = Def.flatMap(getData) { data =>
|
||||
import std.TaskExtra.*
|
||||
sfilter(data).toSeq.map(s => Project.inScope(s, i)).join(_.join)
|
||||
}
|
||||
val dynamicDeps = i match
|
||||
case k: Def.KeyedInitialize[?] => Seq((k.scopedKey.key, sfilter))
|
||||
case _ => Nil
|
||||
Def.withDynamicDependencies(inner, dynamicDeps)
|
||||
}
|
||||
|
||||
private[sbt] val Make = new Make {}
|
||||
|
|
@ -218,6 +230,41 @@ object ScopeFilter {
|
|||
val allScopes: AllScopes
|
||||
)
|
||||
|
||||
private def dataFromStructure(structure: BuildStructure): Data = {
|
||||
val units = structure.units
|
||||
val rootProject = Load.getRootProject(units)
|
||||
val resolve: ProjectReference => ProjectRef = ref =>
|
||||
Scope.resolveProjectRef(structure.root, rootProject, ref)
|
||||
val scopes = structure.data.scopes
|
||||
val grouped: ScopeMap = scopes
|
||||
.groupBy(_.project)
|
||||
.view
|
||||
.mapValues { byProj =>
|
||||
byProj.groupBy(_.config).view.mapValues { byConfig =>
|
||||
byConfig.groupBy(_.task).view.mapValues(_.toSet).toMap
|
||||
}.toMap
|
||||
}
|
||||
.toMap
|
||||
new Data(units, resolve, new AllScopes(scopes, grouped))
|
||||
}
|
||||
|
||||
def expandDynamicDeps(deps: Seq[Any], structure: BuildStructure): Set[ScopedKey[?]] = {
|
||||
if deps.isEmpty then Set.empty
|
||||
else {
|
||||
val data = dataFromStructure(structure)
|
||||
val result = scala.collection.mutable.Set.empty[ScopedKey[?]]
|
||||
for dep <- deps do
|
||||
dep match
|
||||
case (key: AttributeKey[?], filter: ScopeFilter) =>
|
||||
try
|
||||
filter(data).foreach(scope => result += Def.ScopedKey(scope, key))
|
||||
catch
|
||||
case _: Exception => ()
|
||||
case _ =>
|
||||
result.toSet
|
||||
}
|
||||
}
|
||||
|
||||
private[sbt] val allScopes: Initialize[AllScopes] = Def.setting {
|
||||
val scopes = Def.StaticScopes.value
|
||||
val grouped: ScopeMap =
|
||||
|
|
|
|||
|
|
@ -135,7 +135,11 @@ object LintUnused {
|
|||
val display = Def.showShortKey(None) // extracted.showKey
|
||||
val comp = structure.compiledMap
|
||||
val cMap = Def.flattenLocals(comp)
|
||||
val used: Set[ScopedKey[?]] = cMap.values.flatMap(_.dependencies).toSet
|
||||
val staticUsed: Set[ScopedKey[?]] = cMap.values.flatMap(_.dependencies).toSet
|
||||
val dynamicDeps =
|
||||
comp.values.flatMap(_.settings).flatMap(s => s.init.dynamicDependencies).toSeq
|
||||
val dynamicUsed = ScopeFilter.expandDynamicDeps(dynamicDeps, structure)
|
||||
val used: Set[ScopedKey[?]] = staticUsed ++ dynamicUsed
|
||||
val unused: Seq[ScopedKey[?]] = cMap.keys.filter(!used.contains(_)).toSeq
|
||||
val withDefinedAts: Seq[UnusedKey] = unused.map { u =>
|
||||
val data = Project.scopedKeyData(structure, u)
|
||||
|
|
|
|||
|
|
@ -630,6 +630,10 @@ trait Init:
|
|||
*/
|
||||
sealed trait Initialize[A1]:
|
||||
def dependencies: Seq[ScopedKey[?]]
|
||||
|
||||
/** Dynamic dependencies (e.g. from .all(ScopeFilter)) not visible in the static graph. */
|
||||
private[sbt] def dynamicDependencies: Seq[Any] = Nil
|
||||
|
||||
def apply[A2](g: A1 => A2): Initialize[A2]
|
||||
|
||||
private[sbt] def mapReferenced(g: MapScoped): Initialize[A1]
|
||||
|
|
@ -999,6 +1003,32 @@ trait Init:
|
|||
inputs.toList0.foldLeft(init) { (v, i) => i.processAttributes(v)(f) }
|
||||
end Apply
|
||||
|
||||
private[sbt] final class DynamicDepsInitialize[A1](
|
||||
inner: Initialize[A1],
|
||||
val dynamicDeps: Seq[Any]
|
||||
) extends Initialize[A1]:
|
||||
override def dependencies: Seq[ScopedKey[?]] = inner.dependencies
|
||||
override def dynamicDependencies: Seq[Any] = dynamicDeps
|
||||
override def apply[A2](g: A1 => A2): Initialize[A2] =
|
||||
DynamicDepsInitialize(inner.apply(g), dynamicDeps)
|
||||
override def mapReferenced(g: MapScoped): Initialize[A1] =
|
||||
DynamicDepsInitialize(inner.mapReferenced(g), dynamicDeps)
|
||||
override def validateKeyReferenced(g: ValidateKeyRef): ValidatedInit[A1] =
|
||||
inner.validateKeyReferenced(g).map(DynamicDepsInitialize(_, dynamicDeps))
|
||||
override def mapConstant(g: MapConstant): Initialize[A1] =
|
||||
DynamicDepsInitialize(inner.mapConstant(g), dynamicDeps)
|
||||
override def evaluate(ss: Settings): A1 = inner.evaluate(ss)
|
||||
private[sbt] override def processAttributes[A2](init: A2)(f: (A2, AttributeMap) => A2): A2 =
|
||||
inner.processAttributes(init)(f)
|
||||
end DynamicDepsInitialize
|
||||
|
||||
private[sbt] object DynamicDepsInitialize:
|
||||
def apply[A1](inner: Initialize[A1], deps: Seq[Any]): Initialize[A1] =
|
||||
if deps.isEmpty then inner else new DynamicDepsInitialize(inner, deps)
|
||||
|
||||
def withDynamicDependencies[A1](init: Initialize[A1], deps: Seq[Any]): Initialize[A1] =
|
||||
DynamicDepsInitialize(init, deps)
|
||||
|
||||
private def remove[A](s: Seq[A], v: A) = s.filterNot(_ == v)
|
||||
|
||||
final class Undefined private[sbt] (val defining: Setting[?], val referencedKey: ScopedKey[?])
|
||||
|
|
|
|||
Loading…
Reference in New Issue