From ec48779829fe38842e8eda330358e18c2cfa259d Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Mon, 9 Jan 2012 08:00:29 -0500 Subject: [PATCH] moved task axis before the key --- main/Act.scala | 155 ++++++++++++++++++--------- main/KeyIndex.scala | 4 + main/Keys.scala | 2 +- main/Project.scala | 4 + main/Reference.scala | 4 + main/Scope.scala | 48 +++++++-- main/src/test/scala/ParseKey.scala | 159 ++++++++++++++++++++++++++++ main/src/test/scala/TestBuild.scala | 84 ++++++++++++--- util/collection/Show.scala | 4 + util/complete/Parser.scala | 3 +- util/io/IO.scala | 2 +- 11 files changed, 389 insertions(+), 80 deletions(-) create mode 100644 main/src/test/scala/ParseKey.scala diff --git a/main/Act.scala b/main/Act.scala index 5de1c5b2c..a65a03c26 100644 --- a/main/Act.scala +++ b/main/Act.scala @@ -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) } } \ No newline at end of file diff --git a/main/KeyIndex.scala b/main/KeyIndex.scala index a4a5794ee..9a58c4809 100644 --- a/main/KeyIndex.scala +++ b/main/KeyIndex.scala @@ -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] diff --git a/main/Keys.scala b/main/Keys.scala index e551e1dbb..d4bc31146 100644 --- a/main/Keys.scala +++ b/main/Keys.scala @@ -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.") diff --git a/main/Project.scala b/main/Project.scala index 68746e6b9..b609e8430 100755 --- a/main/Project.scala +++ b/main/Project.scala @@ -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 { diff --git a/main/Reference.scala b/main/Reference.scala index 8704e6cf9..814a04ce7 100644 --- a/main/Reference.scala +++ b/main/Reference.scala @@ -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) diff --git a/main/Scope.scala b/main/Scope.scala index eb5173d14..8baaf759c 100644 --- a/main/Scope.scala +++ b/main/Scope.scala @@ -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) diff --git a/main/src/test/scala/ParseKey.scala b/main/src/test/scala/ParseKey.scala new file mode 100644 index 000000000..490a9759d --- /dev/null +++ b/main/src/test/scala/ParseKey.scala @@ -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 ) +} diff --git a/main/src/test/scala/TestBuild.scala b/main/src/test/scala/TestBuild.scala index 5f6934276..16d55125f 100644 --- a/main/src/test/scala/TestBuild.scala +++ b/main/src/test/scala/TestBuild.scala @@ -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, "") + showKeys(map) - scopeStrings.mkString("\n\t") + (Scope.display(scope, ""), 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) diff --git a/util/collection/Show.scala b/util/collection/Show.scala index b19a6ca2d..fe4e85950 100644 --- a/util/collection/Show.scala +++ b/util/collection/Show.scala @@ -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) } } \ No newline at end of file diff --git a/util/complete/Parser.scala b/util/complete/Parser.scala index 5c7bcddc3..a23ae3f48 100644 --- a/util/complete/Parser.scala +++ b/util/complete/Parser.scala @@ -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) diff --git a/util/io/IO.scala b/util/io/IO.scala index 3aef64075..deccf50bd 100644 --- a/util/io/IO.scala +++ b/util/io/IO.scala @@ -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