Merge branch 'parsing_changes' into 0.12

This commit is contained in:
Mark Harrah 2012-01-15 13:09:41 -05:00
commit 6a7eb6c937
19 changed files with 622 additions and 173 deletions

View File

@ -7,32 +7,103 @@ 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 = "*"
// this does not take aggregation into account
def scopedKey(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], keyMap: Map[String, AttributeKey[_]]): Parser[ScopedKey[_]] =
{
for {
proj <- optProjectRef(index, current)
confAmb <- config( index configs proj )
keyConfs <- key(index, proj, configs(confAmb, defaultConfigs, proj), keyMap)
keyConfTaskExtra <- {
val andTaskExtra = (key: AttributeKey[_], conf: Option[String]) =>
taskExtrasParser(index.tasks(proj, conf, key.label), keyMap, IMap.empty) map { case (task, extra) => (key, conf, task, extra) }
oneOf(keyConfs map { _ flatMap andTaskExtra.tupled })
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
Aggregation.aggregate(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) )
}
} yield {
val (key, conf, task, extra) = keyConfTaskExtra
ScopedKey( Scope( toAxis(proj, Global), toAxis(conf map ConfigKey.apply, Global), task, extra), key )
}
def scopedKeyFull(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], keyMap: Map[String, AttributeKey[_]]): Parser[Seq[Parser[ParsedKey]]] =
{
def taskKeyExtra(proj: Option[ResolvedReference], confAmb: ParsedAxis[String], baseMask: ScopeMask): Seq[Parser[ParsedKey]] =
for {
conf <- configs(confAmb, defaultConfigs, proj, index)
} yield for {
taskAmb <- taskAxis(conf, index.tasks(proj, conf), keyMap)
task = resolveTask(taskAmb)
key <- key(index, proj, conf, task, keyMap)
extra <- extraAxis(keyMap, IMap.empty)
} yield {
val mask = baseMask.copy(task = taskAmb.isExplicit, extra = true)
new ParsedKey( makeScopedKey( proj, conf, task, extra, key ), mask)
}
for {
rawProject <- optProjectRef(index, current)
proj = resolveProject(rawProject, current)
confAmb <- config( index configs proj )
partialMask = ScopeMask(rawProject.isExplicit, confAmb.isExplicit, false, false)
} yield
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[ParsedKey]], data: Settings[Scope])(implicit show: Show[ScopedKey[_]]): Parser[ParsedKey] =
seq(allKeys) flatMap { ss =>
val default = ss.headOption match {
case None => noValidKeys
case Some(x) => success(x)
}
selectFromValid(ss filter isValid(data), default)
}
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(keys(multi)))
}
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(_.key.scope.config == x.key.scope.config) match
{
case Seq() => x :: Nil
case xs => x +: xs
}
}
def selectByTask(ss: Seq[ParsedKey]): Seq[ParsedKey] =
{
val (selects, globals) = ss.partition(_.key.scope.task.isSelect)
if(globals.nonEmpty) globals else selects
}
def noValidKeys = failure("No such key.")
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])(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
def examplesStrict(p: Parser[String], exs: Set[String], label: String): Parser[String] =
@ -42,31 +113,29 @@ 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[Option[String]] =
token( (examplesStrict(ID, confs, "configuration") | GlobalString) <~ ':' ).?
def config(confs: Set[String]): Parser[ParsedAxis[String]] =
token( (GlobalString ^^^ ParsedGlobal | value(examples(ID, confs, "configuration")) ) <~ ':' ) ?? Omitted
def configs(explicit: Option[String], defaultConfigs: Option[ResolvedReference] => Seq[String], proj: Option[ResolvedReference]): List[Option[String]] =
def configs(explicit: ParsedAxis[String], defaultConfigs: Option[ResolvedReference] => Seq[String], proj: Option[ResolvedReference], index: KeyIndex): Seq[Option[String]] =
explicit match
{
case None => None :: defaultConfigs(proj).map(c => Some(c)).toList
case Some(GlobalString) => None :: Nil
case Some(_) => explicit :: Nil
case Omitted => None +: defaultConfigs(proj).flatMap(nonEmptyConfig(index, proj))
case ParsedGlobal => None :: Nil
case pv: ParsedValue[String] => Some(pv.value) :: Nil
}
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], confs: Seq[Option[String]], keyMap: Map[String,AttributeKey[_]]): Parser[Seq[Parser[(AttributeKey[_], Option[String])]]] =
def key(index: KeyIndex, proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]], keyMap: Map[String,AttributeKey[_]]): Parser[AttributeKey[_]] =
{
val confMap = confs map { conf => (conf, index.keys(proj, conf)) } toMap;
val allKeys = (Set.empty[String] /: confMap.values)(_ ++ _)
token(ID !!! "Expected key" examples allKeys).map { keyString =>
val valid = confs.filter{ conf => confMap(conf) contains keyString }
def get(conf: Option[String]) = getKey(keyMap, keyString, k => (k, conf))
val parsers = valid map { conf => getKey(keyMap, keyString, k => (k, conf)) }
if(parsers.isEmpty) get(None) :: Nil else parsers
}
def keyParser(keys: Set[String]): Parser[AttributeKey[_]] =
token(ID !!! "Expected key" examples keys) flatMap { keyString=>
getKey(keyMap, keyString, idFun)
}
keyParser(index.keys(proj, conf, task))
}
def getKey[T](keyMap: Map[String,AttributeKey[_]], keyString: String, f: AttributeKey[_] => T): Parser[T] =
keyMap.get(keyString) match {
case Some(k) => success(f(k))
@ -75,30 +144,28 @@ object Act
val spacedComma = token(OptSpace ~ ',' ~ OptSpace)
def taskExtrasParser(tasks: Set[AttributeKey[_]], knownKeys: Map[String, AttributeKey[_]], knownValues: IMap[AttributeKey, Set]): Parser[(ScopeAxis[AttributeKey[_]], ScopeAxis[AttributeMap])] =
def extraAxis(knownKeys: Map[String, AttributeKey[_]], knownValues: IMap[AttributeKey, Set]): Parser[ScopeAxis[AttributeMap]] =
{
val extras = extrasParser(knownKeys, knownValues)
val taskParser = optionalAxis(taskAxisParser(tasks, knownKeys), Global)
val taskAndExtra =
taskParser flatMap { taskAxis =>
if(taskAxis.isSelect)
optionalAxis(spacedComma ~> extras, Global) map { x => (taskAxis, x) }
else
extras map { x => (taskAxis, Select(x)) }
}
val base = token('(', hide = _ == 1 && tasks.isEmpty) ~> taskAndExtra <~ token(')')
base ?? ( (Global, Global) )
val extrasP = extrasParser(knownKeys, knownValues)
val extras = token('(', hide = _ == 1 && knownValues.isEmpty) ~> extrasP <~ token(')')
optionalAxis(extras, Global)
}
def taskAxisParser(tasks: Set[AttributeKey[_]], allKnown: Map[String, AttributeKey[_]]): Parser[AttributeKey[_]] =
def taskAxis(d: Option[String], tasks: Set[AttributeKey[_]], allKnown: Map[String, AttributeKey[_]]): Parser[ParsedAxis[AttributeKey[_]]] =
{
val knownKeys: Map[String, AttributeKey[_]] = tasks.toSeq.map(key => (key.label, key)).toMap
val known = allKnown ++ knownKeys
val valid = known.keys.toSet
val suggested = knownKeys.keys.toSet
val keyP = filterStrings(examples(ID, suggested, "key"), valid, "key") map known
token("for" ~ Space) ~> token(keyP)
val valid = allKnown ++ knownKeys
val suggested = knownKeys.keySet
val keyP = filterStrings(examples(ID, suggested, "key"), valid.keySet, "key") map valid
(token(value(keyP) | GlobalString ^^^ ParsedGlobal ) <~ token("::".id) ) ?? Omitted
}
def resolveTask(task: ParsedAxis[AttributeKey[_]]): Option[AttributeKey[_]] =
task match
{
case ParsedGlobal | Omitted => None
case t: ParsedValue[AttributeKey[_]] => Some(t.value)
}
def filterStrings(base: Parser[String], valid: Set[String], label: String): Parser[String] =
base.filter(valid, Command.invalidValue(label, valid))
@ -122,14 +189,14 @@ 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] =
{
def projectID(uri: URI) = token( examplesStrict(ID, index projects uri, "project ID") <~ trailing )
def projectID(uri: URI) = token( examples(ID, index projects uri, "project ID") <~ trailing )
def projectRef(uri: URI) = projectID(uri) map { id => ProjectRef(uri, id) }
val uris = index.buildURIs
@ -138,36 +205,60 @@ object Act
buildRef flatMap {
case None => projectRef(currentBuild)
case Some(uri) => projectRef(uri) | token(trailing ~> success(BuildRef(uri)))
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[_]] =
{
import extracted._
def confs(uri: URI) = if(structure.units.contains(uri)) defaultConfigs(structure.data)(ProjectRef(uri, 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
def scopedKeyParser(extracted: Extracted): Parser[ScopedKey[_]] = scopedKeyParser(extracted.structure, extracted.currentRef)
def scopedKeyParser(structure: BuildStructure, currentRef: ProjectRef): Parser[ScopedKey[_]] =
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)
}
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] {
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]
def value[T](t: Parser[T]): Parser[ParsedAxis[T]] = t map { v => new ParsedValue(v) }
}

