moved task axis before the key

This commit is contained in:
Mark Harrah 2012-01-09 08:00:29 -05:00
parent 79bbe8f8a4
commit ec48779829
11 changed files with 389 additions and 80 deletions

View File

@ -17,22 +17,70 @@ 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[_]] =
def scopedKey(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String],
keyMap: Map[String, AttributeKey[_]], data: Settings[Scope]): Parser[ScopedKey[_]] =
{
implicit val show = Project.showRelativeKey(current, index.buildURIs.size > 1)
def taskKeyExtra(proj: Option[ResolvedReference], confAmb: ParsedAxis[String]): Seq[Parser[ScopedKey[_]]] =
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)
extra <- extraAxis(keyMap, IMap.empty)
} yield
makeScopedKey( proj, conf, task, extra, key )
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 })
}
} yield {
val (key, conf, task, extra) = keyConfTaskExtra
ScopedKey( Scope( toAxis(proj, Global), toAxis(conf map ConfigKey.apply, Global), task, extra), key )
}
result <- select(taskKeyExtra(proj, confAmb), data)
} yield
result
}
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[_]] =
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[ScopedKey[_]], default: Parser[ScopedKey[_]])(implicit show: Show[ScopedKey[_]]): Parser[ScopedKey[_]] =
selectByTask(selectByConfig(ss)) match
{
case Seq() => default
case Seq(single) => success(single)
case multi => failure("Ambiguous keys: " + showAmbiguous(multi))
}
def selectByConfig(ss: Seq[ScopedKey[_]]): Seq[ScopedKey[_]] =
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
{
case Seq() => x :: Nil
case xs => x +: xs
}
}
def selectByTask(ss: Seq[ScopedKey[_]]): Seq[ScopedKey[_]] =
{
val (selects, globals) = ss.partition(_.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])(key: ScopedKey[_]): Boolean =
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] =
@ -45,28 +93,29 @@ object Act
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[_]], data: Settings[Scope]):
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 +124,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))
@ -124,12 +171,12 @@ object Act
def projectRef(index: KeyIndex, currentBuild: URI): Parser[Option[ResolvedReference]] =
{
val global = token(GlobalString <~ '/') ^^^ None
val global = token(GlobalString ~ '/') ^^^ None
global | some(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,7 +185,7 @@ 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]] =
@ -156,18 +203,24 @@ object Act
}
def showParser = token( (ShowCommand ~ Space) ^^^ true) ?? false
def scopedKeyParser(state: State): Parser[ScopedKey[_]] = scopedKeyParser(Project extract state)
def scopedKeyParser(extracted: Extracted): Parser[ScopedKey[_]] =
def scopedKeyParser(extracted: Extracted): Parser[ScopedKey[_]] = scopedKeyParser(extracted.structure, extracted.currentRef)
def scopedKeyParser(structure: BuildStructure, currentRef: ProjectRef): Parser[ScopedKey[_]] =
{
import extracted._
def confs(uri: URI) = if(structure.units.contains(uri)) defaultConfigs(structure.data)(ProjectRef(uri, rootProject(uri))) else Nil
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, defaultConfs, structure.index.keyMap)
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]
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

@ -36,6 +36,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]

View File

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

@ -211,7 +211,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)

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,159 @@
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 = resolveConfig(structure, key.scope, key.key, mask).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[_] =
{
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)
}
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,40 @@ 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")
}
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 +94,13 @@ 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)
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
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 +117,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 +136,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 +154,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 +178,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 +198,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

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