From 82326cc899c64fe3c1e86eb909a28f0ff5135cc4 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Mon, 9 Jan 2012 08:00:29 -0500 Subject: [PATCH] new aggregation approach, still need exclusion mechanism --- main/Act.scala | 112 +++++++++++++++++++--------- main/Aggregation.scala | 44 ++--------- main/BuildUtil.scala | 39 ++++++++++ main/KeyIndex.scala | 18 ++++- main/Load.scala | 28 +++++-- main/Main.scala | 19 +++-- main/Output.scala | 18 ++--- main/Project.scala | 3 +- main/Resolve.scala | 84 +++++++++++++++++++++ main/src/test/scala/ParseKey.scala | 34 +-------- main/src/test/scala/TestBuild.scala | 11 +-- util/collection/Relation.scala | 1 - 12 files changed, 272 insertions(+), 139 deletions(-) create mode 100644 main/BuildUtil.scala create mode 100644 main/Resolve.scala diff --git a/main/Act.scala b/main/Act.scala index a65a03c26..442872e59 100644 --- a/main/Act.scala +++ b/main/Act.scala @@ -7,11 +7,13 @@ package sbt import Keys.{sessionSettings, thisProject} import Load.BuildStructure import complete.{DefaultParsers, Parser} + import Aggregation.{KeyValue,Values} import DefaultParsers._ import Types.idFun import java.net.URI import CommandSupport.ShowCommand +final class ParsedKey(val key: ScopedKey[_], val mask: ScopeMask) object Act { val GlobalString = "*" @@ -19,29 +21,46 @@ object Act // this does not take aggregation into account def scopedKey(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], keyMap: Map[String, AttributeKey[_]], data: Settings[Scope]): Parser[ScopedKey[_]] = + scopedKeySelected(index, current, defaultConfigs, keyMap, data).map(_.key) + + // the index should be an aggregated index for proper tab completion + def scopedKeyAggregated(current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], structure: BuildStructure): KeysParser = + for(selected <- scopedKeySelected(structure.index.aggregateKeyIndex, current, defaultConfigs, structure.index.keyMap, structure.data) ) yield + Resolve.aggregateDeps(selected.key, selected.mask, structure.extra) + + def scopedKeySelected(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], + keyMap: Map[String, AttributeKey[_]], data: Settings[Scope]): Parser[ParsedKey] = + scopedKeyFull(index, current, defaultConfigs, keyMap) flatMap { choices => + select(choices, data)( Project.showRelativeKey(current, index.buildURIs.size > 1) ) + } + + def scopedKeyFull(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], keyMap: Map[String, AttributeKey[_]]): Parser[Seq[Parser[ParsedKey]]] = { - implicit val show = Project.showRelativeKey(current, index.buildURIs.size > 1) - def taskKeyExtra(proj: Option[ResolvedReference], confAmb: ParsedAxis[String]): Seq[Parser[ScopedKey[_]]] = + def taskKeyExtra(proj: Option[ResolvedReference], confAmb: ParsedAxis[String], baseMask: ScopeMask): Seq[Parser[ParsedKey]] = for { conf <- configs(confAmb, defaultConfigs, proj, index) } yield for { - task <- taskAxis(conf, index.tasks(proj, conf), keyMap) map resolveTask - key <- key(index, proj, conf, task, keyMap, data) + taskAmb <- taskAxis(conf, index.tasks(proj, conf), keyMap) + task = resolveTask(taskAmb) + key <- key(index, proj, conf, task, keyMap) extra <- extraAxis(keyMap, IMap.empty) - } yield - makeScopedKey( proj, conf, task, extra, key ) + } yield { + val mask = baseMask.copy(task = taskAmb.isExplicit, extra = true) + new ParsedKey( makeScopedKey( proj, conf, task, extra, key ), mask) + } for { - proj <- optProjectRef(index, current) + rawProject <- optProjectRef(index, current) + proj = resolveProject(rawProject, current) confAmb <- config( index configs proj ) - result <- select(taskKeyExtra(proj, confAmb), data) + partialMask = ScopeMask(rawProject.isExplicit, confAmb.isExplicit, false, false) } yield - result + taskKeyExtra(proj, confAmb, partialMask) } def makeScopedKey(proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]], extra: ScopeAxis[AttributeMap], key: AttributeKey[_]): ScopedKey[_] = ScopedKey( Scope( toAxis(proj, Global), toAxis(conf map ConfigKey.apply, Global), toAxis(task, Global), extra), key ) - def select(allKeys: Seq[Parser[ScopedKey[_]]], data: Settings[Scope])(implicit show: Show[ScopedKey[_]]): Parser[ScopedKey[_]] = + def select(allKeys: Seq[Parser[ParsedKey]], data: Settings[Scope])(implicit show: Show[ScopedKey[_]]): Parser[ParsedKey] = seq(allKeys) flatMap { ss => val default = ss.headOption match { case None => noValidKeys @@ -49,27 +68,28 @@ object Act } selectFromValid(ss filter isValid(data), default) } - def selectFromValid(ss: Seq[ScopedKey[_]], default: Parser[ScopedKey[_]])(implicit show: Show[ScopedKey[_]]): Parser[ScopedKey[_]] = + def selectFromValid(ss: Seq[ParsedKey], default: Parser[ParsedKey])(implicit show: Show[ScopedKey[_]]): Parser[ParsedKey] = selectByTask(selectByConfig(ss)) match { case Seq() => default case Seq(single) => success(single) - case multi => failure("Ambiguous keys: " + showAmbiguous(multi)) + case multi => failure("Ambiguous keys: " + showAmbiguous(keys(multi))) } - def selectByConfig(ss: Seq[ScopedKey[_]]): Seq[ScopedKey[_]] = + private[this] def keys(ss: Seq[ParsedKey]): Seq[ScopedKey[_]] = ss.map(_.key) + def selectByConfig(ss: Seq[ParsedKey]): Seq[ParsedKey] = ss match { case Seq() => Nil case Seq(x, tail @ _*) => // select the first configuration containing a valid key - tail.takeWhile(_.scope.config == x.scope.config) match + tail.takeWhile(_.key.scope.config == x.key.scope.config) match { case Seq() => x :: Nil case xs => x +: xs } } - def selectByTask(ss: Seq[ScopedKey[_]]): Seq[ScopedKey[_]] = + def selectByTask(ss: Seq[ParsedKey]): Seq[ParsedKey] = { - val (selects, globals) = ss.partition(_.scope.task.isSelect) + val (selects, globals) = ss.partition(_.key.scope.task.isSelect) if(globals.nonEmpty) globals else selects } @@ -78,8 +98,11 @@ object Act def showAmbiguous(keys: Seq[ScopedKey[_]])(implicit show: Show[ScopedKey[_]]): String = keys.take(3).map(x => show(x)).mkString("", ", ", if(keys.size > 3) ", ..." else "") - def isValid(data: Settings[Scope])(key: ScopedKey[_]): Boolean = + def isValid(data: Settings[Scope])(parsed: ParsedKey): Boolean = + { + val key = parsed.key data.definingScope(key.scope, key.key) == Some(key.scope) + } def examples(p: Parser[String], exs: Set[String], label: String): Parser[String] = p !!! ("Expected " + label) examples exs @@ -90,8 +113,6 @@ object Act p.? map { opt => toAxis(opt, ifNone) } def toAxis[T](opt: Option[T], ifNone: ScopeAxis[T]): ScopeAxis[T] = opt match { case Some(t) => Select(t); case None => ifNone } - def defaultConfigs(data: Settings[Scope])(project: ProjectRef): Seq[String] = - thisProject in project get data map( _.configurations.map(_.name)) getOrElse Nil def config(confs: Set[String]): Parser[ParsedAxis[String]] = token( (GlobalString ^^^ ParsedGlobal | value(examples(ID, confs, "configuration")) ) <~ ':' ) ?? Omitted @@ -106,8 +127,7 @@ object Act def nonEmptyConfig(index: KeyIndex, proj: Option[ResolvedReference]): String => Seq[Option[String]] = config => if(index.isEmpty(proj, Some(config))) Nil else Some(config) :: Nil - def key(index: KeyIndex, proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]], keyMap: Map[String,AttributeKey[_]], data: Settings[Scope]): - Parser[AttributeKey[_]] = + def key(index: KeyIndex, proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]], keyMap: Map[String,AttributeKey[_]]): Parser[AttributeKey[_]] = { def keyParser(keys: Set[String]): Parser[AttributeKey[_]] = token(ID !!! "Expected key" examples keys) flatMap { keyString=> @@ -169,10 +189,10 @@ object Act def knownIDParser[T](knownKeys: Map[String, T], label: String): Parser[T] = token(examplesStrict(ID, knownKeys.keys.toSet, label)) map knownKeys - def projectRef(index: KeyIndex, currentBuild: URI): Parser[Option[ResolvedReference]] = + def projectRef(index: KeyIndex, currentBuild: URI): Parser[ParsedAxis[ResolvedReference]] = { - val global = token(GlobalString ~ '/') ^^^ None - global | some(resolvedReference(index, currentBuild, '/')) + val global = token(GlobalString ~ '/') ^^^ ParsedGlobal + global | value(resolvedReference(index, currentBuild, '/')) } def resolvedReference(index: KeyIndex, currentBuild: URI, trailing: Parser[_]): Parser[ResolvedReference] = { @@ -188,37 +208,55 @@ object Act case Some(uri) => projectRef(uri) | token(trailing ^^^ BuildRef(uri)) } } - def optProjectRef(index: KeyIndex, current: ProjectRef): Parser[Option[ResolvedReference]] = - projectRef(index, current.build) ?? Some(current) + def optProjectRef(index: KeyIndex, current: ProjectRef): Parser[ParsedAxis[ResolvedReference]] = + projectRef(index, current.build) ?? Omitted + def resolveProject(parsed: ParsedAxis[ResolvedReference], current: ProjectRef): Option[ResolvedReference] = + parsed match + { + case Omitted => Some(current) + case ParsedGlobal => None + case pv: ParsedValue[ResolvedReference] => Some(pv.value) + } def actParser(s: State): Parser[() => State] = requireSession(s, actParser0(s)) - private[this] def actParser0(state: State) = + private[this] def actParser0(state: State): Parser[() => State] = { val extracted = Project extract state import extracted.{showKey, structure} showParser.flatMap { show => - scopedKeyParser(extracted) flatMap Aggregation.valueParser(state, structure, show) + aggregatedKeyParser(extracted) flatMap { kvs => + Aggregation.evaluatingParser(state, structure, show)( keyValues(structure)(kvs) ) + } } } def showParser = token( (ShowCommand ~ Space) ^^^ true) ?? false def scopedKeyParser(state: State): Parser[ScopedKey[_]] = scopedKeyParser(Project extract state) def scopedKeyParser(extracted: Extracted): Parser[ScopedKey[_]] = scopedKeyParser(extracted.structure, extracted.currentRef) def scopedKeyParser(structure: BuildStructure, currentRef: ProjectRef): Parser[ScopedKey[_]] = - { - def confs(uri: URI) = if(structure.units.contains(uri)) defaultConfigs(structure.data)(ProjectRef(uri, structure.rootProject(uri))) else Nil - val defaultConfs: Option[ResolvedReference] => Seq[String] = { - case None => confs(structure.root) - case Some(BuildRef(uri)) => confs(uri) - case Some(ref: ProjectRef) => if(Project.getProject(ref, structure).isDefined) defaultConfigs(structure.data)(ref) else Nil + scopedKey(structure.index.keyIndex, currentRef, structure.extra.configurationsForAxis, structure.index.keyMap, structure.data) + + type KeysParser = Parser[Seq[ScopedKey[T]] forSome { type T}] + def aggregatedKeyParser(state: State): KeysParser = aggregatedKeyParser(Project extract state) + def aggregatedKeyParser(extracted: Extracted): KeysParser = aggregatedKeyParser(extracted.structure, extracted.currentRef) + def aggregatedKeyParser(structure: BuildStructure, currentRef: ProjectRef): KeysParser = + scopedKeyAggregated(currentRef, structure.extra.configurationsForAxis, structure) + + def keyValues[T](state: State)(keys: Seq[ScopedKey[T]]): Values[T] = keyValues(Project extract state)(keys) + def keyValues[T](extracted: Extracted)(keys: Seq[ScopedKey[T]]): Values[T] = keyValues(extracted.structure)(keys) + def keyValues[T](structure: BuildStructure)(keys: Seq[ScopedKey[T]]): Values[T] = + keys.flatMap { key => + structure.data.get(key.scope, key.key) map { value => + KeyValue(key, value) + } } - scopedKey(structure.index.keyIndex, currentRef, defaultConfs, structure.index.keyMap, structure.data) - } def requireSession[T](s: State, p: => Parser[T]): Parser[T] = if(s get sessionSettings isEmpty) failure("No project loaded") else p - sealed trait ParsedAxis[+T] + sealed trait ParsedAxis[+T] { + final def isExplicit = this != Omitted + } final object ParsedGlobal extends ParsedAxis[Nothing] final object Omitted extends ParsedAxis[Nothing] final class ParsedValue[T](val value: T) extends ParsedAxis[T] diff --git a/main/Aggregation.scala b/main/Aggregation.scala index 2423c5d7a..b42f45f52 100644 --- a/main/Aggregation.scala +++ b/main/Aggregation.scala @@ -22,42 +22,6 @@ final object Aggregation final class Explicit(val dependencies: Seq[ProjectReference], val transitive: Boolean) extends Aggregation final case class KeyValue[+T](key: ScopedKey[_], value: T) - def getTasks[T](key: ScopedKey[T], structure: BuildStructure, transitive: Boolean): Seq[KeyValue[T]] = - getTasks0(key, structure, transitive, new mutable.HashMap[(ScopedKey[_], Boolean), Seq[KeyValue[T]]]) - private type Memo[T] = mutable.Map[(ScopedKey[_], Boolean), Seq[KeyValue[T]]] - private[this] def getTasks0[T](key: ScopedKey[T], structure: BuildStructure, transitive: Boolean, memo: Memo[T]): Seq[KeyValue[T]] = - memo.getOrElseUpdate( (key, transitive), { - val task = structure.data.get(key.scope, key.key).toList.map(t => KeyValue(key,t)) - if(transitive) aggregateDeps(key, structure, memo) ++ task else task - }) - def projectAggregate(key: ScopedKey[_], structure: BuildStructure): Seq[ProjectRef] = - { - val project = key.scope.project.toOption.flatMap { ref => Project.getProjectForReference(ref, structure) } - project match { case Some(p) => p.aggregate; case None => Nil } - } - private[this] def aggregateDeps[T](key: ScopedKey[T], structure: BuildStructure, memo: Memo[T]): Seq[KeyValue[T]] = - { - val aggregated = aggregate in Scope.fillTaskAxis(key.scope, key.key) get structure.data getOrElse Enabled - val (agg, transitive) = - aggregated match - { - case Implicit(false) => (Nil, false) - case Implicit(true) => (projectAggregate(key, structure), true) - case e: Explicit => (e.dependencies, e.transitive) - } - val currentBuild = key.scope.project.toOption.flatMap { case ProjectRef(uri, _) => Some(uri); case BuildRef(ref) => Some(ref); case _ => None } - agg flatMap { a => - val resolved = subCurrentBuild(a, currentBuild) - val newKey = ScopedKey(key.scope.copy(project = Select(resolved)), key.key) - getTasks(newKey, structure, transitive) - } - } - private def subCurrentBuild(ref: Reference, currentBuild: Option[URI]): Reference = - currentBuild match - { - case None => ref - case Some(current) => Scope.resolveBuildOnly(current, ref) - } def printSettings[T](xs: Seq[KeyValue[T]], log: Logger)(implicit display: Show[ScopedKey[_]]) = xs match @@ -66,6 +30,7 @@ final object Aggregation case _ => xs foreach { case KeyValue(key, value) => log.info(display(key) + "\n\t" + value.toString) } } type Values[T] = Seq[KeyValue[T]] + type AnyKeys = Values[_] def seqParser[T](ps: Values[Parser[T]]): Parser[Seq[KeyValue[T]]] = seq(ps.map { case KeyValue(k,p) => p.map(v => KeyValue(k,v) ) }) def applyTasks[T](s: State, structure: BuildStructure, ps: Values[Parser[Task[T]]], show: Boolean)(implicit display: Show[ScopedKey[_]]): Parser[() => State] = @@ -141,10 +106,11 @@ final object Aggregation runTasks(s, structure, roots, dummies, show) } } - def valueParser(s: State, structure: BuildStructure, show: Boolean)(key: ScopedKey[_])(implicit display: Show[ScopedKey[_]]): Parser[() => State] = - getTasks(key, structure, true).toList match + + def evaluatingParser[T](s: State, structure: BuildStructure, show: Boolean)(keys: Seq[KeyValue[T]])(implicit display: Show[ScopedKey[_]]): Parser[() => State] = + keys.toList match { - case Nil => failure("No such setting/task: " + display(key)) + case Nil => failure("No such setting/task") case xs @ KeyValue(_, _: InputStatic[t]) :: _ => applyTasks(s, structure, maps(xs.asInstanceOf[Values[InputStatic[t]]])(_.parser(s)), show) case xs @ KeyValue(_, _: InputDynamic[t]) :: _ => applyDynamicTasks(s, structure, xs.asInstanceOf[Values[InputDynamic[t]]], show) case xs @ KeyValue(_, _: Task[t]) :: _ => applyTasks(s, structure, maps(xs.asInstanceOf[Values[Task[t]]])(x => success(x)), show) diff --git a/main/BuildUtil.scala b/main/BuildUtil.scala new file mode 100644 index 000000000..a358215fa --- /dev/null +++ b/main/BuildUtil.scala @@ -0,0 +1,39 @@ +package sbt + +import java.net.URI + +final class BuildUtil[Proj]( + val keyIndex: KeyIndex, + val root: URI, + val rootProjectID: URI => String, + val project: (URI, String) => Proj, + val configurations: Proj => Seq[ConfigKey], + val aggregates: Relation[ProjectRef, ProjectRef] +) +{ + def rootProject(uri: URI): Proj = + project(uri, rootProjectID(uri)) + + def resolveRef(ref: Reference): ResolvedReference = + Scope.resolveReference(root, rootProjectID, ref) + + def projectFor(ref: ResolvedReference): Proj = ref match { + case ProjectRef(uri, id) => project(uri, id) + case BuildRef(uri) => rootProject(uri) + } + def projectRefFor(ref: ResolvedReference): ProjectRef = ref match { + case p: ProjectRef => p + case BuildRef(uri) => ProjectRef(uri, rootProjectID(uri)) + } + def projectForAxis(ref: Option[ResolvedReference]): Proj = ref match { + case Some(ref) => projectFor(ref) + case None => rootProject(root) + } + def exactProject(refOpt: Option[Reference]): Option[Proj] = refOpt map resolveRef flatMap { + case ProjectRef(uri, id) => Some(project(uri, id)) + case _ => None + } + + val configurationsForAxis: Option[ResolvedReference] => Seq[String] = + refOpt => configurations(projectForAxis(refOpt)).map(_.name) +} \ No newline at end of file diff --git a/main/KeyIndex.scala b/main/KeyIndex.scala index 9a58c4809..2383e78b8 100644 --- a/main/KeyIndex.scala +++ b/main/KeyIndex.scala @@ -5,6 +5,7 @@ package sbt import java.net.URI import Project.ScopedKey + import Load.BuildStructure import complete.DefaultParsers.validID import Types.{idFun, some} @@ -13,6 +14,9 @@ object KeyIndex def empty: ExtendableKeyIndex = new KeyIndex0(emptyBuildIndex) def apply(known: Iterable[ScopedKey[_]]): ExtendableKeyIndex = (empty /: known) { _ add _ } + def aggregate(known: Iterable[ScopedKey[_]], extra: BuildUtil[_]): ExtendableKeyIndex = + (empty /: known) { (index, key) => index.addAggregated(key, extra) } + def combine(indices: Seq[KeyIndex]): KeyIndex = new KeyIndex { def buildURIs = concat(_.buildURIs) def projects(uri: URI) = concat(_.projects(uri)) @@ -52,6 +56,7 @@ trait KeyIndex trait ExtendableKeyIndex extends KeyIndex { def add(scoped: ScopedKey[_]): ExtendableKeyIndex + def addAggregated(scoped: ScopedKey[_], extra: BuildUtil[_]): ExtendableKeyIndex } // task axis <-> key private final class AKeyIndex(val data: Relation[ Option[AttributeKey[_]], String]) @@ -110,13 +115,22 @@ private final class KeyIndex0(val data: BuildIndex) extends ExtendableKeyIndex } private[this] def optConfigs(project: Option[ResolvedReference]): Seq[Option[String]] = None +: (configs(project).toSeq map some.fn) + def addAggregated(scoped: ScopedKey[_], extra: BuildUtil[_]): ExtendableKeyIndex = + if(validID(scoped.key.label)) + { + val aggregateProjects = Resolve.aggregateDeps(scoped, ScopeMask(), extra, reverse = true) + ((this: ExtendableKeyIndex) /: aggregateProjects)(_ add _) + } + else + this + def add(scoped: ScopedKey[_]): ExtendableKeyIndex = if(validID(scoped.key.label)) add0(scoped) else this private[this] def add0(scoped: ScopedKey[_]): ExtendableKeyIndex = { val (build, project) = parts(scoped.scope.project.toOption) - add(build, project, scoped.scope.config, scoped.scope.task, scoped.key) + add1(build, project, scoped.scope.config, scoped.scope.task, scoped.key) } - def add(uri: Option[URI], id: Option[String], config: ScopeAxis[ConfigKey], task: ScopeAxis[AttributeKey[_]], key: AttributeKey[_]): ExtendableKeyIndex = + private[this] def add1(uri: Option[URI], id: Option[String], config: ScopeAxis[ConfigKey], task: ScopeAxis[AttributeKey[_]], key: AttributeKey[_]): ExtendableKeyIndex = new KeyIndex0( data.add(uri, id, config.toOption.map(_.name), task.toOption, key) ) } diff --git a/main/Load.scala b/main/Load.scala index ca2a52e7e..e7b61e04f 100644 --- a/main/Load.scala +++ b/main/Load.scala @@ -121,7 +121,7 @@ object Load val settings = finalTransforms(buildConfigurations(loaded, getRootProject(projects), rootEval, config.injectSettings)) val delegates = config.delegates(loaded) val data = Project.makeSettings(settings, delegates, config.scopeLocal)( Project.showLoadingKey( loaded ) ) - val index = structureIndex(data, settings) + val index = structureIndex(data, settings, loaded.extra) val streams = mkStreams(projects, loaded.root, data) (rootEval, new BuildStructure(projects, loaded.root, settings, data, index, streams, delegates, config.scopeLocal)) } @@ -160,12 +160,14 @@ object Load def setDefinitionKey[T](tk: Task[T], key: ScopedKey[_]): Task[T] = if(isDummy(tk)) tk else Task(tk.info.set(Keys.taskDefinitionKey, key), tk.work) - def structureIndex(data: Settings[Scope], settings: Seq[Setting[_]]): StructureIndex = + def structureIndex(data: Settings[Scope], settings: Seq[Setting[_]], extra: KeyIndex => BuildUtil[_]): StructureIndex = { val keys = Index.allKeys(settings) val attributeKeys = Index.attributeKeys(data) ++ keys.map(_.key) val scopedKeys = keys ++ data.allKeys( (s,k) => ScopedKey(s,k)) - new StructureIndex(Index.stringToKeyMap(attributeKeys), Index.taskToKeyMap(data), Index.triggers(data), KeyIndex(scopedKeys)) + val keyIndex = KeyIndex(scopedKeys) + val aggIndex = KeyIndex.aggregate(scopedKeys, extra(keyIndex)) + new StructureIndex(Index.stringToKeyMap(attributeKeys), Index.taskToKeyMap(data), Index.triggers(data), keyIndex, aggIndex) } // Reevaluates settings after modifying them. Does not recompile or reload any build components. @@ -173,7 +175,7 @@ object Load { val transformed = finalTransforms(newSettings) val newData = Project.makeSettings(transformed, structure.delegates, structure.scopeLocal) - val newIndex = structureIndex(newData, transformed) + val newIndex = structureIndex(newData, transformed, index => buildUtil(structure.root, structure.units, index)) val newStreams = mkStreams(structure.units, structure.root, newData) new BuildStructure(units = structure.units, root = structure.root, settings = transformed, data = newData, index = newIndex, streams = newStreams, delegates = structure.delegates, scopeLocal = structure.scopeLocal) } @@ -545,6 +547,7 @@ object Load { checkCycles(units) def allProjectRefs: Seq[(ProjectRef, ResolvedProject)] = for( (uri, unit) <- units.toSeq; (id, proj) <- unit.defined ) yield ProjectRef(uri, id) -> proj + def extra(keyIndex: KeyIndex): BuildUtil[ResolvedProject] = buildUtil(root, units, keyIndex) } def checkCycles(units: Map[URI, LoadedBuildUnit]) { @@ -567,6 +570,7 @@ object Load final class LoadedBuildUnit(val unit: BuildUnit, val defined: Map[String, ResolvedProject], val rootProjects: Seq[String], val buildSettings: Seq[Setting[_]]) extends BuildUnitBase { assert(!rootProjects.isEmpty, "No root projects defined for build unit " + unit) + val root = rootProjects.head def localBase = unit.localBase def classpath: Seq[File] = unit.definitions.target ++ unit.plugins.classpath def loader = unit.definitions.loader @@ -584,8 +588,16 @@ object Load def allProjects(build: URI): Seq[ResolvedProject] = units(build).defined.values.toSeq def allProjectRefs: Seq[ProjectRef] = units.toSeq flatMap { case (build, unit) => refs(build, unit.defined.values.toSeq) } def allProjectRefs(build: URI): Seq[ProjectRef] = refs(build, allProjects(build)) + val extra: BuildUtil[ResolvedProject] = buildUtil(root, units, index.keyIndex) private[this] def refs(build: URI, projects: Seq[ResolvedProject]): Seq[ProjectRef] = projects.map { p => ProjectRef(build, p.id) } } + def buildUtil(root: URI, units: Map[URI, LoadedBuildUnit], keyIndex: KeyIndex): BuildUtil[ResolvedProject] = + { + val getp = (build: URI, project: String) => Load.getProject(units, build, project) + val configs = (_: ResolvedProject).configurations.map(c => ConfigKey(c.name)) + val aggregates = Resolve.aggregates(units) + new BuildUtil(keyIndex, root, Load getRootProject units, getp, configs, aggregates) + } final case class LoadBuildConfiguration(stagingDirectory: File, classpath: Seq[Attributed[File]], loader: ClassLoader, compilers: Compilers, evalPluginDef: (BuildStructure, State) => Seq[Attributed[File]], definesClass: DefinesClass, delegates: LoadedBuild => Scope => Seq[Scope], scopeLocal: ScopeLocal, injectSettings: InjectSettings, globalPlugin: Option[GlobalPlugin], log: Logger) { lazy val (globalPluginClasspath, globalPluginLoader) = pluginDefinitionLoader(this, Load.globalPluginClasspath(globalPlugin)) @@ -594,5 +606,11 @@ object Load final case class InjectSettings(global: Seq[Setting[_]], project: Seq[Setting[_]], projectLoaded: ClassLoader => Seq[Setting[_]]) // information that is not original, but can be reconstructed from the rest of BuildStructure - final class StructureIndex(val keyMap: Map[String, AttributeKey[_]], val taskToKey: Map[Task[_], ScopedKey[Task[_]]], val triggers: Triggers[Task], val keyIndex: KeyIndex) + final class StructureIndex( + val keyMap: Map[String, AttributeKey[_]], + val taskToKey: Map[Task[_], ScopedKey[Task[_]]], + val triggers: Triggers[Task], + val keyIndex: KeyIndex, + val aggregateKeyIndex: KeyIndex + ) } diff --git a/main/Main.scala b/main/Main.scala index d65cb905e..acb565613 100644 --- a/main/Main.scala +++ b/main/Main.scala @@ -8,6 +8,7 @@ package sbt import HistoryCommands.{Start => HistoryPrefix} import compiler.EvalImports import Types.{const,idFun} + import Aggregation.AnyKeys import Command.applyEffect import Keys.{analysis,historyPath,globalLogging,shellPrompt} @@ -427,9 +428,9 @@ object BuiltinCommands } } def lastGrep = Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) { - case (s, (pattern,Some(sk))) => + case (s, (pattern,Some(sks))) => val (str, ref, display) = extractLast(s) - Output.lastGrep(sk, str, str.streams(s), pattern, printLast(s))(display) + Output.lastGrep(sks, str.streams(s), pattern, printLast(s))(display) keepLastLog(s) case (s, (pattern, None)) => for(logFile <- lastLogFile(s)) yield @@ -460,13 +461,17 @@ object BuiltinCommands val keyMap = Project.structure(s).index.keyMap token(Space ~> (ID !!! "Expected key" examples keyMap.keySet)) flatMap { key => Act.getKey(keyMap, key, idFun) } } + val spacedKeyParser = (s: State) => Act.requireSession(s, token(Space) ~> Act.scopedKeyParser(s)) - val optSpacedKeyParser = (s: State) => spacedKeyParser(s).? - def lastGrepParser(s: State) = Act.requireSession(s, (token(Space) ~> token(NotSpace, "")) ~ optSpacedKeyParser(s)) - def last = Command(LastCommand, lastBrief, lastDetailed)(optSpacedKeyParser) { - case (s,Some(sk)) => + val spacedAggregatedParser = (s: State) => Act.requireSession(s, token(Space) ~> Act.aggregatedKeyParser(s)) + val aggregatedKeyValueParser: State => Parser[Option[AnyKeys]] = + (s: State) => spacedAggregatedParser(s).map(x => Act.keyValues(s)(x) ).? + + def lastGrepParser(s: State) = Act.requireSession(s, (token(Space) ~> token(NotSpace, "")) ~ aggregatedKeyValueParser(s)) + def last = Command(LastCommand, lastBrief, lastDetailed)(aggregatedKeyValueParser) { + case (s,Some(sks)) => val (str, ref, display) = extractLast(s) - Output.last(sk, str, str.streams(s), printLast(s))(display) + Output.last(sks, str.streams(s), printLast(s))(display) keepLastLog(s) case (s, None) => for(logFile <- lastLogFile(s)) yield diff --git a/main/Output.scala b/main/Output.scala index 7fb1ca977..665e24a81 100644 --- a/main/Output.scala +++ b/main/Output.scala @@ -7,8 +7,7 @@ package sbt import java.io.File import Keys.{Streams, TaskStreams} import Project.ScopedKey - import Load.BuildStructure - import Aggregation.{getTasks, KeyValue} + import Aggregation.{KeyValue, Values} import Types.idFun import annotation.tailrec import scala.Console.{BOLD, RESET} @@ -30,16 +29,16 @@ object Output } final val DefaultTail = "> " - def last(key: ScopedKey[_], structure: BuildStructure, streams: Streams, printLines: Seq[String] => Unit)(implicit display: Show[ScopedKey[_]]): Unit = - printLines( flatLines(lastLines(key, structure, streams))(idFun) ) + def last(keys: Values[_], streams: Streams, printLines: Seq[String] => Unit)(implicit display: Show[ScopedKey[_]]): Unit = + printLines( flatLines(lastLines(keys, streams))(idFun) ) def last(file: File, printLines: Seq[String] => Unit, tailDelim: String = DefaultTail): Unit = printLines(tailLines(file, tailDelim)) - def lastGrep(key: ScopedKey[_], structure: BuildStructure, streams: Streams, patternString: String, printLines: Seq[String] => Unit)(implicit display: Show[ScopedKey[_]]): Unit = + def lastGrep(keys: Values[_], streams: Streams, patternString: String, printLines: Seq[String] => Unit)(implicit display: Show[ScopedKey[_]]): Unit = { val pattern = Pattern compile patternString - val lines = flatLines( lastLines(key, structure, streams) )(_ flatMap showMatches(pattern)) + val lines = flatLines( lastLines(keys, streams) )(_ flatMap showMatches(pattern)) printLines( lines ) } def lastGrep(file: File, patternString: String, printLines: Seq[String] => Unit, tailDelim: String = DefaultTail): Unit = @@ -47,7 +46,7 @@ object Output def grep(lines: Seq[String], patternString: String): Seq[String] = lines flatMap showMatches(Pattern compile patternString) - def flatLines(outputs: Seq[KeyValue[Seq[String]]])(f: Seq[String] => Seq[String])(implicit display: Show[ScopedKey[_]]): Seq[String] = + def flatLines(outputs: Values[Seq[String]])(f: Seq[String] => Seq[String])(implicit display: Show[ScopedKey[_]]): Seq[String] = { val single = outputs.size == 1 outputs flatMap { case KeyValue(key, lines) => @@ -57,10 +56,9 @@ object Output } def bold(s: String) = if(ConsoleLogger.formatEnabled) BOLD + s + RESET else s - def lastLines(key: ScopedKey[_], structure: BuildStructure, streams: Streams): Seq[KeyValue[Seq[String]]] = + def lastLines(keys: Values[_], streams: Streams): Values[Seq[String]] = { - val aggregated = Aggregation.getTasks(key, structure, true) - val outputs = aggregated map { case KeyValue(key, value) => KeyValue(key, lastLines(key, streams)) } + val outputs = keys map { case KeyValue(key, value) => KeyValue(key, lastLines(key, streams)) } outputs.filterNot(_.value.isEmpty) } def lastLines(key: ScopedKey[_], mgr: Streams): Seq[String] = mgr.use(key) { s => IO.readLines(s.readText( Project.fillTaskAxis(key) )) } diff --git a/main/Project.scala b/main/Project.scala index b609e8430..f7ce6927d 100755 --- a/main/Project.scala +++ b/main/Project.scala @@ -85,7 +85,8 @@ final case class Extracted(structure: BuildStructure, session: SessionSettings, def runAggregated[T](key: TaskKey[T], state: State): State = { val rkey = resolve(key.scopedKey) - val tasks = Aggregation.getTasks(rkey, structure, true) + val keys = Resolve.aggregateDeps(rkey, ScopeMask(), structure.extra) + val tasks = Act.keyValues(structure)(keys) Aggregation.runTasks(state, structure, tasks, Aggregation.Dummies(KNil, HNil), show = false )(showKey) } private[this] def resolve[T](key: ScopedKey[T]): ScopedKey[T] = diff --git a/main/Resolve.scala b/main/Resolve.scala new file mode 100644 index 000000000..de1c0709c --- /dev/null +++ b/main/Resolve.scala @@ -0,0 +1,84 @@ +package sbt + + import java.net.URI + import Load.LoadedBuildUnit + +object Resolve +{ + def apply(index: BuildUtil[_], current: ScopeAxis[Reference], key: AttributeKey[_], mask: ScopeMask): Scope => Scope = + { + val rs = + resolveProject(current, mask) _ :: + resolveExtra(mask) _ :: + resolveTask(mask) _ :: + resolveConfig(index, key, mask) _ :: + Nil + scope => (scope /: rs) { (s, f) => f(s) } + } + def resolveTask(mask: ScopeMask)(scope: Scope): Scope = + if(mask.task) scope else scope.copy(task = Global) + + def resolveProject(current: ScopeAxis[Reference], mask: ScopeMask)(scope: Scope): Scope = + if(mask.project) scope else scope.copy(project = current) + + def resolveExtra(mask: ScopeMask)(scope: Scope): Scope = + if(mask.extra) scope else scope.copy(extra = Global) + + def resolveConfig[P](index: BuildUtil[P], key: AttributeKey[_], mask: ScopeMask)(scope: Scope): Scope = + if(mask.config) + scope + else + { + val (resolvedRef, proj) = scope.project match { + case Select(ref) => + val r = index resolveRef ref + (Some(r), index.projectFor(r)) + case Global | This => + (None, index.rootProject(index.root)) + } + val task = scope.task.toOption + val keyIndex = index.keyIndex + val definesKey = (c: ScopeAxis[ConfigKey]) => keyIndex.keys(resolvedRef, c.toOption.map(_.name), task) contains key.label + val projectConfigs = index.configurations(proj).map(ck => Select(ck)) + val config: ScopeAxis[ConfigKey] = (Global +: projectConfigs) find definesKey getOrElse Global + scope.copy(config = config) + } + + import Load.BuildStructure + import Project.ScopedKey + + def projectAggregate[Proj](proj: Option[Reference], extra: BuildUtil[Proj], reverse: Boolean): Seq[ProjectRef] = + { + val resRef = proj.map(p => extra.projectRefFor(extra.resolveRef(p))) + resRef.toList.flatMap(ref => + if(reverse) extra.aggregates.reverse(ref) else extra.aggregates.forward(ref) + ) + } + + def aggregateDeps[T, Proj](key: ScopedKey[T], rawMask: ScopeMask, extra: BuildUtil[Proj], reverse: Boolean = false): Seq[ScopedKey[T]] = + { + val mask = rawMask.copy(project = true) + Dag.topologicalSort(key) { k => + val kref = k.scope.project + for( ref <- projectAggregate(kref.toOption, extra, reverse)) yield + { + val toResolve = k.scope.copy(project = Select(ref)) + val resolved = apply(extra, Global, k.key, mask)(toResolve) + ScopedKey(resolved, k.key) + } + } + } + + def aggregates(units: Map[URI, LoadedBuildUnit]): Relation[ProjectRef, ProjectRef] = + { + val depPairs = + for { + (uri, unit) <- units.toIterable + project <- unit.defined.values + ref = ProjectRef(uri, project.id) + agg <- project.aggregate + } yield + (ref, agg) + Relation.empty ++ depPairs + } +} diff --git a/main/src/test/scala/ParseKey.scala b/main/src/test/scala/ParseKey.scala index 490a9759d..aa15b09fc 100644 --- a/main/src/test/scala/ParseKey.scala +++ b/main/src/test/scala/ParseKey.scala @@ -65,7 +65,7 @@ object ParseKey extends Properties("Key parser test") import skm.{structure, key} val mask = ScopeMask(config = false) val string = Project.displayMasked(key, mask) - val resolvedConfig = resolveConfig(structure, key.scope, key.key, mask).config + val resolvedConfig = Resolve.resolveConfig(structure.extra, key.key, mask)(key.scope).config ("Key: " + Project.displayFull(key)) |: ("Mask: " + mask) |: @@ -85,37 +85,7 @@ object ParseKey extends Properties("Key parser test") final class StructureKeyMask(val structure: Structure, val key: ScopedKey[_], val mask: ScopeMask) def resolve(structure: Structure, key: ScopedKey[_], mask: ScopeMask): ScopedKey[_] = - { - val scope = (key.scope /: List(resolveProject _, resolveExtra _, resolveTask _, resolveConfig _)) { (scope, f) => - f(structure, scope, key.key, mask) - } - ScopedKey(scope, key.key) - } - def resolveTask(structure: Structure, scope: Scope, key: AttributeKey[_], mask: ScopeMask): Scope = - if(mask.task) - scope - else - scope.copy(task = Global) - - def resolveProject(structure: Structure, scope: Scope, key: AttributeKey[_], mask: ScopeMask): Scope = - if(mask.project) scope else scope.copy(project = Select(structure.current)) - def resolveExtra(structure: Structure, scope: Scope, key: AttributeKey[_], mask: ScopeMask): Scope = - if(mask.extra) scope else scope.copy(extra = Global) - def resolveConfig(structure: Structure, scope: Scope, key: AttributeKey[_], mask: ScopeMask): Scope = - if(mask.config) - scope - else - { - val env = structure.env - val resolved = env.resolveAxis(scope.project) - val project = env.projectFor( resolved ) - val ref = scope.project.toOption.map(env.resolve) - val task = scope.task.toOption - val projectConfigs = project.configurations.map(c => Select(ConfigKey(c.name))) - val definesKey = (c: ScopeAxis[ConfigKey]) => structure.keyIndex.keys(ref, c.toOption.map(_.name), task) contains key.label - val config = (Global +: projectConfigs) find definesKey getOrElse Global - scope.copy(config = config) - } + ScopedKey(Resolve(structure.extra, Select(structure.current), key.key, mask)(key.scope), key.key) def parseExpected(structure: Structure, s: String, expected: ScopedKey[_], mask: ScopeMask): Prop = ("Expected: " + Project.displayFull(expected)) |: diff --git a/main/src/test/scala/TestBuild.scala b/main/src/test/scala/TestBuild.scala index 16d55125f..07dd0be7e 100644 --- a/main/src/test/scala/TestBuild.scala +++ b/main/src/test/scala/TestBuild.scala @@ -53,6 +53,12 @@ object TestBuild (Scope.display(scope, ""), showKeys(map)) scopeStrings.toSeq.sorted.map(t => t._1 + t._2).mkString("\n\t") } + val extra: BuildUtil[Proj] = + { + val getp = (build: URI, project: String) => env.buildMap(build).projectMap(project) + new BuildUtil(keyIndex, env.root.uri, env.rootProject, getp, _.configurations.map(c => ConfigKey(c.name)), const(Nil)) + } + lazy val allAttributeKeys: Set[AttributeKey[_]] = data.data.values.flatMap(_.keys).toSet lazy val (taskAxes, globalTaskAxis, onlyTaskAxis, multiTaskAxis) = { @@ -94,11 +100,6 @@ object TestBuild val taskMap = mapBy(tasks)(getKey) def project(ref: ProjectRef) = buildMap(ref.build).projectMap(ref.project) def projectFor(ref: ResolvedReference) = ref match { case pr: ProjectRef => project(pr); case BuildRef(uri) => buildMap(uri).root } - def resolveAxis(ref: ScopeAxis[Reference]): ResolvedReference = - { - val rootRef = BuildRef(root.uri) - ref.foldStrict(resolve, rootRef, rootRef) - } lazy val allProjects = builds.flatMap(_.allProjects) def rootProject(uri: URI): String = buildMap(uri).root.id diff --git a/util/collection/Relation.scala b/util/collection/Relation.scala index c5195ffb7..dce3d9048 100644 --- a/util/collection/Relation.scala +++ b/util/collection/Relation.scala @@ -17,7 +17,6 @@ object Relation make(forward, reverse) } - private[sbt] def remove[X,Y](map: M[X,Y], from: X, to: Y): M[X,Y] = map.get(from) match { case Some(tos) =>