diff --git a/main/src/main/scala/sbt/ScopeFilter.scala b/main/src/main/scala/sbt/ScopeFilter.scala new file mode 100644 index 000000000..8a97e4c91 --- /dev/null +++ b/main/src/main/scala/sbt/ScopeFilter.scala @@ -0,0 +1,186 @@ +package sbt + + import Types.{const, idFun} + import Def.Initialize + import java.net.URI + import ScopeFilter.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[_]] + + /** Constructs a Scope filter from filters for the individual axes. + * If a project filter is not supplied, the enclosing project is selected. + * If a configuration filter is not supplied, global is selected. + * If a task filter is not supplied, global is selected. + * Generally, always specify the project axis.*/ + def apply(projects: ProjectFilter = inProjects(ThisProject), configurations: ConfigurationFilter = globalAxis, tasks: TaskFilter = globalAxis): ScopeFilter = + new ScopeFilter { + private[sbt] def apply(data: Data): Scope => Boolean = + { + val pf = projects(data) + val cf = configurations(data) + val tf = tasks(data) + s => pf(s.project) && cf(s.config) && tf(s.task) + } + } + + final class SettingKeyAll[T] private[sbt](i: Initialize[T]) { + /** 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[T]] = Def.bind(getData) { data => + data.allScopes.toSeq.filter(sfilter(data)).map(s => Project.inScope(s, i)).join + } + } + final class TaskKeyAll[T] private[sbt](i: Initialize[Task[T]]) { + /** 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[T]]] = Def.bind(getData) { data => + import std.TaskExtra._ + data.allScopes.toSeq.filter(sfilter(data)).map(s => Project.inScope(s, i)).join(_.join) + } + } + + trait Make + { + /** Selects Scopes with a global task axis. */ + def inGlobalTask: TaskFilter = globalAxis[AttributeKey[_]] + /** Selects Scopes with a global project axis. */ + def inGlobalProject: ProjectFilter = globalAxis[Reference] + /** Selects Scopes with a global configuration axis. */ + def inGlobalConfiguration: ConfigurationFilter = globalAxis[ConfigKey] + + /** Selects all scopes that apply to a single project. Global and build-level scopes are excluded. */ + def inAnyProject: ProjectFilter = selectAxis(const { case p: ProjectRef => true; case _ => false }) + /** Accepts all values for the task axis except Global. */ + def inAnyTask: TaskFilter = selectAny[AttributeKey[_]] + /** Accepts all values for the configuration axis except Global. */ + def inAnyConfiguration: ConfigurationFilter = selectAny[ConfigKey] + + /** Selects Scopes that have a project axis that is aggregated by `ref`, transitively if `transitive` is true. + * If `includeRoot` is true, Scopes with `ref` itself as the project axis value are also selected. */ + def inAggregates(ref: ProjectReference, transitive: Boolean=true, includeRoot: Boolean=true): ProjectFilter = + byDeps(ref, transitive=transitive, includeRoot=includeRoot, aggregate=true, classpath=false) + + /** Selects Scopes that have a project axis that is a dependency of `ref`, transitively if `transitive` is true. + * If `includeRoot` is true, Scopes with `ref` itself as the project axis value are also selected. */ + def inDependencies(ref: ProjectReference, transitive: Boolean=true, includeRoot: Boolean=true): ProjectFilter = + byDeps(ref, transitive=transitive, includeRoot=includeRoot, aggregate=false, classpath=true) + + /** Selects Scopes that have a project axis with one of the provided values.*/ + def inProjects(projects: ProjectReference*): ProjectFilter = ScopeFilter.inProjects(projects : _*) + + /** Selects Scopes that have a task axis with one of the provided values.*/ + def inTasks(tasks: Scoped*): TaskFilter = { + val ts = tasks.map(_.key).toSet + selectAxis[AttributeKey[_]](const(ts)) + } + + /** Selects Scopes that have a task axis with one of the provided values.*/ + def inConfigurations(configs: Configuration*): ConfigurationFilter = { + val cs = configs.map(_.name).toSet + selectAxis[ConfigKey](const(c => cs(c.name))) + } + + 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) + } + + /** Information provided to Scope filters. These provide project relationships, + * project reference resolution, and the list of all static Scopes.*/ + private final class Data(val units: Map[URI, LoadedBuildUnit], val resolve: ProjectReference => ProjectRef, val allScopes: Set[Scope]) + + /** 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 current = Keys.thisProjectRef.?.value match { + case Some(ProjectRef(uri, _)) => uri + case None => build.root + } + val rootProject = Load.getRootProject(build.units) + val resolve: ProjectReference => ProjectRef = p => Scope.resolveProjectRef(current, rootProject, p) + new Data(build.units, resolve, scopes) + } + + private[this] def getDependencies(structure: Map[URI,LoadedBuildUnit], classpath: Boolean, aggregate: Boolean): ProjectRef => Seq[ProjectRef] = + ref => Project.getProject(ref, structure).toList flatMap { p => + (if(classpath) p.dependencies.map(_.project) else Nil) ++ + (if(aggregate) p.aggregate else Nil) + } + + private[this] def byDeps(ref: ProjectReference, transitive: Boolean, includeRoot: Boolean, aggregate: Boolean, classpath: Boolean): ProjectFilter = + inResolvedProjects { data => + val resolvedRef = data.resolve(ref) + val direct = getDependencies(data.units, classpath=classpath, aggregate=aggregate) + if(transitive) { + val full = Dag.topologicalSort(resolvedRef)(direct) + if(includeRoot) full else full dropRight 1 + } else { + val directDeps = direct(resolvedRef) + if(includeRoot) resolvedRef +: directDeps else directDeps + } + } + + private def inProjects(projects: ProjectReference*): ProjectFilter = + inResolvedProjects( data => projects.map(data.resolve) ) + + private[this] def inResolvedProjects(projects: Data => Seq[ProjectRef]): ProjectFilter = + selectAxis(data => projects(data).toSet) + + private[this] def globalAxis[T]: AxisFilter[T] = new AxisFilter[T] { + private[sbt] def apply(data: Data): ScopeAxis[T] => Boolean = + _ == Global + } + 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 + } + } + } + + /** Base functionality for filters on values of type `In` that need access to build data.*/ + sealed abstract class Base[In] + { self => + /** Implements this filter. */ + private[sbt] def apply(data: Data): In => Boolean + + /** Constructs a filter that selects values that match this filter but not `other`.*/ + def --(other: Base[In]): Base[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 = { + val a = self(data) + val b = other(data) + s => a(s) && b(s) + } + } + + /** 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 = { + val a = self(data) + val b = other(data) + s => a(s) || b(s) + } + } + /** 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 = { + val a = self(data) + s => !a(s) + } + } + } + +} \ No newline at end of file diff --git a/sbt/src/main/scala/package.scala b/sbt/src/main/scala/package.scala index c820cbae6..34eeb1939 100644 --- a/sbt/src/main/scala/package.scala +++ b/sbt/src/main/scala/package.scala @@ -3,6 +3,7 @@ */ package object sbt extends sbt.std.TaskExtra with sbt.Types with sbt.ProcessExtra with sbt.impl.DependencyBuilders with sbt.PathExtra with sbt.ProjectExtra with sbt.DependencyFilterExtra with sbt.BuildExtra with sbt.TaskMacroExtra + with sbt.ScopeFilter.Make { @deprecated("Renamed to CommandStrings.", "0.12.0") val CommandSupport = CommandStrings diff --git a/util/collection/src/main/scala/sbt/INode.scala b/util/collection/src/main/scala/sbt/INode.scala index 6c2e845ba..4ce5ef8bb 100644 --- a/util/collection/src/main/scala/sbt/INode.scala +++ b/util/collection/src/main/scala/sbt/INode.scala @@ -20,12 +20,14 @@ abstract class EvaluateSettings[Scope] private[this] val complete = new LinkedBlockingQueue[Option[Throwable]] private[this] val static = PMap.empty[ScopedKey, INode] + private[this] val allScopes: Set[Scope] = compiledSettings.map(_.key.scope).toSet private[this] def getStatic[T](key: ScopedKey[T]): INode[T] = static get key getOrElse sys.error("Illegal reference to key " + key) private[this] val transform: Initialize ~> INode = new (Initialize ~> INode) { def apply[T](i: Initialize[T]): INode[T] = i match { case k: Keyed[s, T] => single(getStatic(k.scopedKey), k.transform) case a: Apply[k,T] => new MixedNode[k,T]( a.alist.transform[Initialize, INode](a.inputs, transform), a.f, a.alist) case b: Bind[s,T] => new BindNode[s,T]( transform(b.in), x => transform(b.f(x))) + case init.StaticScopes => constant(() => allScopes.asInstanceOf[T]) // can't convince scalac that StaticScopes => T == Set[Scope] case v: Value[T] => constant(v.value) case t: TransformCapture => constant(() => t.f) case o: Optional[s,T] => o.a match { @@ -33,7 +35,7 @@ abstract class EvaluateSettings[Scope] case Some(i) => single[s,T](transform(i), x => o.f(Some(x))) } }} - private[this] val roots: Seq[INode[_]] = compiledSettings flatMap { cs => + private[this] lazy val roots: Seq[INode[_]] = compiledSettings flatMap { cs => (cs.settings map { s => val t = transform(s.init) static(s.key) = t diff --git a/util/collection/src/main/scala/sbt/Settings.scala b/util/collection/src/main/scala/sbt/Settings.scala index 08f347ccd..cf12a58fc 100644 --- a/util/collection/src/main/scala/sbt/Settings.scala +++ b/util/collection/src/main/scala/sbt/Settings.scala @@ -353,6 +353,7 @@ trait Init[Scope] def mapConstant(g: MapConstant) = this def evaluate(map: Settings[Scope]): T = value() } + private[sbt] final val StaticScopes: Initialize[Set[Scope]] = new Value(() => error("internal sbt error: GetScopes not substituted")) private[sbt] final class Apply[K[L[x]], T](val f: K[Id] => T, val inputs: K[Initialize], val alist: AList[K]) extends Initialize[T] { def dependencies = deps(alist.toList(inputs))