View File

@ -4,7 +4,7 @@
package sbt
import Project.ScopedKey
import Load.BuildStructure
import Load.{BuildStructure,LoadedBuildUnit}
import Keys.{aggregate, showSuccess, showTiming, timingFormat}
import sbt.complete.Parser
import java.net.URI
@ -14,50 +14,7 @@ package sbt
sealed trait Aggregation
final object Aggregation
{
def apply(dependencies: Seq[ProjectReference], transitive: Boolean = true): Aggregation = new Explicit(dependencies, transitive)
implicit def fromBoolean(b: Boolean): Aggregation = if(b) Enabled else Disabled
val Enabled = new Implicit(true)
val Disabled = new Implicit(false)
final case class Implicit(enabled: Boolean) extends 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 +23,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 +99,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)
@ -152,4 +111,56 @@ final object Aggregation
}
private[this] def maps[T, S](vs: Values[T])(f: T => S): Values[S] =
vs map { case KeyValue(k,v) => KeyValue(k, f(v)) }
def projectAggregates[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 aggregate[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 =>
if(reverse)
reverseAggregatedKeys(k, extra, mask)
else if(aggregationEnabled(key, extra.data))
aggregatedKeys(k, extra, mask)
else
Nil
}
}
def reverseAggregatedKeys[T](key: ScopedKey[T], extra: BuildUtil[_], mask: ScopeMask): Seq[ScopedKey[T]] =
projectAggregates(key.scope.project.toOption, extra, reverse = true) flatMap { ref =>
val toResolve = key.scope.copy(project = Select(ref))
val resolved = Resolve(extra, Global, key.key, mask)(toResolve)
val skey = ScopedKey(resolved, key.key)
if( aggregationEnabled(skey, extra.data) ) skey :: Nil else Nil
}
def aggregatedKeys[T](key: ScopedKey[T], extra: BuildUtil[_], mask: ScopeMask): Seq[ScopedKey[T]] =
projectAggregates(key.scope.project.toOption, extra, reverse = false) map { ref =>
val toResolve = key.scope.copy(project = Select(ref))
val resolved = Resolve(extra, Global, key.key, mask)(toResolve)
ScopedKey(resolved, key.key)
}
def aggregationEnabled(key: ScopedKey[_], data: Settings[Scope]): Boolean =
Keys.aggregate in Scope.fillTaskAxis(key.scope, key.key) get data getOrElse true
def relation(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
}
}

