API for evaluating a setting or task in multiple scopes

This commit is contained in:
Mark Harrah 2013-03-27 09:17:53 -04:00
parent 2f13b7a8c7
commit df5e79e3be
4 changed files with 191 additions and 1 deletions

View File

@ -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)
}
}
}
}

View File

@ -3,6 +3,7 @@
*/ */
package object sbt extends sbt.std.TaskExtra with sbt.Types with sbt.ProcessExtra with sbt.impl.DependencyBuilders 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.PathExtra with sbt.ProjectExtra with sbt.DependencyFilterExtra with sbt.BuildExtra with sbt.TaskMacroExtra
with sbt.ScopeFilter.Make
{ {
@deprecated("Renamed to CommandStrings.", "0.12.0") @deprecated("Renamed to CommandStrings.", "0.12.0")
val CommandSupport = CommandStrings val CommandSupport = CommandStrings

View File

@ -20,12 +20,14 @@ abstract class EvaluateSettings[Scope]
private[this] val complete = new LinkedBlockingQueue[Option[Throwable]] private[this] val complete = new LinkedBlockingQueue[Option[Throwable]]
private[this] val static = PMap.empty[ScopedKey, INode] 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] 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 { 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 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 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 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 v: Value[T] => constant(v.value)
case t: TransformCapture => constant(() => t.f) case t: TransformCapture => constant(() => t.f)
case o: Optional[s,T] => o.a match { 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))) 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 => (cs.settings map { s =>
val t = transform(s.init) val t = transform(s.init)
static(s.key) = t static(s.key) = t

View File

@ -353,6 +353,7 @@ trait Init[Scope]
def mapConstant(g: MapConstant) = this def mapConstant(g: MapConstant) = this
def evaluate(map: Settings[Scope]): T = value() 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] 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)) def dependencies = deps(alist.toList(inputs))