diff --git a/main-settings/src/main/scala/sbt/SlashSyntax.scala b/main-settings/src/main/scala/sbt/SlashSyntax.scala index fc98cfc39..93d31b1aa 100644 --- a/main-settings/src/main/scala/sbt/SlashSyntax.scala +++ b/main-settings/src/main/scala/sbt/SlashSyntax.scala @@ -42,22 +42,33 @@ trait SlashSyntax { implicit def sbtSlashSyntaxRichConfiguration(c: Configuration): RichConfiguration = (c: ConfigKey) - implicit def sbtSlashSyntaxRichScopeFromScoped(t: Scoped): RichScope = - new RichScope(Scope(This, This, Select(t.key), This)) - implicit def sbtSlashSyntaxRichScope(s: Scope): RichScope = new RichScope(s) - implicit def sbtSlashSyntaxScopeAndKeyRescope(scopeAndKey: ScopeAndKey[_]): TerminalScope = - scopeAndKey.rescope + /** + * This handles task scoping an existing scoped key (such as `Compile / test`) + * into a task scoping in `(Compile / test) / name`. + */ + implicit def sbtSlashSyntaxRichScopeFromScoped(t: Scoped): RichScope = + new RichScope(t.scope.copy(task = Select(t.key))) + + implicit def sbtSlashSyntaxRichScopeFromAttributeKey(a: AttributeKey[_]): RichScope = + Scope(This, This, Select(a), This) - implicit def sbtSlashSyntaxScopeAndKeyMaterialize[K <: Key[K]](scopeAndKey: ScopeAndKey[K]): K = - scopeAndKey.materialize } object SlashSyntax { + sealed trait HasSlashKey { + protected def scope: Scope + final def /[K](key: Scoped.ScopingSetting[K]): K = key in scope + } + + sealed trait HasSlashKeyOrAttrKey extends HasSlashKey { + final def /(key: AttributeKey[_]): RichScope = new RichScope(scope in key) + } + /** RichReference wraps a reference to provide the `/` operator for scoping. */ - final class RichReference(protected val scope: Scope) extends RichScopeLike { + final class RichReference(protected val scope: Scope) extends HasSlashKeyOrAttrKey { def /(c: ConfigKey): RichConfiguration = new RichConfiguration(scope in c) def /(c: Configuration): RichConfiguration = new RichConfiguration(scope in c) @@ -67,47 +78,13 @@ object SlashSyntax { } /** RichConfiguration wraps a configuration to provide the `/` operator for scoping. */ - final class RichConfiguration(protected val scope: Scope) extends RichScopeLike { - + final class RichConfiguration(protected val scope: Scope) extends HasSlashKeyOrAttrKey { // This is for handling `Zero / Zero / Zero / name`. def /(taskAxis: ScopeAxis[AttributeKey[_]]): RichScope = new RichScope(scope.copy(task = taskAxis)) } - /** Both `Scoped.ScopingSetting` and `Scoped` are parents of `SettingKey`, `TaskKey` and - * `InputKey`. We'll need both, so this is a convenient type alias. */ - type Key[K] = Scoped.ScopingSetting[K] with Scoped - - sealed trait RichScopeLike { - protected def scope: Scope - - // We don't know what the key is for yet, so just capture for now. - def /[K <: Key[K]](key: K): ScopeAndKey[K] = new ScopeAndKey(scope, key) - } - /** RichScope wraps a general scope to provide the `/` operator for scoping. */ - final class RichScope(protected val scope: Scope) extends RichScopeLike - - /** TerminalScope provides the last `/` for scoping. */ - final class TerminalScope(scope: Scope) { - def /[K <: Key[K]](key: K): K = key in scope - } - - /** - * ScopeAndKey is a synthetic DSL construct necessary to capture both the built-up scope with a - * given key, while we're not sure if the given key is terminal or task-scoping. The "materialize" - * method will be used if it's terminal, returning the scoped key, while "rescope" will be used - * if we're task-scoping. - * - * @param scope the built-up scope - * @param key a given key - * @tparam K the type of the given key, necessary to type "materialize" - */ - final class ScopeAndKey[K <: Key[K]](scope: Scope, key: K) { - private[sbt] def materialize: K = key in scope - private[sbt] def rescope: TerminalScope = new TerminalScope(scope in key.key) - - override def toString: String = s"$scope / ${key.key}" - } + final class RichScope(protected val scope: Scope) extends HasSlashKey } diff --git a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala index 8432a3f54..d439809f3 100644 --- a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala +++ b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala @@ -11,7 +11,7 @@ import org.scalacheck.{ Test => _, _ }, Arbitrary.arbitrary, Gen._, Prop._ import java.io.File import sbt.io.IO -import sbt.SlashSyntax, SlashSyntax.Key +import sbt.SlashSyntax import sbt.{ Scope, ScopeAxis, Scoped, Select, This, Zero }, Scope.{ Global, ThisScope } import sbt.{ BuildRef, LocalProject, LocalRootProject, ProjectRef, Reference, RootProject, ThisBuild, ThisProject } import sbt.ConfigKey @@ -59,25 +59,22 @@ object BuildDSLInstances { def withScope[K <: Scoped.ScopingSetting[K]](keyGen: Gen[K]): Arbitrary[K] = Arbitrary(Gen.frequency( - 50 -> keyGen, + 5 -> keyGen, 1 -> (for (key <- keyGen; scope <- arbitrary[Scope]) yield key in scope) )) - implicit def arbInputKey[A: Manifest]: Arbitrary[InputKey[A]] = - withScope(Gen.identifier map (InputKey[A](_))) + def genInputKey[A: Manifest]: Gen[InputKey[A]] = Gen.identifier map (InputKey[A](_)) + def genSettingKey[A: Manifest]: Gen[SettingKey[A]] = Gen.identifier map (SettingKey[A](_)) + def genTaskKey[A: Manifest]: Gen[TaskKey[A]] = Gen.identifier map (TaskKey[A](_)) - implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = - withScope(Gen.identifier map (SettingKey[A](_))) + implicit def arbInputKey[A: Manifest]: Arbitrary[InputKey[A]] = withScope(genInputKey[A]) + implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = withScope(genSettingKey[A]) + implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = withScope(genTaskKey[A]) - implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = - withScope(Gen.identifier map (TaskKey[A](_))) - - implicit def arbKey[A: Manifest]: Arbitrary[Key[_]] = Arbitrary { - Gen.frequency[Key[_]]( - 15 -> arbitrary[InputKey[A]], // 15,431 - 20 -> arbitrary[SettingKey[A]], // 19,645 - 23 -> arbitrary[TaskKey[A]], // 22,867 - ) + object WithoutScope { + implicit def arbInputKey[A: Manifest]: Arbitrary[InputKey[A]] = Arbitrary(genInputKey[A]) + implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = Arbitrary(genSettingKey[A]) + implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = Arbitrary(genTaskKey[A]) } implicit def arbScopeAxis[A: Arbitrary]: Arbitrary[ScopeAxis[A]] = @@ -128,27 +125,32 @@ object CustomEquality { } } - def expectValue[A: Eq](expected: A)(x: A) = x.toString |: (expected =? x) + def expectValue[A: Eq](expected: A)(x: A) = expected =? x } import CustomEquality._ object SlashSyntaxSpec extends Properties("SlashSyntax") with SlashSyntax { + type Key[K] = Scoped.ScopingSetting[K] with Scoped + property("Global / key == key in Global") = { def check[K <: Key[K]: Arbitrary] = forAll((k: K) => expectValue(k in Global)(Global / k)) check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] } + property("Reference / key == key in Reference") = { def check[K <: Key[K]: Arbitrary] = forAll((r: Reference, k: K) => expectValue(k in r)(r / k)) check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] } + property("Reference / Config / key == key in Reference in Config") = { def check[K <: Key[K]: Arbitrary] = forAll((r: Reference, c: ConfigKey, k: K) => expectValue(k in r in c)(r / c / k)) check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] } - property("Reference / task / key == key in Reference in task") = { + + property("Reference / task.key / key == key in Reference in task") = { def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = - forAll((r: Reference, t: K, k: K) => expectValue(k in (r, t))(r / t / k)) + forAll((r: Reference, t: T, k: K) => expectValue(k in (r, t))(r / t.key / k)) (true && check[InputKey[String], InputKey[String]] && check[InputKey[String], SettingKey[String]] @@ -161,9 +163,11 @@ object SlashSyntaxSpec extends Properties("SlashSyntax") with SlashSyntax { && check[TaskKey[String], TaskKey[String]] ) } - property("Reference / Config / task / key == key in Reference in Config in task") = { + + property("Reference / task / key ~= key in Reference in task") = { + import WithoutScope._ def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = - forAll((r: Reference, c: ConfigKey, t: K, k: K) => expectValue(k in (r, c, t))(r / c / t / k)) + forAll((r: Reference, t: T, k: K) => expectValue(k in (r, t))(r / t / k)) (true && check[InputKey[String], InputKey[String]] && check[InputKey[String], SettingKey[String]] @@ -176,14 +180,49 @@ object SlashSyntaxSpec extends Properties("SlashSyntax") with SlashSyntax { && check[TaskKey[String], TaskKey[String]] ) } + + property("Reference / Config / task.key / key == key in Reference in Config in task") = { + def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = + forAll((r: Reference, c: ConfigKey, t: T, k: K) => expectValue(k in (r, c, t))(r / c / t.key / k)) + (true + && check[InputKey[String], InputKey[String]] + && check[InputKey[String], SettingKey[String]] + && check[InputKey[String], TaskKey[String]] + && check[SettingKey[String], InputKey[String]] + && check[SettingKey[String], SettingKey[String]] + && check[SettingKey[String], TaskKey[String]] + && check[TaskKey[String], InputKey[String]] + && check[TaskKey[String], SettingKey[String]] + && check[TaskKey[String], TaskKey[String]] + ) + } + + property("Reference / Config / task / key ~= key in Reference in Config in task") = { + import WithoutScope._ + def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = + forAll((r: Reference, c: ConfigKey, t: T, k: K) => expectValue(k in (r, c, t))(r / c / t / k)) + (true + && check[InputKey[String], InputKey[String]] + && check[InputKey[String], SettingKey[String]] + && check[InputKey[String], TaskKey[String]] + && check[SettingKey[String], InputKey[String]] + && check[SettingKey[String], SettingKey[String]] + && check[SettingKey[String], TaskKey[String]] + && check[TaskKey[String], InputKey[String]] + && check[TaskKey[String], SettingKey[String]] + && check[TaskKey[String], TaskKey[String]] + ) + } + property("Config / key == key in Config") = { def check[K <: Key[K]: Arbitrary] = forAll((c: ConfigKey, k: K) => expectValue(k in c)(c / k)) check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] } - property("Config / task / key == key in Config in task") = { + + property("Config / task.key / key == key in Config in task") = { def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = - forAll((c: ConfigKey, t: K, k: K) => expectValue(k in c in t)(c / t / k)) + forAll((c: ConfigKey, t: T, k: K) => expectValue(k in c in t)(c / t.key / k)) (true && check[InputKey[String], InputKey[String]] && check[InputKey[String], SettingKey[String]] @@ -196,9 +235,11 @@ object SlashSyntaxSpec extends Properties("SlashSyntax") with SlashSyntax { && check[TaskKey[String], TaskKey[String]] ) } - property("task / key == key in task") = { + + property("Config / task / key ~= key in Config in task") = { + import WithoutScope._ def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = - forAll((t: K, k: K) => expectValue(k in t)(t / k)) + forAll((c: ConfigKey, t: T, k: K) => expectValue(k in c in t)(c / t / k)) (true && check[InputKey[String], InputKey[String]] && check[InputKey[String], SettingKey[String]] @@ -209,36 +250,74 @@ object SlashSyntaxSpec extends Properties("SlashSyntax") with SlashSyntax { && check[TaskKey[String], InputKey[String]] && check[TaskKey[String], SettingKey[String]] && check[TaskKey[String], TaskKey[String]] - ) + ) } + + property("task.key / key == key in task") = { + def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = + forAll((t: T, k: K) => expectValue(k in t)(t.key / k)) + (true + && check[InputKey[String], InputKey[String]] + && check[InputKey[String], SettingKey[String]] + && check[InputKey[String], TaskKey[String]] + && check[SettingKey[String], InputKey[String]] + && check[SettingKey[String], SettingKey[String]] + && check[SettingKey[String], TaskKey[String]] + && check[TaskKey[String], InputKey[String]] + && check[TaskKey[String], SettingKey[String]] + && check[TaskKey[String], TaskKey[String]] + ) + } + + property("task / key ~= key in task") = { + import WithoutScope._ + def check[T <: Key[T]: Arbitrary, K <: Key[K]: Arbitrary] = + forAll((t: T, k: K) => expectValue(k in t)(t / k)) + (true + && check[InputKey[String], InputKey[String]] + && check[InputKey[String], SettingKey[String]] + && check[InputKey[String], TaskKey[String]] + && check[SettingKey[String], InputKey[String]] + && check[SettingKey[String], SettingKey[String]] + && check[SettingKey[String], TaskKey[String]] + && check[TaskKey[String], InputKey[String]] + && check[TaskKey[String], SettingKey[String]] + && check[TaskKey[String], TaskKey[String]] + ) + } + property("Scope / key == key in Scope") = { def check[K <: Key[K]: Arbitrary] = forAll((s: Scope, k: K) => expectValue(k in s)(s / k)) check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] } + property("Reference? / key == key in ThisScope.copy(..)") = { def check[K <: Key[K]: Arbitrary] = forAll((r: ScopeAxis[Reference], k: K) => expectValue(k in ThisScope.copy(project = r))(r / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] } + property("Reference? / ConfigKey? / key == key in ThisScope.copy(..)") = { def check[K <: Key[K]: Arbitrary] = forAll((r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], k: K) => expectValue(k in ThisScope.copy(project = r, config = c))(r / c / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] } + // property("Reference? / AttributeKey? / key == key in ThisScope.copy(..)") = { // def check[K <: Key[K]: Arbitrary] = // forAll( // (r: ScopeAxis[Reference], t: ScopeAxis[AttributeKey[_]], k: K) => // expectValue(k in ThisScope.copy(project = r, task = t))(r / t / k)) -// check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] +// check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] // } + property("Reference? / ConfigKey? / AttributeKey? / key == key in ThisScope.copy(..)") = { def check[K <: Key[K]: Arbitrary] = forAll( (r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], t: ScopeAxis[AttributeKey[_]], k: K) => expectValue(k in ThisScope.copy(project = r, config = c, task = t))(r / c / t / k)) - check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] + check[InputKey[String]] && check[SettingKey[String]] && check[TaskKey[String]] } } diff --git a/main-settings/src/test/scala/sbt/SlashSyntaxTest.scala b/main-settings/src/test/scala/sbt/SlashSyntaxTest.scala index f7a1ae065..31235f79d 100644 --- a/main-settings/src/test/scala/sbt/SlashSyntaxTest.scala +++ b/main-settings/src/test/scala/sbt/SlashSyntaxTest.scala @@ -7,7 +7,9 @@ package sbt.test -import sbt.Def.{ Setting, settingKey, taskKey } +import java.io.File +import sjsonnew._, BasicJsonProtocol._ +import sbt.Def.{ Setting, inputKey, settingKey, taskKey } import sbt.Scope.Global import sbt.librarymanagement.ModuleID import sbt.librarymanagement.syntax._ @@ -23,9 +25,15 @@ object SlashSyntaxTest extends sbt.SlashSyntax { val console = taskKey[Unit]("") val libraryDependencies = settingKey[Seq[ModuleID]]("") val name = settingKey[String]("") + val run = inputKey[Unit]("") val scalaVersion = settingKey[String]("") val scalacOptions = taskKey[Seq[String]]("") + val foo = taskKey[Int]("") + val bar = taskKey[Int]("") + val baz = inputKey[Unit]("") + val buildInfo = taskKey[Seq[File]]("") + val uTest = "com.lihaoyi" %% "utest" % "0.5.3" Seq[Setting[_]]( @@ -34,8 +42,23 @@ object SlashSyntaxTest extends sbt.SlashSyntax { console / scalacOptions += "-deprecation", Compile / console / scalacOptions += "-Ywarn-numeric-widen", projA / Compile / console / scalacOptions += "-feature", + Zero / name := "foo", Zero / Zero / name := "foo", Zero / Zero / Zero / name := "foo", + Test / bar := 1, + Test / foo := (Test / bar).value + 1, + Compile / foo := { + (Compile / bar).previous.getOrElse(1) + }, + Compile / bar := { + (Compile / foo).previous.getOrElse(2) + }, + Test / buildInfo := Nil, + baz := { + val _ = (Test / buildInfo).taskValue + (Compile / run).evaluated + }, + foo := (Test / bar).value + 1, libraryDependencies += uTest % Test, ) } diff --git a/sbt/src/sbt-test/project/unified/build.sbt b/sbt/src/sbt-test/project/unified/build.sbt index 91f772197..7f456b66c 100644 --- a/sbt/src/sbt-test/project/unified/build.sbt +++ b/sbt/src/sbt-test/project/unified/build.sbt @@ -1,8 +1,14 @@ import sbt.internal.CommandStrings.{ inspectBrief, inspectDetailed } import sbt.internal.Inspect +import sjsonnew._, BasicJsonProtocol._ val uTest = "com.lihaoyi" %% "utest" % "0.5.3" +val foo = taskKey[Int]("") +val bar = taskKey[Int]("") +val baz = inputKey[Unit]("") +val buildInfo = taskKey[Seq[File]]("The task that generates the build info.") + lazy val root = (project in file(".")) .settings( Global / cancelable := true, @@ -10,9 +16,24 @@ lazy val root = (project in file(".")) console / scalacOptions += "-deprecation", Compile / console / scalacOptions += "-Ywarn-numeric-widen", projA / Compile / console / scalacOptions += "-feature", + Zero / name := "foo", Zero / Zero / name := "foo", Zero / Zero / Zero / name := "foo", + Test / bar := 1, + Test / foo := (Test / bar).value + 1, + Compile / foo := { + (Compile / bar).previous.getOrElse(1) + }, + Compile / bar := { + (Compile / foo).previous.getOrElse(2) + }, + Test / buildInfo := Nil, + baz := { + val x = (Test / buildInfo).taskValue + (Compile / run).evaluated + }, + libraryDependencies += uTest % Test, testFrameworks += new TestFramework("utest.runner.Framework"),