40
main/BuildUtil.scala Normal file
View File

@ -0,0 +1,40 @@
package sbt
import java.net.URI
final class BuildUtil[Proj](
val keyIndex: KeyIndex,
val data: Settings[Scope],
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)
}

View File

@ -98,7 +98,7 @@ object Defaults extends BuildCommon
sbtPlugin :== false,
crossPaths :== true,
classpathTypes :== Set("jar", "bundle"),
aggregate :== Aggregation.Enabled,
aggregate :== true,
maxErrors :== 100,
showTiming :== true,
timingFormat :== Aggregation.defaultFormat,

View File

@ -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))
@ -36,6 +40,10 @@ import KeyIndex._
trait KeyIndex
{
// TODO, optimize
def isEmpty(proj: Option[ResolvedReference], conf: Option[String]): Boolean = keys(proj, conf).isEmpty
def isEmpty(proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]]): Boolean = keys(proj, conf, task).isEmpty
def buildURIs: Set[URI]
def projects(uri: URI): Set[String]
def configs(proj: Option[ResolvedReference]): Set[String]
@ -48,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])
@ -106,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 = Aggregation.aggregate(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) )
}

View File

@ -149,7 +149,7 @@ object Keys
val definesClass = TaskKey[DefinesClass]("defines-class", "Internal use: provides a function that determines whether the provided file contains a given class.")
val doc = TaskKey[File]("doc", "Generates API documentation.")
val copyResources = TaskKey[Seq[(File,File)]]("copy-resources", "Copies resources to the output directory.")
val aggregate = SettingKey[Aggregation]("aggregate", "Configures task aggregation.")
val aggregate = SettingKey[Boolean]("aggregate", "Configures task aggregation.")
// package keys
val packageBin = TaskKey[File]("package-bin", "Produces a main artifact, such as a binary jar.")
@ -298,7 +298,7 @@ object Keys
val sbtDependency = SettingKey[ModuleID]("sbt-dependency", "Provides a definition for declaring the current version of sbt.")
val sbtVersion = SettingKey[String]("sbt-version", "Provides the version of sbt. This setting should be not be modified.")
val sbtBinaryVersion = SettingKey[String]("sbt-binary-version", "Defines the binary compatibility version substring.")
val skip = TaskKey[Boolean]("skip", "For tasks that support it (currently only 'compile'), setting skip to true will force the task to not to do its work. This exact semantics may vary by task.")
val skip = TaskKey[Boolean]("skip", "For tasks that support it (currently only 'compile' and 'update'), setting skip to true will force the task to not to do its work. This exact semantics may vary by task.")
// special
val sessionVars = AttributeKey[SessionVar.Map]("session-vars", "Bindings that exist for the duration of the session.")

