diff --git a/build.sbt b/build.sbt index 2152c7b0f..61231fbd7 100644 --- a/build.sbt +++ b/build.sbt @@ -492,6 +492,10 @@ lazy val mainSettingsProj = (project in file("main-settings")) mimaSettings, mimaBinaryIssueFilters ++= Seq( exclude[DirectMissingMethodProblem]("sbt.Scope.display012StyleMasked"), + + // added a method to a sealed trait + exclude[InheritedNewAbstractMethodProblem]("sbt.Scoped.canEqual"), + exclude[InheritedNewAbstractMethodProblem]("sbt.ScopedTaskable.canEqual"), ), ) .configure( diff --git a/main-settings/src/main/scala/sbt/Structure.scala b/main-settings/src/main/scala/sbt/Structure.scala index a6e5d9f65..a855a6014 100644 --- a/main-settings/src/main/scala/sbt/Structure.scala +++ b/main-settings/src/main/scala/sbt/Structure.scala @@ -17,7 +17,18 @@ import sbt.Def.{ Initialize, KeyedInitialize, ScopedKey, Setting, setting } import std.TaskExtra.{ task => mktask, _ } /** An abstraction on top of Settings for build configuration and task definition. */ -sealed trait Scoped { def scope: Scope; val key: AttributeKey[_] } +sealed trait Scoped extends Equals { + def scope: Scope + val key: AttributeKey[_] + + override def equals(that: Any) = + (this eq that.asInstanceOf[AnyRef]) || (that match { + case that: Scoped => scope == that.scope && key == that.key && canEqual(that) + case _ => false + }) + + override def hashCode() = (scope, key).## +} /** A common type for SettingKey and TaskKey so that both can be used as inputs to tasks.*/ sealed trait ScopedTaskable[T] extends Scoped { @@ -95,6 +106,8 @@ sealed abstract class SettingKey[T] final def withRank(rank: Int): SettingKey[T] = SettingKey(AttributeKey.copyWithRank(key, rank)) + + def canEqual(that: Any): Boolean = that.isInstanceOf[SettingKey[_]] } /** @@ -163,6 +176,8 @@ sealed abstract class TaskKey[T] final def withRank(rank: Int): TaskKey[T] = TaskKey(AttributeKey.copyWithRank(key, rank)) + + def canEqual(that: Any): Boolean = that.isInstanceOf[TaskKey[_]] } /** @@ -195,6 +210,8 @@ sealed trait InputKey[T] final def withRank(rank: Int): InputKey[T] = InputKey(AttributeKey.copyWithRank(key, rank)) + + def canEqual(that: Any): Boolean = that.isInstanceOf[InputKey[_]] } /** Methods and types related to constructing settings, including keys, scopes, and initializations. */ diff --git a/main-settings/src/test/scala/sbt/BuildSettingsInstances.scala b/main-settings/src/test/scala/sbt/BuildSettingsInstances.scala new file mode 100644 index 000000000..3618fb841 --- /dev/null +++ b/main-settings/src/test/scala/sbt/BuildSettingsInstances.scala @@ -0,0 +1,135 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt.test + +import org.scalacheck.{ Test => _, _ }, Arbitrary.arbitrary, Gen._ + +import java.io.File +import sbt.io.IO +import sbt.{ Scope, ScopeAxis, Scoped, Select, This, Zero } +import sbt.{ + BuildRef, + LocalProject, + LocalRootProject, + ProjectRef, + Reference, + RootProject, + ThisBuild, + ThisProject +} +import sbt.ConfigKey +import sbt.librarymanagement.syntax._ +import sbt.{ InputKey, SettingKey, TaskKey } +import sbt.internal.util.{ AttributeKey, AttributeMap } + +object BuildSettingsInstances { + val genFile: Gen[File] = Gen.oneOf(new File("."), new File("/tmp")) // for now.. + + implicit val arbBuildRef: Arbitrary[BuildRef] = Arbitrary(genFile map (f => BuildRef(IO toURI f))) + + implicit val arbProjectRef: Arbitrary[ProjectRef] = + Arbitrary(for (f <- genFile; id <- Gen.identifier) yield ProjectRef(f, id)) + + implicit val arbLocalProject: Arbitrary[LocalProject] = + Arbitrary(arbitrary[String] map LocalProject) + + implicit val arbRootProject: Arbitrary[RootProject] = Arbitrary(genFile map (RootProject(_))) + + implicit val arbReference: Arbitrary[Reference] = Arbitrary { + Gen.frequency( + 96 -> arbitrary[BuildRef], + 10271 -> ThisBuild, + 325 -> LocalRootProject, + 2283 -> arbitrary[ProjectRef], + 299 -> ThisProject, + 436 -> arbitrary[LocalProject], + 1133 -> arbitrary[RootProject], + ) + } + + implicit def arbConfigKey: Arbitrary[ConfigKey] = Arbitrary { + Gen.frequency( + 2 -> const[ConfigKey](Compile), + 2 -> const[ConfigKey](Test), + 1 -> const[ConfigKey](Runtime), + 1 -> const[ConfigKey](IntegrationTest), + 1 -> const[ConfigKey](Provided), + ) + } + + implicit def arbAttrKey[A: Manifest]: Arbitrary[AttributeKey[_]] = + Arbitrary(Gen.identifier map (AttributeKey[A](_))) + + implicit val arbAttributeMap: Arbitrary[AttributeMap] = Arbitrary { + Gen.frequency( + 20 -> AttributeMap.empty, + 1 -> { + for (name <- Gen.identifier; isModule <- arbitrary[Boolean]) + yield + AttributeMap.empty + .put(AttributeKey[String]("name"), name) + .put(AttributeKey[Boolean]("isModule"), isModule) + } + ) + } + + implicit def arbScopeAxis[A: Arbitrary]: Arbitrary[ScopeAxis[A]] = + Arbitrary(Gen.oneOf[ScopeAxis[A]](This, Zero, arbitrary[A] map (Select(_)))) + + implicit def arbScope: Arbitrary[Scope] = Arbitrary( + for { + r <- arbitrary[ScopeAxis[Reference]] + c <- arbitrary[ScopeAxis[ConfigKey]] + t <- arbitrary[ScopeAxis[AttributeKey[_]]] + e <- arbitrary[ScopeAxis[AttributeMap]] + } yield Scope(r, c, t, e) + ) + + type Key = K forSome { type K <: Scoped.ScopingSetting[K] with Scoped } + + final case class Label(value: String) + val genLabel: Gen[Label] = Gen.identifier map Label + implicit def arbLabel: Arbitrary[Label] = Arbitrary(genLabel) + + def genInputKey[A: Manifest]: Gen[InputKey[A]] = genLabel map (x => InputKey[A](x.value)) + def genSettingKey[A: Manifest]: Gen[SettingKey[A]] = genLabel map (x => SettingKey[A](x.value)) + def genTaskKey[A: Manifest]: Gen[TaskKey[A]] = genLabel map (x => TaskKey[A](x.value)) + + def withScope[K <: Scoped.ScopingSetting[K]](keyGen: Gen[K]): Arbitrary[K] = Arbitrary { + Gen.frequency( + 5 -> keyGen, + 1 -> (for (key <- keyGen; scope <- arbitrary[Scope]) yield key in scope) + ) + } + + 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 arbKey[A: Manifest]( + implicit + arbInputKey: Arbitrary[InputKey[A]], + arbSettingKey: Arbitrary[SettingKey[A]], + arbTaskKey: Arbitrary[TaskKey[A]], + ): Arbitrary[Key] = Arbitrary { + def convert[T](g: Gen[T]) = g.asInstanceOf[Gen[Key]] + Gen.frequency( + 15431 -> convert(arbitrary[InputKey[A]]), + 19645 -> convert(arbitrary[SettingKey[A]]), + 22867 -> convert(arbitrary[TaskKey[A]]), + ) + } + + 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 arbScoped[A: Manifest]: Arbitrary[Scoped] = Arbitrary(arbitrary[Key]) +} diff --git a/main-settings/src/test/scala/sbt/ScopedSpec.scala b/main-settings/src/test/scala/sbt/ScopedSpec.scala new file mode 100644 index 000000000..5992403b0 --- /dev/null +++ b/main-settings/src/test/scala/sbt/ScopedSpec.scala @@ -0,0 +1,145 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt.test + +import org.scalacheck._, Prop._, util.Pretty + +import sbt.internal.util.AttributeKey +import sbt.util.NoJsonWriter +import sbt.{ InputTask, Scope, Task } +import sbt.{ InputKey, Scoped, SettingKey, TaskKey } + +import BuildSettingsInstances._ + +object ScopedSpec extends Properties("Scoped") { + val intManifest = manifest[Int] + val stringManifest = manifest[String] + + implicit val arbManifest: Arbitrary[Manifest[_]] = + Arbitrary(Gen.oneOf(intManifest, stringManifest)) + + property("setting keys are structurally equal") = { + forAll { (label: Label, manifest: Manifest[_], scope: Scope) => + val k1 = settingKey(label, manifest, scope) + val k2 = settingKey(label, manifest, scope) + expectEq(k1, k2) + } + } + + property("task keys are structurally equal") = { + forAll { (label: Label, manifest: Manifest[_], scope: Scope) => + val k1 = taskKey(label, manifest, scope) + val k2 = taskKey(label, manifest, scope) + expectEq(k1, k2) + } + } + + property("input keys are structurally equal") = { + forAll { (label: Label, manifest: Manifest[_], scope: Scope) => + val k1 = inputKey(label, manifest, scope) + val k2 = inputKey(label, manifest, scope) + expectEq(k1, k2) + } + } + + property("different key types are not equal") = { + forAll { (label: Label, manifest: Manifest[_], scope: Scope) => + val settingKey1 = settingKey(label, manifest, scope) + val taskKey1 = taskKey(label, manifest, scope) + val inputKey1 = inputKey(label, manifest, scope) + + all( + expectNe(settingKey1, taskKey1), + expectNe(settingKey1, inputKey1), + expectNe(taskKey1, inputKey1), + ) + } + } + + property("different key types, with the same manifest, are not equal") = { + forAll { (label: Label, scope: Scope) => + val prop1 = { + val manifest1 = manifest[Task[String]] + val attrKey = attributeKey(label, manifest1) + val k1 = SettingKey(attrKey) in scope + val k2 = TaskKey(attrKey) in scope + expectNeSameManifest(k1, k2) + } + + val prop2 = { + val manifest1 = manifest[InputTask[String]] + val attrKey = attributeKey(label, manifest1) + val k1 = SettingKey(attrKey) in scope + val k2 = InputKey(attrKey) in scope + expectNeSameManifest(k1, k2) + } + + all(prop1, prop2) + } + } + + /// + + def settingKey[A](label: Label, manifest: Manifest[A], scope: Scope): SettingKey[A] = { + val noJsonWriter = NoJsonWriter[A]() + SettingKey[A](label.value)(manifest, noJsonWriter) in scope + } + + def taskKey[A](label: Label, manifest: Manifest[A], s: Scope): TaskKey[A] = + TaskKey[A](label.value)(manifest) in s + + def inputKey[A](label: Label, manifest: Manifest[A], scope: Scope): InputKey[A] = + InputKey[A](label.value)(manifest) in scope + + def attributeKey[A](label: Label, manifest: Manifest[A]): AttributeKey[A] = { + val jsonWriter = NoJsonWriter[A]() + AttributeKey[A](label.value)(manifest, jsonWriter) + } + + /// + + def expectEq(k1: Scoped, k2: Scoped): Prop = + ?=(k1, k2) && ?=(k2, k1) map eqLabels(k1, k2) + + def expectNe(k1: Scoped, k2: Scoped): Prop = + !=(k1, k2) && !=(k2, k1) map eqLabels(k1, k2) + + def expectNeSameManifest(k1: Scoped, k2: Scoped) = { + all( + ?=(k1.key.manifest, k2.key.manifest), // sanity check the manifests are the same + expectNe(k1, k2), + ) + } + + def eqLabels(k1: Scoped, k2: Scoped): Prop.Result => Prop.Result = r => { + val eqLabel = k1.key.label == k2.key.label + val eqManifest = k1.key.manifest == k2.key.manifest + val eqScope = k1.scope == k2.scope + r.label(s"label equality: ${k1.key.label} == ${k2.key.label} : $eqLabel") + .label(s"manifest equality: ${k1.key.manifest} == ${k2.key.manifest} : $eqManifest") + .label(s"scope equality: ${k1.scope} == ${k2.scope} : $eqScope") + } + + def ?=[T](x: T, y: T)(implicit pp: T => Pretty): Prop = + if (x == y) proved + else + falsified :| { + val act = Pretty.pretty[T](x, Pretty.Params(0)) + val exp = Pretty.pretty[T](y, Pretty.Params(0)) + s"Expected $act to be equal to $exp" + } + + def !=[T](x: T, y: T)(implicit pp: T => Pretty): Prop = + if (x == y) falsified + else + proved :| { + val act = Pretty.pretty[T](x, Pretty.Params(0)) + val exp = Pretty.pretty[T](y, Pretty.Params(0)) + s"Expected $act to NOT be equal to $exp" + } +} diff --git a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala index e68d3ba0a..b3b77c71d 100644 --- a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala +++ b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala @@ -7,130 +7,15 @@ package sbt.test -import org.scalacheck.{ Test => _, _ }, Arbitrary.arbitrary, Gen._, Prop._ +import org.scalacheck.{ Test => _, _ }, Prop._ -import java.io.File -import sbt.io.IO 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.{ Scope, ScopeAxis, Scoped }, Scope.{ Global, ThisScope } +import sbt.Reference import sbt.ConfigKey -import sbt.librarymanagement.syntax._ -import sbt.{ InputKey, SettingKey, TaskKey } -import sbt.internal.util.{ AttributeKey, AttributeMap } +import sbt.internal.util.AttributeKey -object BuildDSLInstances { - val genFile: Gen[File] = Gen.oneOf(new File("."), new File("/tmp")) // for now.. - - implicit val arbBuildRef: Arbitrary[BuildRef] = Arbitrary(genFile map (f => BuildRef(IO toURI f))) - - implicit val arbProjectRef: Arbitrary[ProjectRef] = - Arbitrary(for (f <- genFile; id <- Gen.identifier) yield ProjectRef(f, id)) - - implicit val arbLocalProject: Arbitrary[LocalProject] = - Arbitrary(arbitrary[String] map LocalProject) - - implicit val arbRootProject: Arbitrary[RootProject] = Arbitrary(genFile map (RootProject(_))) - - implicit val arbReference: Arbitrary[Reference] = Arbitrary { - Gen.frequency( - 96 -> arbitrary[BuildRef], - 10271 -> ThisBuild, - 325 -> LocalRootProject, - 2283 -> arbitrary[ProjectRef], - 299 -> ThisProject, - 436 -> arbitrary[LocalProject], - 1133 -> arbitrary[RootProject], - ) - } - - implicit def arbConfigKey: Arbitrary[ConfigKey] = Arbitrary { - Gen.frequency( - 2 -> const[ConfigKey](Compile), - 2 -> const[ConfigKey](Test), - 1 -> const[ConfigKey](Runtime), - 1 -> const[ConfigKey](IntegrationTest), - 1 -> const[ConfigKey](Provided), - ) - } - - implicit def arbAttrKey[A: Manifest]: Arbitrary[AttributeKey[_]] = - Arbitrary(Gen.identifier map (AttributeKey[A](_))) - - implicit val arbAttributeMap: Arbitrary[AttributeMap] = Arbitrary { - Gen.frequency( - 20 -> AttributeMap.empty, - 1 -> { - for (name <- Gen.identifier; isModule <- arbitrary[Boolean]) - yield - AttributeMap.empty - .put(AttributeKey[String]("name"), name) - .put(AttributeKey[Boolean]("isModule"), isModule) - } - ) - } - - implicit def arbScopeAxis[A: Arbitrary]: Arbitrary[ScopeAxis[A]] = - Arbitrary(Gen.oneOf[ScopeAxis[A]](This, Zero, arbitrary[A] map (Select(_)))) - - implicit def arbScope: Arbitrary[Scope] = Arbitrary( - for { - r <- arbitrary[ScopeAxis[Reference]] - c <- arbitrary[ScopeAxis[ConfigKey]] - t <- arbitrary[ScopeAxis[AttributeKey[_]]] - e <- arbitrary[ScopeAxis[AttributeMap]] - } yield Scope(r, c, t, e) - ) - - type Key = K forSome { type K <: Scoped.ScopingSetting[K] with Scoped } - - 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](_)) - - def withScope[K <: Scoped.ScopingSetting[K]](keyGen: Gen[K]): Arbitrary[K] = Arbitrary { - Gen.frequency( - 5 -> keyGen, - 1 -> (for (key <- keyGen; scope <- arbitrary[Scope]) yield key in scope) - ) - } - - 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 arbKey[A: Manifest]( - implicit - arbInputKey: Arbitrary[InputKey[A]], - arbSettingKey: Arbitrary[SettingKey[A]], - arbTaskKey: Arbitrary[TaskKey[A]], - ): Arbitrary[Key] = Arbitrary { - def convert[T](g: Gen[T]) = g.asInstanceOf[Gen[Key]] - Gen.frequency( - 15431 -> convert(arbitrary[InputKey[A]]), - 19645 -> convert(arbitrary[SettingKey[A]]), - 22867 -> convert(arbitrary[TaskKey[A]]), - ) - } - - 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 arbScoped[A: Manifest]: Arbitrary[Scoped] = Arbitrary(arbitrary[Key]) -} -import BuildDSLInstances._ +import BuildSettingsInstances._ object SlashSyntaxSpec extends Properties("SlashSyntax") with SlashSyntax { property("Global / key == key in Global") = { diff --git a/project/plugins.sbt b/project/plugins.sbt index 53ea90fb6..278929bd0 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,7 @@ scalaVersion := "2.12.4" scalacOptions ++= Seq("-feature", "-language:postfixOps") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.2.0") addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.5") addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.4.0") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2")