From 5c200d6258991716c754dd80b5001b8b4581a5da Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Tue, 10 Sep 2024 11:39:40 +0200 Subject: [PATCH 1/2] Optimize ScopeFilter --- main/src/main/scala/sbt/Defaults.scala | 1 + main/src/main/scala/sbt/Keys.scala | 1 + main/src/main/scala/sbt/ScopeFilter.scala | 154 ++++++++++++---------- 3 files changed, 90 insertions(+), 66 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 125e3dd82..6b8c31744 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -327,6 +327,7 @@ object Defaults extends BuildCommon { outputStrategy :== None, // TODO - This might belong elsewhere. buildStructure := Project.structure(state.value), settingsData := buildStructure.value.data, + allScopes := ScopeFilter.allScopes.value, checkBuildSources / aggregate :== false, checkBuildSources / changedInputFiles / aggregate := false, checkBuildSources / Continuous.dynamicInputs := None, diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index e5c46c962..edf906c26 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -632,6 +632,7 @@ object Keys { val forcegc = settingKey[Boolean]("Enables (true) or disables (false) forcing garbage collection after task run when needed.").withRank(BMinusSetting) val minForcegcInterval = settingKey[Duration]("Minimal interval to check for forcing garbage collection.") val settingsData = std.FullInstance.settingsData + private[sbt] val allScopes = settingKey[ScopeFilter.AllScopes]("Internal use: a view of all scopes for filtering") @cacheLevel(include = Array.empty) val streams = taskKey[TaskStreams]("Provides streams for logging and persisting data.").withRank(DTask) val taskDefinitionKey = Def.taskDefinitionKey diff --git a/main/src/main/scala/sbt/ScopeFilter.scala b/main/src/main/scala/sbt/ScopeFilter.scala index 700b0e8e4..730d56333 100644 --- a/main/src/main/scala/sbt/ScopeFilter.scala +++ b/main/src/main/scala/sbt/ScopeFilter.scala @@ -9,29 +9,54 @@ package sbt import sbt.internal.{ Load, LoadedBuildUnit } -import sbt.internal.util.{ AttributeKey, Dag, Types } +import sbt.internal.util.{ AttributeKey, Dag } import sbt.librarymanagement.{ ConfigRef, Configuration } -import Types.const +import sbt.internal.util.Types.const import Def.Initialize import sbt.Project.inScope import java.net.URI +sealed abstract class ScopeFilter { self => + + /** Implements this filter. */ + private[ScopeFilter] def apply(data: ScopeFilter.Data): Set[Scope] + + /** Constructs a filter that selects values that match this filter but not `other`. */ + def --(other: ScopeFilter): ScopeFilter = this && -other + + /** Constructs a filter that selects values that match this filter and `other`. */ + def &&(other: ScopeFilter): ScopeFilter = new ScopeFilter: + def apply(data: ScopeFilter.Data): Set[Scope] = self(data).intersect(other(data)) + + /** Constructs a filter that selects values that match this filter or `other`. */ + def ||(other: ScopeFilter): ScopeFilter = new ScopeFilter: + def apply(data: ScopeFilter.Data): Set[Scope] = self(data) ++ other(data) + + /** Constructs a filter that selects values that do not match this filter. */ + def unary_- : ScopeFilter = new ScopeFilter: + def apply(data: ScopeFilter.Data): Set[Scope] = data.allScopes.set -- self(data) +} + object ScopeFilter { - type ScopeFilter = Base[Scope] - type AxisFilter[T] = Base[ScopeAxis[T]] type ProjectFilter = AxisFilter[Reference] type ConfigurationFilter = AxisFilter[ConfigKey] type TaskFilter = AxisFilter[AttributeKey[_]] + private type ScopeMap = Map[ + ScopeAxis[Reference], + Map[ + ScopeAxis[ConfigKey], + Map[ScopeAxis[AttributeKey[_]], Set[Scope]] + ] + ] + /** * Construct a Scope filter from a sequence of individual scopes. */ - def in(scopes: Seq[Scope]): ScopeFilter = { + def in(scopes: Seq[Scope]): ScopeFilter = val scopeSet = scopes.toSet - new ScopeFilter { - override private[ScopeFilter] def apply(data: Data): Scope => Boolean = scopeSet.contains - } - } + new ScopeFilter: + def apply(data: Data): Set[Scope] = data.allScopes.set.intersect(scopeSet) /** * Constructs a Scope filter from filters for the individual axes. @@ -45,64 +70,49 @@ object ScopeFilter { configurations: ConfigurationFilter = zeroAxis, tasks: TaskFilter = zeroAxis ): ScopeFilter = - new ScopeFilter { - private[sbt] def apply(data: Data): Scope => Boolean = { + new ScopeFilter: + def apply(data: Data): Set[Scope] = val pf = projects(data) val cf = configurations(data) val tf = tasks(data) - s => pf(s.project) && cf(s.config) && tf(s.task) - } - } + val res = + for { + (project, configs) <- data.allScopes.grouped.iterator if pf(project) + (config, tasks) <- configs.iterator if cf(config) + (task, scopes) <- tasks.iterator if tf(task) + scope <- scopes + } yield scope + res.toSet def debug(delegate: ScopeFilter): ScopeFilter = - new ScopeFilter { - private[sbt] def apply(data: Data): Scope => Boolean = { - val d = delegate(data) - scope => { - val accept = d(scope) - println((if (accept) "ACCEPT " else "reject ") + scope) - accept - } - } - } + new ScopeFilter: + def apply(data: Data): Set[Scope] = + val res = delegate(data) + println(s"ACCEPT $res") + res final class SettingKeyAll[A] private[sbt] (i: Initialize[A]): - /** * 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 => - data.allScopes.toSeq - .withFilter(sfilter(data)) - .map(s => Project.inScope(s, i)) - .join - } - end SettingKeyAll + def all(sfilter: => ScopeFilter): Initialize[Seq[A]] = Def.flatMap(getData) { data => + sfilter(data).toSeq.map(s => Project.inScope(s, i)).join + } final class TaskKeyAll[A] private[sbt] (i: Initialize[Task[A]]): - /** * 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._ - data.allScopes.toSeq - .withFilter(sfilter(data)) - .map(s => Project.inScope(s, i)) - .join(_.join) - } - end TaskKeyAll + 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) + } private[sbt] val Make = new Make {} trait Make { - /** Selects the Scopes used in `.all()`. */ - type ScopeFilter = Base[Scope] - /** Selects Scopes with a Zero task axis. */ def inZeroTask: TaskFilter = zeroAxis[AttributeKey[_]] @@ -191,10 +201,13 @@ object ScopeFilter { selectAxis[ConfigKey](const(cs)) } - implicit def settingKeyAll[T](key: Initialize[T]): SettingKeyAll[T] = new SettingKeyAll[T](key) + implicit def settingKeyAll[T](key: Initialize[T]): SettingKeyAll[T] = + new SettingKeyAll[T](key) implicit def taskKeyAll[T](key: Initialize[Task[T]]): TaskKeyAll[T] = new TaskKeyAll[T](key) } + private[sbt] final class AllScopes(val set: Set[Scope], val grouped: ScopeMap) + /** * Information provided to Scope filters. These provide project relationships, * project reference resolution, and the list of all static Scopes. @@ -202,14 +215,25 @@ object ScopeFilter { private[sbt] final class Data( val units: Map[URI, LoadedBuildUnit], val resolve: ProjectReference => ProjectRef, - val allScopes: Set[Scope] + val allScopes: AllScopes ) + private[sbt] val allScopes: Initialize[AllScopes] = Def.setting { + val scopes = Def.StaticScopes.value + val grouped: ScopeMap = + scopes + .groupBy(_.project) + .map { case (k, v) => + k -> v.groupBy(_.config).map { case (k, v) => k -> v.groupBy(_.task) } + } + new AllScopes(scopes, grouped) + } + /** Constructs a Data instance from the list of static scopes and the project relationships. */ private[this] val getData: Initialize[Data] = Def.setting { val build = Keys.loadedBuild.value - val scopes = Def.StaticScopes.value + val scopes = Keys.allScopes.value val thisRef = Keys.thisProjectRef.?.value val current = thisRef match { case Some(ProjectRef(uri, _)) => uri @@ -262,33 +286,31 @@ object ScopeFilter { selectAxis(data => projects(data).toSet) private[this] def zeroAxis[T]: AxisFilter[T] = new AxisFilter[T] { - private[sbt] def apply(data: Data): ScopeAxis[T] => Boolean = - _ == Zero + private[sbt] def apply(data: Data): ScopeAxis[T] => Boolean = _ == Zero } private[this] def selectAny[T]: AxisFilter[T] = selectAxis(const(const(true))) private[this] def selectAxis[T](f: Data => T => Boolean): AxisFilter[T] = new AxisFilter[T] { private[sbt] def apply(data: Data): ScopeAxis[T] => Boolean = { val g = f(data) - s => - s match { - case Select(t) => g(t) - case _ => false - } + _ match { + case Select(t) => g(t) + case _ => false + } } } - /** Base functionality for filters on values of type `In` that need access to build data. */ - sealed abstract class Base[In] { self => + /** Base functionality for filters on axis of type `In` that need access to build data. */ + sealed abstract class AxisFilter[In] { self => /** Implements this filter. */ - private[ScopeFilter] def apply(data: Data): In => Boolean + private[ScopeFilter] def apply(data: Data): ScopeAxis[In] => Boolean /** Constructs a filter that selects values that match this filter but not `other`. */ - def --(other: Base[In]): Base[In] = this && -other + def --(other: AxisFilter[In]): AxisFilter[In] = this && -other /** Constructs a filter that selects values that match this filter and `other`. */ - def &&(other: Base[In]): Base[In] = new Base[In] { - private[sbt] def apply(data: Data): In => Boolean = { + def &&(other: AxisFilter[In]): AxisFilter[In] = new AxisFilter[In] { + private[sbt] def apply(data: Data): ScopeAxis[In] => Boolean = { val a = self(data) val b = other(data) s => a(s) && b(s) @@ -296,8 +318,8 @@ object ScopeFilter { } /** Constructs a filter that selects values that match this filter or `other`. */ - def ||(other: Base[In]): Base[In] = new Base[In] { - private[sbt] def apply(data: Data): In => Boolean = { + def ||(other: AxisFilter[In]): AxisFilter[In] = new AxisFilter[In] { + private[sbt] def apply(data: Data): ScopeAxis[In] => Boolean = { val a = self(data) val b = other(data) s => a(s) || b(s) @@ -305,8 +327,8 @@ object ScopeFilter { } /** Constructs a filter that selects values that do not match this filter. */ - def unary_- : Base[In] = new Base[In] { - private[sbt] def apply(data: Data): In => Boolean = { + def unary_- : AxisFilter[In] = new AxisFilter[In] { + private[sbt] def apply(data: Data): ScopeAxis[In] => Boolean = { val a = self(data) s => !a(s) } From 9e92338884da145411385ffbac65276d149e07e1 Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Tue, 10 Sep 2024 13:52:38 +0200 Subject: [PATCH 2/2] Adapt multi-scope test --- sbt-app/src/sbt-test/actions/multi-scope/build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt-app/src/sbt-test/actions/multi-scope/build.sbt b/sbt-app/src/sbt-test/actions/multi-scope/build.sbt index fd5bc2492..5a1cb9c78 100644 --- a/sbt-app/src/sbt-test/actions/multi-scope/build.sbt +++ b/sbt-app/src/sbt-test/actions/multi-scope/build.sbt @@ -1,7 +1,7 @@ lazy val taskX = taskKey[Set[Int]]("numbers") lazy val filterX = ScopeFilter( inDependencies(ThisProject, transitive=false, includeRoot=false) ) -lazy val filterA: ScopeFilter.ScopeFilter = ScopeFilter( +lazy val filterA: ScopeFilter = ScopeFilter( inAggregates( LocalProject(e.id) ), inConfigurations(Compile,Test) || inZeroConfiguration, inTasks(console) || inZeroTask