View File

@ -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(data))
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, newData))
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)
}
@ -556,6 +558,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(data: Settings[Scope])(keyIndex: KeyIndex): BuildUtil[ResolvedProject] = buildUtil(root, units, keyIndex, data)
}
def checkCycles(units: Map[URI, LoadedBuildUnit])
{
@ -578,6 +581,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
@ -595,8 +599,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, data)
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, data: Settings[Scope]): BuildUtil[ResolvedProject] =
{
val getp = (build: URI, project: String) => Load.getProject(units, build, project)
val configs = (_: ResolvedProject).configurations.map(c => ConfigKey(c.name))
val aggregates = Aggregation.relation(units)
new BuildUtil(keyIndex, data, 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))
@ -605,5 +617,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
)
}

View File

@ -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
@ -458,15 +459,19 @@ object BuiltinCommands
def allKeyParser(s: State): Parser[AttributeKey[_]] =
{
val keyMap = Project.structure(s).index.keyMap
token((Space ~> ID) !!! "Expected key" examples keyMap.keySet) flatMap { key => Act.getKey(keyMap, key, idFun) }
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, "<pattern>")) ~ 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, "<pattern>")) ~ 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

View File

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

View File

@ -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 = Aggregation.aggregate(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] =
@ -211,7 +212,11 @@ object Project extends Init[Scope] with ProjectExtra
def makeSettings(settings: Seq[Setting[_]], delegates: Scope => Seq[Scope], scopeLocal: ScopedKey[_] => Seq[Setting[_]])(implicit display: Show[ScopedKey[_]]) =
make(settings)(delegates, scopeLocal, display)
def equal(a: ScopedKey[_], b: ScopedKey[_], mask: ScopeMask): Boolean =
a.key == b.key && Scope.equal(a.scope, b.scope, mask)
def displayFull(scoped: ScopedKey[_]): String = Scope.display(scoped.scope, scoped.key.label)
def displayMasked(scoped: ScopedKey[_], mask: ScopeMask): String = Scope.displayMasked(scoped.scope, scoped.key.label, mask)
def display(ref: Reference): String =
ref match
{

View File

@ -45,6 +45,10 @@ object RootProject
}
object Reference
{
def buildURI(ref: ResolvedReference): URI = ref match {
case BuildRef(b) => b
case ProjectRef(b, _) => b
}
/** Extracts the build URI from a Reference if one has been explicitly defined.*/
def uri(ref: Reference): Option[URI] = ref match {
case RootProject(b) => Some(b)

45
main/Resolve.scala Normal file
View File

@ -0,0 +1,45 @@
package sbt
import java.net.URI
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)
}
}

