mirror of https://github.com/sbt/sbt.git
Unified slash syntax
Fixes sbt/sbt#1812 This adds unified slash syntax for both sbt shell and the build.sbt DSL. Instead of the current `<project-id>/config:intask::key`, this adds `<project-id>/<config-ident>/intask/key` where <config-ident> is the Scala identifier notation for the configurations like `Compile` and `Test`. This also adds a series of implicits called `SlashSyntax` that adds `/` operators to project refererences, configuration, and keys such that the same syntax works in build.sbt. These examples work for both from the shell and in build.sbt. Global / cancelable ThisBuild / scalaVersion Test / test root / Compile / compile / scalacOptions ProjectRef(uri("file:/xxx/helloworld/"),"root")/Compile/scalacOptions Zero / Zero / name The inspect command now outputs something that can be copy-pasted: > inspect compile [info] Task: sbt.inc.Analysis [info] Description: [info] Compiles sources. [info] Provided by: [info] ProjectRef(uri("file:/xxx/helloworld/"),"root")/Compile/compile [info] Defined at: [info] (sbt.Defaults) Defaults.scala:326 [info] Dependencies: [info] Compile/manipulateBytecode [info] Compile/incCompileSetup [info] Reverse dependencies: [info] Compile/printWarnings [info] Compile/products [info] Compile/discoveredSbtPlugins [info] Compile/discoveredMainClasses [info] Delegates: [info] Compile/compile [info] compile [info] ThisBuild/Compile/compile [info] ThisBuild/compile [info] Zero/Compile/compile [info] Global/compile [info] Related: [info] Test/compile
This commit is contained in:
parent
67d1da48f1
commit
33a01f3ceb
|
|
@ -45,7 +45,8 @@ def commonSettings: Seq[Setting[_]] =
|
|||
resolvers += Resolver.sonatypeRepo("snapshots"),
|
||||
resolvers += "bintray-sbt-maven-releases" at "https://dl.bintray.com/sbt/maven-releases/",
|
||||
concurrentRestrictions in Global += Util.testExclusiveRestriction,
|
||||
testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"),
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"),
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "2"),
|
||||
javacOptions in compile ++= Seq("-target", "6", "-source", "6", "-Xlint", "-Xlint:-serial"),
|
||||
crossScalaVersions := Seq(baseScalaVersion),
|
||||
bintrayPackage := (bintrayPackage in ThisBuild).value,
|
||||
|
|
@ -374,6 +375,11 @@ lazy val mainProj = (project in file("main"))
|
|||
mimaBinaryIssueFilters ++= Vector(
|
||||
// Changed the signature of NetworkChannel ctor. internal.
|
||||
exclude[DirectMissingMethodProblem]("sbt.internal.server.NetworkChannel.*"),
|
||||
// ctor for ConfigIndex. internal.
|
||||
exclude[DirectMissingMethodProblem]("sbt.internal.ConfigIndex.*"),
|
||||
// New and changed methods on KeyIndex. internal.
|
||||
exclude[ReversedMissingMethodProblem]("sbt.internal.KeyIndex.*"),
|
||||
exclude[DirectMissingMethodProblem]("sbt.internal.KeyIndex.*"),
|
||||
)
|
||||
)
|
||||
.configure(
|
||||
|
|
|
|||
|
|
@ -92,6 +92,11 @@ object AttributeKey {
|
|||
rank0: Int
|
||||
)(implicit mf: Manifest[T], ojw: OptJsonWriter[T]): AttributeKey[T] =
|
||||
new SharedAttributeKey[T] {
|
||||
require(name.headOption match {
|
||||
case Some(c) => c.isLower
|
||||
case None => false
|
||||
}, s"A named attribute key must start with a lowercase letter: $name")
|
||||
|
||||
def manifest = mf
|
||||
val label = Util.hyphenToCamel(name)
|
||||
def description = description0
|
||||
|
|
|
|||
|
|
@ -80,7 +80,12 @@ object SettingsTest extends Properties("settings") {
|
|||
private def mkAttrKeys[T](nr: Int)(implicit mf: Manifest[T]): Gen[List[AttributeKey[T]]] = {
|
||||
import Gen._
|
||||
val nonEmptyAlphaStr =
|
||||
nonEmptyListOf(alphaChar).map(_.mkString).suchThat(_.forall(_.isLetter))
|
||||
nonEmptyListOf(alphaChar)
|
||||
.map({ xs: List[Char] =>
|
||||
val s = xs.mkString
|
||||
s.take(1).toLowerCase + s.drop(1)
|
||||
})
|
||||
.suchThat(_.forall(_.isLetter))
|
||||
|
||||
(for {
|
||||
list <- Gen.listOfN(nr, nonEmptyAlphaStr) suchThat (l => l.size == l.distinct.size)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ trait Parsers {
|
|||
/** Parses a single letter, according to Char.isLetter, into a Char. */
|
||||
lazy val Letter = charClass(_.isLetter, "letter")
|
||||
|
||||
/** Parses a single letter, according to Char.isUpper, into a Char. */
|
||||
lazy val Upper = charClass(_.isUpper, "upper")
|
||||
|
||||
/** Parses a single letter, according to Char.isLower, into a Char. */
|
||||
lazy val Lower = charClass(_.isLower, "lower")
|
||||
|
||||
/** Parses the first Char in an sbt identifier, which must be a [[Letter]].*/
|
||||
def IDStart = Letter
|
||||
|
||||
|
|
@ -67,6 +73,9 @@ trait Parsers {
|
|||
/** Parses a non-symbolic Scala-like identifier. The identifier must start with [[IDStart]] and contain zero or more [[ScalaIDChar]]s after that.*/
|
||||
lazy val ScalaID = identifier(IDStart, ScalaIDChar)
|
||||
|
||||
/** Parses a non-symbolic Scala-like identifier. The identifier must start with [[Upper]] and contain zero or more [[ScalaIDChar]]s after that.*/
|
||||
lazy val CapitalizedID = identifier(Upper, ScalaIDChar)
|
||||
|
||||
/** Parses a String that starts with `start` and is followed by zero or more characters parsed by `rep`.*/
|
||||
def identifier(start: Parser[Char], rep: Parser[Char]): Parser[String] =
|
||||
start ~ rep.* map { case x ~ xs => (x +: xs).mkString }
|
||||
|
|
|
|||
|
|
@ -57,17 +57,36 @@ object Def extends Init[Scope] with TaskMacroExtra {
|
|||
ref => displayBuildRelative(currentBuild, multi, ref)
|
||||
))
|
||||
|
||||
/**
|
||||
* Returns a String expression for the given [[Reference]] (BuildRef, [[ProjectRef]], etc)
|
||||
* relative to the current project.
|
||||
*/
|
||||
def displayRelativeReference(current: ProjectRef, project: Reference): String =
|
||||
displayRelative(current, project, false)
|
||||
|
||||
@deprecated("Use displayRelativeReference", "1.1.0")
|
||||
def displayRelative(current: ProjectRef, multi: Boolean, project: Reference): String =
|
||||
displayRelative(current, project, true)
|
||||
|
||||
/**
|
||||
* Constructs the String of a given [[Reference]] relative to current.
|
||||
* Note that this no longer takes "multi" parameter, and omits the subproject id at all times.
|
||||
*/
|
||||
private[sbt] def displayRelative(current: ProjectRef,
|
||||
project: Reference,
|
||||
trailingSlash: Boolean): String = {
|
||||
val trailing = if (trailingSlash) "/" else ""
|
||||
project match {
|
||||
case BuildRef(current.build) => "{.}/"
|
||||
case `current` => if (multi) current.project + "/" else ""
|
||||
case ProjectRef(current.build, x) => x + "/"
|
||||
case _ => Reference.display(project) + "/"
|
||||
case BuildRef(current.build) => "ThisBuild" + trailing
|
||||
case `current` => ""
|
||||
case ProjectRef(current.build, x) => x + trailing
|
||||
case _ => Reference.display(project) + trailing
|
||||
}
|
||||
}
|
||||
|
||||
def displayBuildRelative(currentBuild: URI, multi: Boolean, project: Reference): String =
|
||||
project match {
|
||||
case BuildRef(`currentBuild`) => "{.}/"
|
||||
case BuildRef(`currentBuild`) => "ThisBuild/"
|
||||
case ProjectRef(`currentBuild`, x) => x + "/"
|
||||
case _ => Reference.display(project) + "/"
|
||||
}
|
||||
|
|
@ -80,6 +99,9 @@ object Def extends Init[Scope] with TaskMacroExtra {
|
|||
def displayMasked(scoped: ScopedKey[_], mask: ScopeMask): String =
|
||||
Scope.displayMasked(scoped.scope, scoped.key.label, mask)
|
||||
|
||||
def displayMasked(scoped: ScopedKey[_], mask: ScopeMask, showZeroConfig: Boolean): String =
|
||||
Scope.displayMasked(scoped.scope, scoped.key.label, mask, showZeroConfig)
|
||||
|
||||
def withColor(s: String, color: Option[String]): String = {
|
||||
val useColor = ConsoleAppender.formatEnabledInEnv
|
||||
color match {
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ object Reference {
|
|||
case LocalRootProject => "{<this>}<root>"
|
||||
case LocalProject(id) => "{<this>}" + id
|
||||
case RootProject(uri) => "{" + uri + " }<root>"
|
||||
case ProjectRef(uri, id) => "{" + uri + "}" + id
|
||||
case ProjectRef(uri, id) => s"""ProjectRef(uri("$uri"),"$id")"""
|
||||
}
|
||||
|
||||
def buildURI(ref: ResolvedReference): URI = ref match {
|
||||
|
|
|
|||
|
|
@ -123,29 +123,84 @@ object Scope {
|
|||
case BuildRef(uri) => BuildRef(resolveBuild(current, uri))
|
||||
}
|
||||
|
||||
def display(config: ConfigKey): String = config.name + ":"
|
||||
def display(config: ConfigKey): String = guessConfigIdent(config.name) + "/"
|
||||
|
||||
private[sbt] val configIdents: Map[String, String] =
|
||||
Map(
|
||||
"it" -> "IntegrationTest",
|
||||
"scala-tool" -> "ScalaTool",
|
||||
"plugin" -> "CompilerPlugin"
|
||||
)
|
||||
private[sbt] val configIdentsInverse: Map[String, String] =
|
||||
configIdents map { _.swap }
|
||||
|
||||
private[sbt] def guessConfigIdent(conf: String): String =
|
||||
configIdents.applyOrElse(conf, (x: String) => x.capitalize)
|
||||
|
||||
private[sbt] def unguessConfigIdent(conf: String): String =
|
||||
configIdentsInverse.applyOrElse(conf, (x: String) => x.take(1).toLowerCase + x.drop(1))
|
||||
|
||||
def displayConfigKey012Style(config: ConfigKey): String = config.name + ":"
|
||||
|
||||
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 = {
|
||||
private[sbt] def displayPedantic(scope: Scope, sep: String): String =
|
||||
displayMasked(scope, sep, showProject, ScopeMask(), true)
|
||||
|
||||
def displayMasked(scope: Scope, sep: String, mask: ScopeMask): String =
|
||||
displayMasked(scope, sep, showProject, mask)
|
||||
|
||||
def displayMasked(scope: Scope, sep: String, mask: ScopeMask, showZeroConfig: Boolean): String =
|
||||
displayMasked(scope, sep, showProject, mask, showZeroConfig)
|
||||
|
||||
def displayMasked(scope: Scope,
|
||||
sep: String,
|
||||
showProject: Reference => String,
|
||||
mask: ScopeMask): String =
|
||||
displayMasked(scope, sep, showProject, mask, false)
|
||||
|
||||
/**
|
||||
* unified slash style introduced in sbt 1.1.0.
|
||||
* By default, sbt will no longer display the Zero-config,
|
||||
* so `name` will render as `name` as opposed to `{uri}proj/Zero/name`.
|
||||
* Technically speaking an unspecified configuration axis defaults to
|
||||
* the scope delegation (first configuration defining the key, then Zero).
|
||||
*/
|
||||
def displayMasked(scope: Scope,
|
||||
sep: String,
|
||||
showProject: Reference => String,
|
||||
mask: ScopeMask,
|
||||
showZeroConfig: Boolean): String = {
|
||||
import scope.{ project, config, task, extra }
|
||||
val configPrefix = config.foldStrict(display, "*:", ".:")
|
||||
val zeroConfig = if (showZeroConfig) "Zero/" else ""
|
||||
val configPrefix = config.foldStrict(display, zeroConfig, "./")
|
||||
val taskPrefix = task.foldStrict(_.label + "/", "", "./")
|
||||
val extras = extra.foldStrict(_.entries.map(_.toString).toList, Nil, Nil)
|
||||
val postfix = if (extras.isEmpty) "" else extras.mkString("(", ", ", ")")
|
||||
if (scope == GlobalScope) "Global/" + sep + postfix
|
||||
else
|
||||
mask.concatShow(projectPrefix(project, showProject), configPrefix, taskPrefix, sep, postfix)
|
||||
}
|
||||
|
||||
// sbt 0.12 style
|
||||
def display012StyleMasked(scope: Scope,
|
||||
sep: String,
|
||||
showProject: Reference => String,
|
||||
mask: ScopeMask): String = {
|
||||
import scope.{ project, config, task, extra }
|
||||
val configPrefix = config.foldStrict(displayConfigKey012Style, "*:", ".:")
|
||||
val taskPrefix = task.foldStrict(_.label + "::", "", ".::")
|
||||
val extras = extra.foldStrict(_.entries.map(_.toString).toList, Nil, Nil)
|
||||
val postfix = if (extras.isEmpty) "" else extras.mkString("(", ", ", ")")
|
||||
mask.concatShow(projectPrefix(project, showProject), configPrefix, taskPrefix, sep, postfix)
|
||||
mask.concatShow(projectPrefix012Style(project, showProject),
|
||||
configPrefix,
|
||||
taskPrefix,
|
||||
sep,
|
||||
postfix)
|
||||
}
|
||||
|
||||
def equal(a: Scope, b: Scope, mask: ScopeMask): Boolean =
|
||||
|
|
@ -154,10 +209,12 @@ object Scope {
|
|||
(!mask.task || a.task == b.task) &&
|
||||
(!mask.extra || a.extra == b.extra)
|
||||
|
||||
def projectPrefix(
|
||||
project: ScopeAxis[Reference],
|
||||
show: Reference => String = showProject
|
||||
): String =
|
||||
def projectPrefix(project: ScopeAxis[Reference],
|
||||
show: Reference => String = showProject): String =
|
||||
project.foldStrict(show, "Zero/", "./")
|
||||
|
||||
def projectPrefix012Style(project: ScopeAxis[Reference],
|
||||
show: Reference => String = showProject): String =
|
||||
project.foldStrict(show, "*/", "./")
|
||||
|
||||
def showProject = (ref: Reference) => Reference.display(ref) + "/"
|
||||
|
|
|
|||
|
|
@ -2336,13 +2336,20 @@ object Classpaths {
|
|||
else Def.task((evictionWarningOptions in update).value)
|
||||
}.value
|
||||
|
||||
val extracted = (Project extract state0)
|
||||
val isPlugin = sbtPlugin.value
|
||||
val thisRef = thisProjectRef.value
|
||||
val label =
|
||||
if (isPlugin) Reference.display(thisRef)
|
||||
else Def.displayRelativeReference(extracted.currentRef, thisRef)
|
||||
|
||||
LibraryManagement.cachedUpdate(
|
||||
// LM API
|
||||
lm = dependencyResolution.value,
|
||||
// Ivy-free ModuleDescriptor
|
||||
module = ivyModule.value,
|
||||
s.cacheStoreFactory.sub(updateCacheName.value),
|
||||
Reference.display(thisProjectRef.value),
|
||||
label = label,
|
||||
updateConf,
|
||||
substituteScalaFiles(scalaOrganization.value, _)(providedScalaJars),
|
||||
skip = (skip in update).value,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import sbt.librarymanagement.Configuration
|
||||
|
||||
/**
|
||||
* SlashSyntax implements the slash syntax to scope keys for build.sbt DSL.
|
||||
* The implicits are set up such that the order that the scope components
|
||||
* must appear in the order of the project axis, the configuration axis, and
|
||||
* the task axis. This ordering is the same as the shell syntax.
|
||||
*
|
||||
* @example
|
||||
* {{{
|
||||
* Global / cancelable := true
|
||||
* ThisBuild / scalaVersion := "2.12.2"
|
||||
* Test / test := ()
|
||||
* console / scalacOptions += "-deprecation"
|
||||
* Compile / console / scalacOptions += "-Ywarn-numeric-widen"
|
||||
* projA / Compile / console / scalacOptions += "-feature"
|
||||
* Zero / Zero / name := "foo"
|
||||
* }}}
|
||||
*/
|
||||
trait SlashSyntax {
|
||||
import SlashSyntax._
|
||||
|
||||
implicit def sbtScopeSyntaxRichReference(r: Reference): RichReference =
|
||||
new RichReference(Scope(Select(r), This, This, This))
|
||||
|
||||
implicit def sbtScopeSyntaxRichProject(p: Project): RichReference =
|
||||
new RichReference(Scope(Select(p), This, This, This))
|
||||
|
||||
implicit def sbtScopeSyntaxRichConfiguration(c: Configuration): RichConfiguration =
|
||||
new RichConfiguration(Scope(This, Select(c), This, This))
|
||||
|
||||
implicit def sbtScopeSyntaxRichScope(s: Scope): RichScope =
|
||||
new RichScope(s)
|
||||
|
||||
implicit def sbtScopeSyntaxRichScopeFromScoped(t: Scoped): RichScope =
|
||||
new RichScope(Scope(This, This, Select(t.key), This))
|
||||
|
||||
implicit def sbtScopeSyntaxRichScopeAxis(a: ScopeAxis[Reference]): RichScopeAxis =
|
||||
new RichScopeAxis(a)
|
||||
|
||||
// Materialize the setting key thunk
|
||||
implicit def sbtScopeSyntaxSettingKeyThunkMaterialize[A](
|
||||
thunk: SettingKeyThunk[A]): SettingKey[A] =
|
||||
thunk.materialize
|
||||
|
||||
implicit def sbtScopeSyntaxSettingKeyThunkKeyRescope[A](thunk: SettingKeyThunk[A]): RichScope =
|
||||
thunk.rescope
|
||||
|
||||
// Materialize the task key thunk
|
||||
implicit def sbtScopeSyntaxTaskKeyThunkMaterialize[A](thunk: TaskKeyThunk[A]): TaskKey[A] =
|
||||
thunk.materialize
|
||||
|
||||
implicit def sbtScopeSyntaxTaskKeyThunkRescope[A](thunk: TaskKeyThunk[A]): RichScope =
|
||||
thunk.rescope
|
||||
|
||||
// Materialize the input key thunk
|
||||
implicit def sbtScopeSyntaxInputKeyThunkMaterialize[A](thunk: InputKeyThunk[A]): InputKey[A] =
|
||||
thunk.materialize
|
||||
|
||||
implicit def sbtScopeSyntaxInputKeyThunkRescope[A](thunk: InputKeyThunk[A]): RichScope =
|
||||
thunk.rescope
|
||||
}
|
||||
|
||||
object SlashSyntax {
|
||||
|
||||
/** RichReference wraps a project to provide the `/` operator for scoping. */
|
||||
final class RichReference(s: Scope) {
|
||||
def /(c: Configuration): RichConfiguration = new RichConfiguration(s in c)
|
||||
|
||||
// We don't know what the key is for yet, so just capture in a thunk.
|
||||
def /[A](key: SettingKey[A]): SettingKeyThunk[A] = new SettingKeyThunk(s, key)
|
||||
|
||||
// We don't know what the key is for yet, so just capture in a thunk.
|
||||
def /[A](key: TaskKey[A]): TaskKeyThunk[A] = new TaskKeyThunk(s, key)
|
||||
|
||||
// We don't know what the key is for yet, so just capture in a thunk.
|
||||
def /[A](key: InputKey[A]): InputKeyThunk[A] = new InputKeyThunk(s, key)
|
||||
}
|
||||
|
||||
/** RichConfiguration wraps a configuration to provide the `/` operator for scoping. */
|
||||
final class RichConfiguration(s: Scope) {
|
||||
// We don't know what the key is for yet, so just capture in a thunk.
|
||||
def /[A](key: SettingKey[A]): SettingKeyThunk[A] = new SettingKeyThunk(s, key)
|
||||
|
||||
// We don't know what the key is for yet, so just capture in a thunk.
|
||||
def /[A](key: TaskKey[A]): TaskKeyThunk[A] = new TaskKeyThunk(s, key)
|
||||
|
||||
// We don't know what the key is for yet, so just capture in a thunk.
|
||||
def /[A](key: InputKey[A]): InputKeyThunk[A] = new InputKeyThunk(s, key)
|
||||
}
|
||||
|
||||
/** RichScope wraps a general scope to provide the `/` operator for scoping. */
|
||||
final class RichScope(scope: Scope) {
|
||||
def /[A](key: SettingKey[A]): SettingKey[A] = key in scope
|
||||
def /[A](key: TaskKey[A]): TaskKey[A] = key in scope
|
||||
def /[A](key: InputKey[A]): InputKey[A] = key in scope
|
||||
}
|
||||
|
||||
/** RichScopeAxis wraps a project axis to provide the `/` operator to `Zero` for scoping. */
|
||||
final class RichScopeAxis(a: ScopeAxis[Reference]) {
|
||||
private[this] def toScope: Scope = Scope(a, This, This, This)
|
||||
|
||||
def /(c: Configuration): RichConfiguration = new RichConfiguration(toScope in c)
|
||||
|
||||
// This is for handling `Zero / Zero / name`.
|
||||
def /(configAxis: ScopeAxis[ConfigKey]): RichConfiguration =
|
||||
new RichConfiguration(toScope.copy(config = configAxis))
|
||||
|
||||
// We don't know what the key is for yet, so just capture in a thunk.
|
||||
def /[A](key: SettingKey[A]): SettingKeyThunk[A] = new SettingKeyThunk(toScope, key)
|
||||
|
||||
// We don't know what the key is for yet, so just capture in a thunk.
|
||||
def /[A](key: TaskKey[A]): TaskKeyThunk[A] = new TaskKeyThunk(toScope, key)
|
||||
|
||||
// We don't know what the key is for yet, so just capture in a thunk.
|
||||
def /[A](key: InputKey[A]): InputKeyThunk[A] = new InputKeyThunk(toScope, key)
|
||||
}
|
||||
|
||||
/**
|
||||
* SettingKeyThunk is a thunk used to hold a scope and a key
|
||||
* while we're not sure if the key is terminal or task-scoping.
|
||||
*/
|
||||
final class SettingKeyThunk[A](base: Scope, key: SettingKey[A]) {
|
||||
private[sbt] def materialize: SettingKey[A] = key in base
|
||||
private[sbt] def rescope: RichScope = new RichScope(base in key.key)
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskKeyThunk is a thunk used to hold a scope and a key
|
||||
* while we're not sure if the key is terminal or task-scoping.
|
||||
*/
|
||||
final class TaskKeyThunk[A](base: Scope, key: TaskKey[A]) {
|
||||
private[sbt] def materialize: TaskKey[A] = key in base
|
||||
private[sbt] def rescope: RichScope = new RichScope(base in key.key)
|
||||
}
|
||||
|
||||
/**
|
||||
* InputKeyThunk is a thunk used to hold a scope and a key
|
||||
* while we're not sure if the key is terminal or task-scoping.
|
||||
*/
|
||||
final class InputKeyThunk[A](base: Scope, key: InputKey[A]) {
|
||||
private[sbt] def materialize: InputKey[A] = key in base
|
||||
private[sbt] def rescope: RichScope = new RichScope(base in key.key)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,13 @@ final class ParsedKey(val key: ScopedKey[_], val mask: ScopeMask)
|
|||
|
||||
object Act {
|
||||
val ZeroString = "*"
|
||||
private[sbt] val GlobalIdent = "Global"
|
||||
private[sbt] val ZeroIdent = "Zero"
|
||||
private[sbt] val ThisBuildIdent = "ThisBuild"
|
||||
|
||||
// new separator for unified shell syntax. this allows optional whitespace around /.
|
||||
private[sbt] val spacedSlash: Parser[Unit] =
|
||||
token(OptSpace ~> '/' <~ OptSpace).examples("/").map(_ => ())
|
||||
|
||||
// this does not take aggregation into account
|
||||
def scopedKey(index: KeyIndex,
|
||||
|
|
@ -52,12 +59,29 @@ object Act {
|
|||
current: ProjectRef,
|
||||
defaultConfigs: Option[ResolvedReference] => Seq[String],
|
||||
keyMap: Map[String, AttributeKey[_]]): Parser[Seq[Parser[ParsedKey]]] = {
|
||||
for {
|
||||
rawProject <- optProjectRef(index, current)
|
||||
proj = resolveProject(rawProject, current)
|
||||
confAmb <- config(index configs proj)
|
||||
partialMask = ScopeMask(rawProject.isExplicit, confAmb.isExplicit, false, false)
|
||||
} yield taskKeyExtra(index, defaultConfigs, keyMap, proj, confAmb, partialMask)
|
||||
def fullKey =
|
||||
for {
|
||||
rawProject <- optProjectRef(index, current)
|
||||
proj = resolveProject(rawProject, current)
|
||||
confAmb <- configIdent(index configs proj,
|
||||
index configIdents proj,
|
||||
index.fromConfigIdent(proj))
|
||||
partialMask = ScopeMask(rawProject.isExplicit, confAmb.isExplicit, false, false)
|
||||
} yield taskKeyExtra(index, defaultConfigs, keyMap, proj, confAmb, partialMask)
|
||||
|
||||
val globalIdent = token(GlobalIdent ~ spacedSlash) ^^^ ParsedGlobal
|
||||
def globalKey =
|
||||
for {
|
||||
g <- globalIdent
|
||||
} yield
|
||||
taskKeyExtra(index,
|
||||
defaultConfigs,
|
||||
keyMap,
|
||||
None,
|
||||
ParsedZero,
|
||||
ScopeMask(true, true, false, false))
|
||||
|
||||
globalKey | fullKey
|
||||
}
|
||||
|
||||
def taskKeyExtra(
|
||||
|
|
@ -148,6 +172,23 @@ object Act {
|
|||
token((ZeroString ^^^ ParsedZero | value(examples(ID, confs, "configuration"))) <~ sep) ?? Omitted
|
||||
}
|
||||
|
||||
// New configuration parser that's able to parse configuration ident trailed by slash.
|
||||
private[sbt] def configIdent(confs: Set[String],
|
||||
idents: Set[String],
|
||||
fromIdent: String => String): Parser[ParsedAxis[String]] = {
|
||||
val oldSep: Parser[Char] = ':'
|
||||
val sep: Parser[Unit] = spacedSlash !!! "Expected '/'"
|
||||
token(
|
||||
((ZeroString ^^^ ParsedZero) <~ oldSep)
|
||||
| ((ZeroString ^^^ ParsedZero) <~ sep)
|
||||
| ((ZeroIdent ^^^ ParsedZero) <~ sep)
|
||||
| (value(examples(ID, confs, "configuration")) <~ oldSep)
|
||||
| (value(examples(CapitalizedID, idents, "configuration ident") map {
|
||||
fromIdent(_)
|
||||
}) <~ sep)
|
||||
) ?? Omitted
|
||||
}
|
||||
|
||||
def configs(explicit: ParsedAxis[String],
|
||||
defaultConfigs: Option[ResolvedReference] => Seq[String],
|
||||
proj: Option[ResolvedReference],
|
||||
|
|
@ -156,8 +197,8 @@ object Act {
|
|||
case Omitted =>
|
||||
None +: defaultConfigurations(proj, index, defaultConfigs).flatMap(
|
||||
nonEmptyConfig(index, proj))
|
||||
case ParsedZero => None :: Nil
|
||||
case pv: ParsedValue[x] => Some(pv.value) :: Nil
|
||||
case ParsedZero | ParsedGlobal => None :: Nil
|
||||
case pv: ParsedValue[x] => Some(pv.value) :: Nil
|
||||
}
|
||||
|
||||
def defaultConfigurations(
|
||||
|
|
@ -220,11 +261,15 @@ object Act {
|
|||
val valid = allKnown ++ normKeys
|
||||
val suggested = normKeys.map(_._1).toSet
|
||||
val keyP = filterStrings(examples(ID, suggested, "key"), valid.keySet, "key") map valid
|
||||
(token(value(keyP) | ZeroString ^^^ ParsedZero) <~ token("::".id)) ?? Omitted
|
||||
(token(
|
||||
value(keyP)
|
||||
| ZeroString ^^^ ParsedZero
|
||||
| ZeroIdent ^^^ ParsedZero) <~ (token("::".id) | spacedSlash)) ?? Omitted
|
||||
}
|
||||
|
||||
def resolveTask(task: ParsedAxis[AttributeKey[_]]): Option[AttributeKey[_]] =
|
||||
task match {
|
||||
case ParsedZero | Omitted => None
|
||||
case ParsedZero | ParsedGlobal | Omitted => None
|
||||
case t: ParsedValue[AttributeKey[_]] @unchecked => Some(t.value)
|
||||
}
|
||||
|
||||
|
|
@ -260,10 +305,36 @@ object Act {
|
|||
}
|
||||
|
||||
def projectRef(index: KeyIndex, currentBuild: URI): Parser[ParsedAxis[ResolvedReference]] = {
|
||||
val zero = token(ZeroString ~ '/') ^^^ ParsedZero
|
||||
val trailing = '/' !!! "Expected '/' (if selecting a project)"
|
||||
zero | value(resolvedReference(index, currentBuild, trailing))
|
||||
val global = token(ZeroString ~ spacedSlash) ^^^ ParsedZero
|
||||
val zeroIdent = token(ZeroIdent ~ spacedSlash) ^^^ ParsedZero
|
||||
val thisBuildIdent = value(token(ThisBuildIdent ~ spacedSlash) ^^^ BuildRef(currentBuild))
|
||||
val trailing = spacedSlash !!! "Expected '/' (if selecting a project)"
|
||||
global | zeroIdent | thisBuildIdent |
|
||||
value(resolvedReferenceIdent(index, currentBuild, trailing)) |
|
||||
value(resolvedReference(index, currentBuild, trailing))
|
||||
}
|
||||
|
||||
private[sbt] def resolvedReferenceIdent(index: KeyIndex,
|
||||
currentBuild: URI,
|
||||
trailing: Parser[_]): Parser[ResolvedReference] = {
|
||||
def projectID(uri: URI) =
|
||||
token(
|
||||
DQuoteChar ~> examplesStrict(ID, index projects uri, "project ID") <~ DQuoteChar <~ OptSpace <~ ")" <~ trailing)
|
||||
def projectRef(uri: URI) = projectID(uri) map { id =>
|
||||
ProjectRef(uri, id)
|
||||
}
|
||||
|
||||
val uris = index.buildURIs
|
||||
val resolvedURI = Uri(uris).map(uri => Scope.resolveBuild(currentBuild, uri))
|
||||
|
||||
val buildRef = token(
|
||||
"ProjectRef(" ~> OptSpace ~> "uri(" ~> OptSpace ~> DQuoteChar ~>
|
||||
resolvedURI <~ DQuoteChar <~ OptSpace <~ ")" <~ spacedComma)
|
||||
buildRef flatMap { uri =>
|
||||
projectRef(uri)
|
||||
}
|
||||
}
|
||||
|
||||
def resolvedReference(index: KeyIndex,
|
||||
currentBuild: URI,
|
||||
trailing: Parser[_]): Parser[ResolvedReference] = {
|
||||
|
|
@ -284,11 +355,13 @@ object Act {
|
|||
}
|
||||
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 ParsedZero => None
|
||||
case ParsedGlobal => None
|
||||
case pv: ParsedValue[rr] => Some(pv.value)
|
||||
}
|
||||
|
||||
|
|
@ -375,6 +448,7 @@ object Act {
|
|||
sealed trait ParsedAxis[+T] {
|
||||
final def isExplicit = this != Omitted
|
||||
}
|
||||
final object ParsedGlobal extends ParsedAxis[Nothing]
|
||||
final object ParsedZero extends ParsedAxis[Nothing]
|
||||
final object Omitted extends ParsedAxis[Nothing]
|
||||
final class ParsedValue[T](val value: T) extends ParsedAxis[T]
|
||||
|
|
|
|||
|
|
@ -9,20 +9,32 @@ import Def.ScopedKey
|
|||
import sbt.internal.util.complete.DefaultParsers.validID
|
||||
import sbt.internal.util.Types.some
|
||||
import sbt.internal.util.{ AttributeKey, Relation }
|
||||
import sbt.librarymanagement.Configuration
|
||||
|
||||
object KeyIndex {
|
||||
def empty: ExtendableKeyIndex = new KeyIndex0(emptyBuildIndex)
|
||||
def apply(known: Iterable[ScopedKey[_]], projects: Map[URI, Set[String]]): ExtendableKeyIndex =
|
||||
(base(projects) /: known) { _ add _ }
|
||||
def apply(known: Iterable[ScopedKey[_]],
|
||||
projects: Map[URI, Set[String]],
|
||||
configurations: Map[String, Seq[Configuration]]): ExtendableKeyIndex =
|
||||
(base(projects, configurations) /: known) { _ add _ }
|
||||
def aggregate(known: Iterable[ScopedKey[_]],
|
||||
extra: BuildUtil[_],
|
||||
projects: Map[URI, Set[String]]): ExtendableKeyIndex =
|
||||
(base(projects) /: known) { (index, key) =>
|
||||
projects: Map[URI, Set[String]],
|
||||
configurations: Map[String, Seq[Configuration]]): ExtendableKeyIndex =
|
||||
(base(projects, configurations) /: known) { (index, key) =>
|
||||
index.addAggregated(key, extra)
|
||||
}
|
||||
private[this] def base(projects: Map[URI, Set[String]]): ExtendableKeyIndex = {
|
||||
val data = for ((uri, ids) <- projects) yield {
|
||||
val data = ids.map(id => Option(id) -> new ConfigIndex(Map.empty))
|
||||
private[this] def base(projects: Map[URI, Set[String]],
|
||||
configurations: Map[String, Seq[Configuration]]): ExtendableKeyIndex = {
|
||||
val data = for {
|
||||
(uri, ids) <- projects
|
||||
} yield {
|
||||
val data = ids map { id =>
|
||||
val configs = configurations.getOrElse(id, Seq())
|
||||
Option(id) -> new ConfigIndex(Map.empty, Map(configs map { c =>
|
||||
(c.name, c.id)
|
||||
}: _*))
|
||||
}
|
||||
Option(uri) -> new ProjectIndex(data.toMap)
|
||||
}
|
||||
new KeyIndex0(new BuildIndex(data.toMap))
|
||||
|
|
@ -33,6 +45,14 @@ object KeyIndex {
|
|||
def projects(uri: URI) = concat(_.projects(uri))
|
||||
def exists(project: Option[ResolvedReference]): Boolean = indices.exists(_ exists project)
|
||||
def configs(proj: Option[ResolvedReference]) = concat(_.configs(proj))
|
||||
private[sbt] def configIdents(proj: Option[ResolvedReference]) = concat(_.configIdents(proj))
|
||||
private[sbt] def fromConfigIdent(proj: Option[ResolvedReference])(configIdent: String): String =
|
||||
(indices find { idx =>
|
||||
idx.exists(proj)
|
||||
}) match {
|
||||
case Some(idx) => idx.fromConfigIdent(proj)(configIdent)
|
||||
case _ => Scope.unguessConfigIdent(configIdent)
|
||||
}
|
||||
def tasks(proj: Option[ResolvedReference], conf: Option[String]) = concat(_.tasks(proj, conf))
|
||||
def tasks(proj: Option[ResolvedReference], conf: Option[String], key: String) =
|
||||
concat(_.tasks(proj, conf, key))
|
||||
|
|
@ -46,7 +66,7 @@ object KeyIndex {
|
|||
private[sbt] def getOr[A, B](m: Map[A, B], key: A, or: B): B = m.getOrElse(key, or)
|
||||
private[sbt] def keySet[A, B](m: Map[Option[A], B]): Set[A] = m.keys.flatten.toSet
|
||||
private[sbt] val emptyAKeyIndex = new AKeyIndex(Relation.empty)
|
||||
private[sbt] val emptyConfigIndex = new ConfigIndex(Map.empty)
|
||||
private[sbt] val emptyConfigIndex = new ConfigIndex(Map.empty, Map.empty)
|
||||
private[sbt] val emptyProjectIndex = new ProjectIndex(Map.empty)
|
||||
private[sbt] val emptyBuildIndex = new BuildIndex(Map.empty)
|
||||
}
|
||||
|
|
@ -73,6 +93,8 @@ trait KeyIndex {
|
|||
def keys(proj: Option[ResolvedReference],
|
||||
conf: Option[String],
|
||||
task: Option[AttributeKey[_]]): Set[String]
|
||||
private[sbt] def configIdents(project: Option[ResolvedReference]): Set[String]
|
||||
private[sbt] def fromConfigIdent(proj: Option[ResolvedReference])(configIdent: String): String
|
||||
}
|
||||
trait ExtendableKeyIndex extends KeyIndex {
|
||||
def add(scoped: ScopedKey[_]): ExtendableKeyIndex
|
||||
|
|
@ -87,13 +109,34 @@ private[sbt] final class AKeyIndex(val data: Relation[Option[AttributeKey[_]], S
|
|||
def tasks: Set[AttributeKey[_]] = data._1s.flatten.toSet
|
||||
def tasks(key: String): Set[AttributeKey[_]] = data.reverse(key).flatten.toSet
|
||||
}
|
||||
private[sbt] final class ConfigIndex(val data: Map[Option[String], AKeyIndex]) {
|
||||
|
||||
/*
|
||||
* data contains the mapping between a configuration and keys.
|
||||
* identData contains the mapping between a configuration and its identifier.
|
||||
*/
|
||||
private[sbt] final class ConfigIndex(val data: Map[Option[String], AKeyIndex],
|
||||
val identData: Map[String, String]) {
|
||||
def add(config: Option[String],
|
||||
task: Option[AttributeKey[_]],
|
||||
key: AttributeKey[_]): ConfigIndex =
|
||||
new ConfigIndex(data updated (config, keyIndex(config).add(task, key)))
|
||||
key: AttributeKey[_]): ConfigIndex = {
|
||||
new ConfigIndex(data updated (config, keyIndex(config).add(task, key)), this.identData)
|
||||
}
|
||||
|
||||
def keyIndex(conf: Option[String]): AKeyIndex = getOr(data, conf, emptyAKeyIndex)
|
||||
def configs: Set[String] = keySet(data)
|
||||
|
||||
private[sbt] val configIdentsInverse: Map[String, String] =
|
||||
identData map { _.swap }
|
||||
|
||||
private[sbt] lazy val idents: Set[String] =
|
||||
configs map { config =>
|
||||
identData.getOrElse(config, Scope.guessConfigIdent(config))
|
||||
}
|
||||
|
||||
// guess Configuration name from an identifier.
|
||||
// There's a guessing involved because we could have scoped key that Project is not aware of.
|
||||
private[sbt] def fromConfigIdent(ident: String): String =
|
||||
configIdentsInverse.getOrElse(ident, Scope.unguessConfigIdent(ident))
|
||||
}
|
||||
private[sbt] final class ProjectIndex(val data: Map[Option[String], ConfigIndex]) {
|
||||
def add(id: Option[String],
|
||||
|
|
@ -122,6 +165,13 @@ private[sbt] final class KeyIndex0(val data: BuildIndex) extends ExtendableKeyIn
|
|||
data.data.get(build).flatMap(_.data.get(project)).isDefined
|
||||
}
|
||||
def configs(project: Option[ResolvedReference]): Set[String] = confIndex(project).configs
|
||||
|
||||
private[sbt] def configIdents(project: Option[ResolvedReference]): Set[String] =
|
||||
confIndex(project).idents
|
||||
|
||||
private[sbt] def fromConfigIdent(proj: Option[ResolvedReference])(configIdent: String): String =
|
||||
confIndex(proj).fromConfigIdent(configIdent)
|
||||
|
||||
def tasks(proj: Option[ResolvedReference], conf: Option[String]): Set[AttributeKey[_]] =
|
||||
keyIndex(proj, conf).tasks
|
||||
def tasks(proj: Option[ResolvedReference],
|
||||
|
|
|
|||
|
|
@ -324,8 +324,13 @@ private[sbt] object Load {
|
|||
val attributeKeys = Index.attributeKeys(data) ++ keys.map(_.key)
|
||||
val scopedKeys = keys ++ data.allKeys((s, k) => ScopedKey(s, k)).toVector
|
||||
val projectsMap = projects.mapValues(_.defined.keySet)
|
||||
val keyIndex = KeyIndex(scopedKeys.toVector, projectsMap)
|
||||
val aggIndex = KeyIndex.aggregate(scopedKeys.toVector, extra(keyIndex), projectsMap)
|
||||
val configsMap: Map[String, Seq[Configuration]] = Map(projects.values.toSeq flatMap { bu =>
|
||||
bu.defined map {
|
||||
case (k, v) => (k, v.configurations)
|
||||
}
|
||||
}: _*)
|
||||
val keyIndex = KeyIndex(scopedKeys.toVector, projectsMap, configsMap)
|
||||
val aggIndex = KeyIndex.aggregate(scopedKeys.toVector, extra(keyIndex), projectsMap, configsMap)
|
||||
new StructureIndex(
|
||||
Index.stringToKeyMap(attributeKeys),
|
||||
Index.taskToKeyMap(data),
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ package sbt
|
|||
import Project._
|
||||
import sbt.internal.util.Types.idFun
|
||||
import sbt.internal.TestBuild._
|
||||
import sbt.librarymanagement.Configuration
|
||||
import org.scalacheck._
|
||||
import Prop._
|
||||
import Gen._
|
||||
|
||||
object Delegates extends Properties("delegates") {
|
||||
property("generate non-empty configs") = forAll { (c: Seq[Config]) =>
|
||||
property("generate non-empty configs") = forAll { (c: Seq[Configuration]) =>
|
||||
c.nonEmpty
|
||||
}
|
||||
property("generate non-empty tasks") = forAll { (t: Seq[Taskk]) =>
|
||||
|
|
|
|||
|
|
@ -22,13 +22,17 @@ object ParseKey extends Properties("Key parser test") {
|
|||
|
||||
property("An explicitly specified axis is always parsed to that explicit value") =
|
||||
forAllNoShrink(structureDefinedKey) { (skm: StructureKeyMask) =>
|
||||
import skm.{ structure, key, mask }
|
||||
import skm.{ structure, key, mask => mask0 }
|
||||
|
||||
val hasZeroConfig = key.scope.config == Zero
|
||||
val mask = if (hasZeroConfig) mask0.copy(project = true) else mask0
|
||||
val expected = resolve(structure, key, mask)
|
||||
val string = displayMasked(key, mask)
|
||||
|
||||
("Key: " + displayFull(key)) |:
|
||||
parseExpected(structure, string, expected, mask)
|
||||
// Note that this explicitly displays the configuration axis set to Zero.
|
||||
// This is to disambiguate `proj/Zero/name`, which could render potentially
|
||||
// as `Zero/name`, but could be interpretted as `Zero/Zero/name`.
|
||||
val s = displayMasked(key, mask, hasZeroConfig)
|
||||
("Key: " + displayPedantic(key)) |:
|
||||
parseExpected(structure, s, expected, mask)
|
||||
}
|
||||
|
||||
property("An unspecified project axis resolves to the current project") =
|
||||
|
|
@ -37,13 +41,16 @@ object ParseKey extends Properties("Key parser test") {
|
|||
|
||||
val mask = skm.mask.copy(project = false)
|
||||
val string = displayMasked(key, mask)
|
||||
// skip when config axis is set to Zero
|
||||
val hasZeroConfig = key.scope.config == Zero
|
||||
|
||||
("Key: " + displayFull(key)) |:
|
||||
("Key: " + displayPedantic(key)) |:
|
||||
("Mask: " + mask) |:
|
||||
("Current: " + structure.current) |:
|
||||
parse(structure, string) {
|
||||
case Left(err) => false
|
||||
case Right(sk) => sk.scope.project == Select(structure.current)
|
||||
case Left(err) => false
|
||||
case Right(sk) if hasZeroConfig => true
|
||||
case Right(sk) => sk.scope.project == Select(structure.current)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +60,7 @@ object ParseKey extends Properties("Key parser test") {
|
|||
val mask = skm.mask.copy(task = false)
|
||||
val string = displayMasked(key, mask)
|
||||
|
||||
("Key: " + displayFull(key)) |:
|
||||
("Key: " + displayPedantic(key)) |:
|
||||
("Mask: " + mask) |:
|
||||
parse(structure, string) {
|
||||
case Left(err) => false
|
||||
|
|
@ -69,15 +76,18 @@ object ParseKey extends Properties("Key parser test") {
|
|||
val string = displayMasked(key, mask)
|
||||
val resolvedConfig = Resolve.resolveConfig(structure.extra, key.key, mask)(key.scope).config
|
||||
|
||||
("Key: " + displayFull(key)) |:
|
||||
("Key: " + displayPedantic(key)) |:
|
||||
("Mask: " + mask) |:
|
||||
("Expected configuration: " + resolvedConfig.map(_.name)) |:
|
||||
parse(structure, string) {
|
||||
case Right(sk) => sk.scope.config == resolvedConfig
|
||||
case Right(sk) => (sk.scope.config == resolvedConfig) || (sk.scope == Scope.GlobalScope)
|
||||
case Left(err) => false
|
||||
}
|
||||
}
|
||||
|
||||
def displayPedantic(scoped: ScopedKey[_]): String =
|
||||
Scope.displayPedantic(scoped.scope, scoped.key.label)
|
||||
|
||||
lazy val structureDefinedKey: Gen[StructureKeyMask] = structureKeyMask { s =>
|
||||
for (scope <- TestBuild.scope(s.env); key <- oneOf(s.allAttributeKeys.toSeq))
|
||||
yield ScopedKey(scope, key)
|
||||
|
|
@ -101,7 +111,10 @@ object ParseKey extends Properties("Key parser test") {
|
|||
("Mask: " + mask) |:
|
||||
parse(structure, s) {
|
||||
case Left(err) => false
|
||||
case Right(sk) => Project.equal(sk, expected, mask)
|
||||
case Right(sk) =>
|
||||
(s"${sk}.key == ${expected}.key: ${sk.key == expected.key}") |:
|
||||
(s"${sk.scope} == ${expected.scope}: ${Scope.equal(sk.scope, expected.scope, mask)}") |:
|
||||
Project.equal(sk, expected, mask)
|
||||
}
|
||||
|
||||
def parse(structure: Structure, s: String)(f: Either[String, ScopedKey[_]] => Prop): Prop = {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import Def.{ ScopedKey, Setting }
|
|||
import sbt.internal.util.{ AttributeKey, AttributeMap, Relation, Settings }
|
||||
import sbt.internal.util.Types.{ const, some }
|
||||
import sbt.internal.util.complete.Parser
|
||||
import sbt.librarymanagement.Configuration
|
||||
|
||||
import java.net.URI
|
||||
import org.scalacheck._
|
||||
|
|
@ -119,7 +120,7 @@ abstract class TestBuild {
|
|||
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
|
||||
projectFor(ref).confMap(config.name).extendsConfigs map toConfigKey
|
||||
def inheritTask(task: AttributeKey[_]) = taskMap.get(task) match {
|
||||
case None => Nil; case Some(t) => t.delegates map getKey
|
||||
}
|
||||
|
|
@ -144,7 +145,7 @@ abstract class TestBuild {
|
|||
} yield Scope(project = ref, config = c, task = t, extra = Zero)
|
||||
}
|
||||
def getKey: Taskk => AttributeKey[_] = _.key
|
||||
def toConfigKey: Config => ConfigKey = c => ConfigKey(c.name)
|
||||
def toConfigKey: Configuration => ConfigKey = c => ConfigKey(c.name)
|
||||
final class Build(val uri: URI, val projects: Seq[Proj]) {
|
||||
override def toString = "Build " + uri.toString + " :\n " + projects.mkString("\n ")
|
||||
val allProjects = projects map { p =>
|
||||
|
|
@ -156,7 +157,7 @@ abstract class TestBuild {
|
|||
final class Proj(
|
||||
val id: String,
|
||||
val delegates: Seq[ProjectRef],
|
||||
val configurations: Seq[Config]
|
||||
val configurations: Seq[Configuration]
|
||||
) {
|
||||
override def toString =
|
||||
"Project " + id + "\n Delegates:\n " + delegates.mkString("\n ") +
|
||||
|
|
@ -164,9 +165,6 @@ abstract class TestBuild {
|
|||
val confMap = mapBy(configurations)(_.name)
|
||||
}
|
||||
|
||||
final class Config(val name: String, val extended: Seq[Config]) {
|
||||
override def toString = name + " (extends: " + extended.map(_.name).mkString(", ") + ")"
|
||||
}
|
||||
final class Taskk(val key: AttributeKey[String], val delegates: Seq[Taskk]) {
|
||||
override def toString =
|
||||
key.label + " (delegates: " + delegates.map(_.key.label).mkString(", ") + ")"
|
||||
|
|
@ -222,7 +220,15 @@ abstract class TestBuild {
|
|||
val keys = data.allKeys((s, key) => ScopedKey(s, key))
|
||||
val keyMap = keys.map(k => (k.key.label, k.key)).toMap[String, AttributeKey[_]]
|
||||
val projectsMap = env.builds.map(b => (b.uri, b.projects.map(_.id).toSet)).toMap
|
||||
new Structure(env, current, data, KeyIndex(keys, projectsMap), keyMap)
|
||||
val confs = for {
|
||||
b <- env.builds.toVector
|
||||
p <- b.projects.toVector
|
||||
c <- p.configurations.toVector
|
||||
} yield c
|
||||
val confMap = Map(confs map { c =>
|
||||
(c.name, Seq(c))
|
||||
}: _*)
|
||||
new Structure(env, current, data, KeyIndex(keys, projectsMap, confMap), keyMap)
|
||||
}
|
||||
|
||||
implicit lazy val mkEnv: Gen[Env] = {
|
||||
|
|
@ -239,7 +245,14 @@ abstract class TestBuild {
|
|||
}
|
||||
|
||||
implicit lazy val idGen: Gen[String] =
|
||||
for (size <- chooseShrinkable(1, MaxIDSize); cs <- listOfN(size, alphaChar)) yield cs.mkString
|
||||
for {
|
||||
size <- chooseShrinkable(1, MaxIDSize)
|
||||
cs <- listOfN(size, alphaChar)
|
||||
} yield {
|
||||
val xs = cs.mkString
|
||||
xs.take(1).toLowerCase + xs.drop(1)
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
@ -256,7 +269,7 @@ abstract class TestBuild {
|
|||
implicit def genProjects(build: URI)(implicit genID: Gen[String],
|
||||
maxDeps: Gen[Int],
|
||||
count: Gen[Int],
|
||||
confs: Gen[Seq[Config]]): Gen[Seq[Proj]] =
|
||||
confs: Gen[Seq[Configuration]]): Gen[Seq[Proj]] =
|
||||
genAcyclic(maxDeps, genID, count) { (id: String) =>
|
||||
for (cs <- confs) yield { (deps: Seq[Proj]) =>
|
||||
new Proj(id, deps.map { dep =>
|
||||
|
|
@ -264,10 +277,16 @@ abstract class TestBuild {
|
|||
}, cs)
|
||||
}
|
||||
}
|
||||
|
||||
def genConfigs(implicit genName: Gen[String],
|
||||
maxDeps: Gen[Int],
|
||||
count: Gen[Int]): Gen[Seq[Config]] =
|
||||
genAcyclicDirect[Config, String](maxDeps, genName, count)((key, deps) => new Config(key, deps))
|
||||
count: Gen[Int]): Gen[Seq[Configuration]] =
|
||||
genAcyclicDirect[Configuration, String](maxDeps, genName, count)(
|
||||
(key, deps) =>
|
||||
Configuration
|
||||
.of(key.capitalize, key)
|
||||
.withExtendsConfigs(deps.toVector))
|
||||
|
||||
def genTasks(implicit genName: Gen[String], maxDeps: Gen[Int], count: Gen[Int]): Gen[Seq[Taskk]] =
|
||||
genAcyclicDirect[Taskk, String](maxDeps, genName, count)((key, deps) =>
|
||||
new Taskk(AttributeKey[String](key), deps))
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ object SettingQueryTest extends org.specs2.mutable.Specification {
|
|||
|
||||
"scalaVersion" in qko("Not a valid project ID: scalaVersion\\nscalaVersion\\n ^")
|
||||
"t/scalacOptions" in qko(
|
||||
s"Key {$baseUri}t/compile:scalacOptions is a task, can only query settings")
|
||||
s"""Key ProjectRef(uri(\\"$baseUri\\"),\\"t\\")/Compile/scalacOptions is a task, can only query settings""")
|
||||
"t/fooo" in qko(
|
||||
"Expected ':' (if selecting a configuration)\\nNot a valid key: fooo (similar: fork)\\nt/fooo\\n ^")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
### Fixes with compatibility implications
|
||||
|
||||
-
|
||||
|
||||
### Improvements
|
||||
|
||||
- Unifies sbt shell and build.sbt syntax. See below.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
-
|
||||
|
||||
### Unified slash syntax for sbt shell and build.sbt
|
||||
|
||||
This adds unified slash syntax for both sbt shell and the build.sbt DSL.
|
||||
Instead of the current `<project-id>/config:intask::key`, this adds
|
||||
`<project-id>/<config-ident>/intask/key` where `<config-ident>` is the Scala identifier
|
||||
notation for the configurations like `Compile` and `Test`. (The old shell syntax will continue to function)
|
||||
|
||||
These examples work both from the shell and in build.sbt.
|
||||
|
||||
Global / cancelable
|
||||
ThisBuild / scalaVersion
|
||||
Test / test
|
||||
root / Compile / compile / scalacOptions
|
||||
ProjectRef(uri("file:/xxx/helloworld/"),"root")/Compile/scalacOptions
|
||||
Zero / Zero / name
|
||||
|
||||
The inspect command now outputs something that can be copy-pasted:
|
||||
|
||||
> inspect compile
|
||||
[info] Task: sbt.inc.Analysis
|
||||
[info] Description:
|
||||
[info] Compiles sources.
|
||||
[info] Provided by:
|
||||
[info] ProjectRef(uri("file:/xxx/helloworld/"),"root")/Compile/compile
|
||||
[info] Defined at:
|
||||
[info] (sbt.Defaults) Defaults.scala:326
|
||||
[info] Dependencies:
|
||||
[info] Compile/manipulateBytecode
|
||||
[info] Compile/incCompileSetup
|
||||
....
|
||||
|
||||
[#3434][3434] by [@eed3si9n][@eed3si9n]
|
||||
|
||||
[3434]: https://github.com/sbt/sbt/pull/3434
|
||||
[@eed3si9n]: https://github.com/eed3si9n
|
||||
[@dwijnand]: http://github.com/dwijnand
|
||||
|
|
@ -16,6 +16,7 @@ package object sbt
|
|||
with sbt.ScopeFilter.Make
|
||||
with sbt.BuildSyntax
|
||||
with sbt.OptionSyntax
|
||||
with sbt.SlashSyntax
|
||||
with sbt.Import {
|
||||
// IO
|
||||
def uri(s: String): URI = new URI(s)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
lazy val akey = AttributeKey[Int]("TestKey")
|
||||
lazy val akey = AttributeKey[Int]("testKey")
|
||||
lazy val testTask = taskKey[String]("")
|
||||
lazy val check = inputKey[Unit]("")
|
||||
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ organization := "org.example"
|
|||
|
||||
proguardSettings
|
||||
|
||||
useJGit
|
||||
useJGit
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
lazy val plugins = (project in file("."))
|
||||
.dependsOn(proguard, git)
|
||||
|
||||
// e7b4732969c137db1b5
|
||||
// d4974f7362bf55d3f52
|
||||
lazy val proguard = uri("git://github.com/sbt/sbt-proguard.git#e7b4732969c137db1b5")
|
||||
lazy val git = uri("git://github.com/sbt/sbt-git.git#2e7c2503850698d60bb")
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
import Dependencies._
|
||||
import sbt.internal.CommandStrings.{ inspectBrief, inspectDetailed }
|
||||
import sbt.internal.Inspect
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.settings(
|
||||
Global / cancelable := true,
|
||||
ThisBuild / scalaVersion := "2.12.3",
|
||||
console / scalacOptions += "-deprecation",
|
||||
Compile / console / scalacOptions += "-Ywarn-numeric-widen",
|
||||
projA / Compile / console / scalacOptions += "-feature",
|
||||
Zero / Zero / name := "foo",
|
||||
|
||||
libraryDependencies += uTest % Test,
|
||||
testFrameworks += new TestFramework("utest.runner.Framework"),
|
||||
|
||||
commands += Command("inspectCheck", inspectBrief, inspectDetailed)(Inspect.parser) {
|
||||
case (s, (option, sk)) =>
|
||||
val actual = Inspect.output(s, option, sk)
|
||||
val expected = s"""Task: Unit
|
||||
Description:
|
||||
\tExecutes all tests.
|
||||
Provided by:
|
||||
\tProjectRef(uri("${baseDirectory.value.toURI}"),"root")/Test/test
|
||||
Defined at:
|
||||
\t(sbt.Defaults.testTasks) Defaults.scala:670
|
||||
Dependencies:
|
||||
\tTest/executeTests
|
||||
\tTest/test/streams
|
||||
\tTest/state
|
||||
\tTest/test/testResultLogger
|
||||
Delegates:
|
||||
\tTest/test
|
||||
\tRuntime/test
|
||||
\tCompile/test
|
||||
\ttest
|
||||
\tThisBuild/Test/test
|
||||
\tThisBuild/Runtime/test
|
||||
\tThisBuild/Compile/test
|
||||
\tThisBuild/test
|
||||
\tZero/Test/test
|
||||
\tZero/Runtime/test
|
||||
\tZero/Compile/test
|
||||
\tGlobal/test
|
||||
Related:
|
||||
\tprojA/Test/test"""
|
||||
|
||||
if (processText(actual) == processText(expected)) ()
|
||||
else {
|
||||
sys.error(s"""actual:
|
||||
$actual
|
||||
|
||||
expected:
|
||||
$expected
|
||||
""")
|
||||
}
|
||||
s.log.info(actual)
|
||||
s
|
||||
}
|
||||
)
|
||||
|
||||
lazy val projA = (project in file("a"))
|
||||
|
||||
def processText(s: String): Vector[String] = {
|
||||
val xs = s.split(IO.Newline).toVector
|
||||
.map( _.trim )
|
||||
// declared location of the task is unstable.
|
||||
.filterNot( _.contains("Defaults.scala") )
|
||||
xs
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import sbt._
|
||||
|
||||
object Dependencies {
|
||||
val uTest = "com.lihaoyi" %% "utest" % "0.5.3"
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package example
|
||||
|
||||
import utest._
|
||||
|
||||
import java.nio.file.{ Files, Path, Paths }
|
||||
|
||||
object HelloTests extends TestSuite {
|
||||
val tests = Tests {
|
||||
'test1 - {
|
||||
val p = Paths.get("target", "foo")
|
||||
Files.createFile(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
> root/Compile/compile
|
||||
|
||||
# "Global" now works in shell.
|
||||
> show Global/cancelable
|
||||
|
||||
# "Global" now works in shell. optional whitespace around /
|
||||
> show Global / cancelable
|
||||
|
||||
# "ThisBuild" now works in shell.
|
||||
> show ThisBuild/scalaVersion
|
||||
|
||||
# "ThisBuild" now works in shell. optional whitespace around /
|
||||
> show ThisBuild / scalaVersion
|
||||
|
||||
$ mkdir target
|
||||
> Test/test
|
||||
# Check the side-effect of executing uTest
|
||||
$ exists target/foo
|
||||
|
||||
# use all axes
|
||||
> show root/Compile/compile/scalacOptions
|
||||
|
||||
# use all axes. optional whitespace around /
|
||||
> show root / Compile / compile / scalacOptions
|
||||
|
||||
> show Zero / Zero / name
|
||||
|
||||
> inspectCheck test
|
||||
Loading…
Reference in New Issue