View File

@ -72,8 +72,11 @@ object Scope
case RootProject(uri) => RootProject(resolveBuild(current, uri))
case ProjectRef(uri, id) => ProjectRef(resolveBuild(current, uri), id)
}
def resolveBuild(current: URI, uri: URI): URI =
IO.directoryURI(current resolve uri)
def resolveBuild(current: URI, uri: URI): URI =
if(!uri.isAbsolute && current.isOpaque && uri.getSchemeSpecificPart == ".")
current // this handles the shortcut of referring to the current build using "."
else
IO.directoryURI(current resolve uri)
def resolveReference(current: URI, rootProject: URI => String, ref: Reference): ResolvedReference =
ref match
@ -98,19 +101,28 @@ object Scope
}
def display(config: ConfigKey): String = config.name + ":"
def display(scope: Scope, sep: String): String = display(scope, sep, ref => Project.display(ref) + "/")
def display(scope: Scope, sep: String, showProject: Reference => String): String =
def display(scope: Scope, sep: String): String = displayMasked(scope, sep, showProject, ScopeMask())
def displayMasked(scope: Scope, sep: String, mask: ScopeMask): String = displayMasked(scope, sep, showProject, mask)
def display(scope: Scope, sep: String, showProject: Reference => String): String = displayMasked(scope, sep, showProject, ScopeMask())
def displayMasked(scope: Scope, sep: String, showProject: Reference => String, mask: ScopeMask): String =
{
import scope.{project, config, task, extra}
val projectPrefix = project.foldStrict(showProject, "*/", "./")
val configPrefix = config.foldStrict(display, "*:", ".:")
val taskPostfix = task.foldStrict(x => ("for " + x.label) :: Nil, Nil, Nil)
val extraPostfix = extra.foldStrict(_.entries.map( _.toString ).toList, Nil, Nil)
val extras = taskPostfix ::: extraPostfix
val taskPrefix = task.foldStrict(_.label + "::", "", ".::")
val extras = extra.foldStrict(_.entries.map( _.toString ).toList, Nil, Nil)
val postfix = if(extras.isEmpty) "" else extras.mkString("(", ", ", ")")
projectPrefix + configPrefix + sep + postfix
mask.concatShow(projectPrefix(project, showProject), configPrefix, taskPrefix, sep, postfix)
}
def equal(a: Scope, b: Scope, mask: ScopeMask): Boolean =
(!mask.project || a.project == b.project) &&
(!mask.config || a.config == b.config) &&
(!mask.task || a.task == b.task) &&
(!mask.extra || a.extra == b.extra)
def projectPrefix(project: ScopeAxis[Reference], show: Reference => String = showProject): String = project.foldStrict(show, "*/", "./")
def showProject = (ref: Reference) => Project.display(ref) + "/"
def parseScopedKey(command: String): (Scope, String) =
{
val ScopedKeyRegex(_, projectID, _, config, key) = command
@ -241,6 +253,24 @@ object ScopeAxis
{
implicit def scopeAxisToScope(axis: ScopeAxis[Nothing]): Scope =
Scope(axis, axis, axis, axis)
def fromOption[T](o: Option[T]): ScopeAxis[T] = o match {
case Some(v) => Select(v)
case None => Global
}
}
/** Specifies the Scope axes that should be used for an operation. `true` indicates an axis should be used. */
final case class ScopeMask(project: Boolean = true, config: Boolean = true, task: Boolean = true, extra: Boolean = true)
{
def concatShow(p: String, c: String, t: String, sep: String, x: String): String =
{
val sb = new StringBuilder
if(project) sb.append(p)
if(config) sb.append(c)
if(task) sb.append(t)
sb.append(sep)
if(extra) sb.append(x)
sb.toString
}
}
final case class ConfigKey(name: String)

View File

@ -0,0 +1,129 @@
package sbt
import Project._
import java.net.URI
import TestBuild._
import complete._
import org.scalacheck._
import Gen._
import Prop._
import Arbitrary.arbBool
/** Tests that the scoped key parser in Act can correctly parse a ScopedKey converted by Project.show*Key.
* This includes properly resolving omitted components.*/
object ParseKey extends Properties("Key parser test")
{
final val MaxKeys = 5
final val MaxScopedKeys = 100
implicit val gstructure = genStructure
property("An explicitly specified axis is always parsed to that explicit value") =
forAllNoShrink(structureDefinedKey) { (skm: StructureKeyMask) =>
import skm.{structure, key, mask}
val expected = resolve(structure, key, mask)
val string = Project.displayMasked(key, mask)
("Key: " + Project.displayFull(key)) |:
parseExpected(structure, string, expected, mask)
}
property("An unspecified project axis resolves to the current project") =
forAllNoShrink(structureDefinedKey) { (skm: StructureKeyMask) =>
import skm.{structure, key}
val mask = skm.mask.copy(project = false)
val string = Project.displayMasked(key, mask)
("Key: " + Project.displayFull(key)) |:
("Mask: " + mask) |:
("Current: " + structure.current) |:
parse(structure, string) {
case Left(err) => false
case Right(sk) => sk.scope.project == Select(structure.current)
}
}
property("An unspecified task axis resolves to Global") =
forAllNoShrink(structureDefinedKey) { (skm: StructureKeyMask) =>
import skm.{structure, key}
val mask = skm.mask.copy(task = false)
val string = Project.displayMasked(key, mask)
("Key: " + Project.displayFull(key)) |:
("Mask: " + mask) |:
parse(structure, string) {
case Left(err) => false
case Right(sk) => sk.scope.task == Global
}
}
property("An unspecified configuration axis resolves to the first configuration directly defining the key or else Global") =
forAllNoShrink(structureDefinedKey) { (skm: StructureKeyMask) =>
import skm.{structure, key}
val mask = ScopeMask(config = false)
val string = Project.displayMasked(key, mask)
val resolvedConfig = Resolve.resolveConfig(structure.extra, key.key, mask)(key.scope).config
("Key: " + Project.displayFull(key)) |:
("Mask: " + mask) |:
("Expected configuration: " + resolvedConfig.map(_.name)) |:
parse(structure, string) {
case Right(sk) => sk.scope.config == resolvedConfig
case Left(err) => false
}
}
lazy val structureDefinedKey: Gen[StructureKeyMask] = structureKeyMask { s =>
for( scope <- TestBuild.scope(s.env); key <- oneOf(s.allAttributeKeys.toSeq)) yield ScopedKey(scope, key)
}
def structureKeyMask(genKey: Structure => Gen[ScopedKey[_]])(implicit maskGen: Gen[ScopeMask], structureGen: Gen[Structure]): Gen[StructureKeyMask] =
for(mask <- maskGen; structure <- structureGen; key <- genKey(structure)) yield
new StructureKeyMask(structure, key, mask)
final class StructureKeyMask(val structure: Structure, val key: ScopedKey[_], val mask: ScopeMask)
def resolve(structure: Structure, key: ScopedKey[_], mask: ScopeMask): ScopedKey[_] =
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)) |:
("Mask: " + mask) |:
parse(structure, s) {
case Left(err) => false
case Right(sk) => Project.equal(sk, expected, mask)
}
def parse(structure: Structure, s: String)(f: Either[String,ScopedKey[_]] => Prop): Prop =
{
val parser = makeParser(structure)
val parsed = DefaultParsers.result(parser, s).left.map(_().toString)
val showParsed = parsed.right.map(Project.displayFull)
("Key string: '" + s + "'") |:
("Parsed: " + showParsed) |:
("Structure: " + structure) |:
f(parsed)
}
def genStructure(implicit genEnv: Gen[Env]): Gen[Structure] =
structureGenF { (scopes: Seq[Scope], env: Env, current: ProjectRef) =>
val settings = for(scope <- scopes; t <- env.tasks) yield Project.setting(ScopedKey(scope, t.key), Project.value(""))
TestBuild.structure(env, settings, current)
}
def structureGenF(f: (Seq[Scope], Env, ProjectRef) => Structure)(implicit genEnv: Gen[Env]): Gen[Structure] =
structureGen( (s,e,p) => Gen.value(f(s,e,p)))
def structureGen(f: (Seq[Scope], Env, ProjectRef) => Gen[Structure])(implicit genEnv: Gen[Env]): Gen[Structure] =
for {
env <- genEnv
loadFactor <- choose(0.0, 1.0)
scopes <- pickN(loadFactor, env.allFullScopes)
current <- oneOf(env.allProjects.unzip._1)
structure <- f(scopes, env, current)
} yield
structure
def pickN[T](load: Double, from: Seq[T]): Gen[Seq[T]] =
pick( (load*from.size).toInt, from )
}

View File

@ -9,18 +9,19 @@ package sbt
import org.scalacheck._
import Prop._
import Gen._
import Arbitrary.arbBool
// Notes:
// Generator doesn't produce cross-build project dependencies or do anything with the 'extra' axis
object TestBuild
{
val MaxTasks = 10
val MaxProjects = 10
val MaxConfigs = 10
val MaxBuilds = 10
val MaxIDSize = 10
val MaxDeps = 10
val KeysPerEnv = 25
val MaxTasks = 6
val MaxProjects = 7
val MaxConfigs = 5
val MaxBuilds = 4
val MaxIDSize = 8
val MaxDeps = 8
val KeysPerEnv = 10
val MaxTasksGen = chooseShrinkable(1, MaxTasks)
val MaxProjectsGen = chooseShrinkable(1, MaxProjects)
@ -38,7 +39,7 @@ object TestBuild
final class Keys(val env: Env, val scopes: Seq[Scope])
{
override def toString = env + "\n" + scopes.mkString("Scopes:\n\t", "\n\t", "")
val delegated = scopes map env.delegates
lazy val delegated = scopes map env.delegates
}
final case class Structure(env: Env, current: ProjectRef, data: Settings[Scope], keyIndex: KeyIndex, keyMap: Map[String, AttributeKey[_]])
@ -49,8 +50,46 @@ object TestBuild
{
val scopeStrings =
for( (scope, map) <- data.data ) yield
Scope.display(scope, "<key>") + showKeys(map)
scopeStrings.mkString("\n\t")
(Scope.display(scope, "<key>"), 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, data, env.root.uri, env.rootProject, getp, _.configurations.map(c => ConfigKey(c.name)), Relation.empty)
}
lazy val allAttributeKeys: Set[AttributeKey[_]] = data.data.values.flatMap(_.keys).toSet
lazy val (taskAxes, globalTaskAxis, onlyTaskAxis, multiTaskAxis) =
{
import collection.{breakOut, mutable}
import mutable.HashSet
// task axis of Scope is set to Global and the value of the second map is the original task axis
val taskAxesMappings =
for( (scope, keys) <- data.data.toIterable; key <- keys.keys ) yield
(ScopedKey(scope.copy(task = Global), key), scope.task) : (ScopedKey[_], ScopeAxis[AttributeKey[_]])
val taskAxes = Relation.empty ++ taskAxesMappings
val global = new HashSet[ScopedKey[_]]
val single = new HashSet[ScopedKey[_]]
val multi = new HashSet[ScopedKey[_]]
for( (skey, tasks) <- taskAxes.forwardMap)
{
def makeKey(task: ScopeAxis[AttributeKey[_]]) = ScopedKey(skey.scope.copy(task = task), skey.key)
val hasGlobal = tasks(Global)
if(hasGlobal)
global += skey
else
{
val keys = tasks map makeKey
if( keys.size == 1)
single ++= keys
else if(keys.size > 1)
multi ++= keys
}
}
(taskAxes, global.toSet, single.toSet, multi.toSet)
}
}
final class Env(val builds: Seq[Build], val tasks: Seq[Taskk])
@ -61,7 +100,8 @@ 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 allProjects = builds.flatMap(_.allProjects)
lazy val allProjects = builds.flatMap(_.allProjects)
def rootProject(uri: URI): String = buildMap(uri).root.id
def inheritConfig(ref: ResolvedReference, config: ConfigKey) = projectFor(ref).confMap(config.name).extended map toConfigKey
def inheritTask(task: AttributeKey[_]) = taskMap.get(task) match { case None => Nil; case Some(t) => t.delegates map getKey }
@ -78,9 +118,13 @@ object TestBuild
inheritTask,
(ref, mp) => Nil
)
def allFullScopes: Seq[Scope] =
for((ref, p) <- allProjects; t <- tasks; c <- p.configurations) yield
Scope(project = Select(ref), config = Select(ConfigKey(c.name)), task = Select(t.key), extra = Global)
lazy val allFullScopes: Seq[Scope] =
for {
(ref, p) <- (Global, root.root) +: allProjects.map { case (ref, p) => (Select(ref), p) }
t <- Global +: tasks.map(t => Select(t.key))
c <- Global +: p.configurations.map(c => Select(ConfigKey(c.name)))
} yield
Scope(project = ref, config = c, task = t, extra = Global)
}
def getKey: Taskk => AttributeKey[_] = _.key
def toConfigKey: Config => ConfigKey = c => ConfigKey(c.name)
@ -93,7 +137,7 @@ object TestBuild
}
final class Proj(val id: String, val delegates: Seq[ProjectRef], val configurations: Seq[Config])
{
override def toString = "Project " + id + "\n Delegates:\n " + delegates.mkString("\n ") +
override def toString = "Project " + id + "\n Delegates:\n " + delegates.mkString("\n ") +
"\n Configurations:\n " + configurations.mkString("\n ")
val confMap = mapBy(configurations)(_.name)
}
@ -111,7 +155,7 @@ object TestBuild
implicit lazy val arbKeys: Arbitrary[Keys] = Arbitrary(keysGen)
lazy val keysGen: Gen[Keys] = for(env <- mkEnv; keyCount <- chooseShrinkable(1, KeysPerEnv); keys <- listOfN(keyCount, scope(env)) ) yield new Keys(env, keys)
def scope(env: Env): Gen[Scope] =
for {
build <- oneOf(env.builds)
@ -135,7 +179,7 @@ object TestBuild
case Some(BuildRef(uri)) => confs(uri)
case Some(ref: ProjectRef) => env.project(ref).configurations.map(_.name)
}
Act.scopedKey(keyIndex, current, defaultConfs, keyMap)//, data)
Act.scopedKey(keyIndex, current, defaultConfs, keyMap, data)
}
def structure(env: Env, settings: Seq[Setting[_]], current: ProjectRef): Structure =
@ -155,6 +199,13 @@ object TestBuild
envGen(buildGen(uriGen, pGen), tGen)
}
implicit def maskGen(implicit arbBoolean: Arbitrary[Boolean]): Gen[ScopeMask] =
{
val b = arbBoolean.arbitrary
for(p <- b; c <- b; t <- b; x <- b) yield
ScopeMask(project = p, config = c, task = t, extra = x)
}
implicit lazy val idGen: Gen[String] = for(size <- chooseShrinkable(1, MaxIDSize); cs <- listOfN(size, alphaChar)) yield cs.mkString
implicit lazy val optIDGen: Gen[Option[String]] = frequency( (1, idGen map some.fn), (1, None) )
implicit lazy val uriGen: Gen[URI] = for(sch <- idGen; ssp <- idGen; frag <- optIDGen) yield new URI(sch, ssp, frag.orNull)

View File

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

View File

@ -2,4 +2,8 @@ package sbt
trait Show[T] {
def apply(t: T): String
}
object Show
{
def apply[T](f: T => String): Show[T] = new Show[T] { def apply(t: T): String = f(t) }
}

View File

@ -431,7 +431,8 @@ private final class ParserSeq[T](a: Seq[Parser[T]], errors: => Seq[String]) exte
{
val res = a.map(_.resultEmpty)
val (failures, values) = separate(res)(_.toEither)
if(failures.isEmpty) Value(values) else mkFailures(failures.flatMap(_()) ++ errors)
// if(failures.isEmpty) Value(values) else mkFailures(failures.flatMap(_()) ++ errors)
if(values.nonEmpty) Value(values) else mkFailures(failures.flatMap(_()) ++ errors)
}
def result = {
val success = a.flatMap(_.result)

View File

@ -613,7 +613,7 @@ object IO
*/
def directoryURI(uri: URI): URI =
{
assertAbsolute(uri)
if(!uri.isAbsolute) return uri;//assertAbsolute(uri)
val str = uri.toASCIIString
val dirStr = if(str.endsWith("/") || uri.getScheme != "file") str else str + "/"
(new URI(dirStr)).normalize