From 12d46e3fce736033b2099d53b849e0224bfc0886 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 23 Jan 2021 15:38:53 -0500 Subject: [PATCH] Make ModuleID#cross(...) preserve existing prefix Fixes https://github.com/sbt/sbt/issues/6280 sbt-platform-deps implements `%%%` operator in https://github.com/portable-scala/sbt-platform-deps/blob/v1.0.0/src/main/scala/org/portablescala/sbtplatformdeps/PlatformDepsBuilders.scala#L36-L43 by setting the prefix field on `sbt.librarymanagement.Binary(...)`. Currently `.cross(...)` would wipe this out, so `%%%` and `cross(CrossVersion.for3Use2_13)` do not compose. This changes the implementation of `.cross(...)` so it can be chained together and it will try to preserve the prefix (and suffix) values from the existing `crossVersion` values. This should fix the 2.13-3.x sandwich on Scala.JS and Scala Native. --- .../librarymanagement/CrossVersionExtra.scala | 22 +++++++-- .../sbt/librarymanagement/ModuleIDExtra.scala | 24 +++++++++- .../sbt/librarymanagement/ModuleIdTest.scala | 46 +++++++++++++------ project/Dependencies.scala | 2 +- 4 files changed, 73 insertions(+), 21 deletions(-) diff --git a/core/src/main/scala/sbt/librarymanagement/CrossVersionExtra.scala b/core/src/main/scala/sbt/librarymanagement/CrossVersionExtra.scala index 6e8ff00d6..a37a52d7c 100644 --- a/core/src/main/scala/sbt/librarymanagement/CrossVersionExtra.scala +++ b/core/src/main/scala/sbt/librarymanagement/CrossVersionExtra.scala @@ -17,14 +17,10 @@ private[librarymanagement] abstract class CrossVersionFunctions { final val Constant = sbt.librarymanagement.Constant final val Full = sbt.librarymanagement.Full final val Patch = sbt.librarymanagement.Patch - final val For3Use2_13 = sbt.librarymanagement.For3Use2_13 - final val For2_13Use3 = sbt.librarymanagement.For2_13Use3 type Binary = sbt.librarymanagement.Binary type Constant = sbt.librarymanagement.Constant type Full = sbt.librarymanagement.Full type Patch = sbt.librarymanagement.Patch - type For3Use2_13 = sbt.librarymanagement.For3Use2_13 - type For2_13Use3 = sbt.librarymanagement.For2_13Use3 /** The first `major.minor` Scala version that the Scala binary version should be used for cross-versioning instead of the full version. */ val TransitionScalaVersion = CrossVersionUtil.TransitionScalaVersion @@ -87,6 +83,24 @@ private[librarymanagement] abstract class CrossVersionFunctions { */ def for2_13Use3With(prefix: String, suffix: String): CrossVersion = For2_13Use3(prefix, suffix) + private[sbt] def getPrefixSuffix(value: CrossVersion): (String, String) = + value match { + case (_: Disabled | _: Constant | _: Patch) => ("", "") + case b: Binary => (b.prefix, b.suffix) + case f: Full => (f.prefix, f.suffix) + case c: For3Use2_13 => (c.prefix, c.suffix) + case c: For2_13Use3 => (c.prefix, c.suffix) + } + + private[sbt] def setPrefixSuffix(value: CrossVersion, p: String, s: String): CrossVersion = + value match { + case (_: Disabled | _: Constant | _: Patch) => value + case b: Binary => b.withPrefix(p).withSuffix(s) + case f: Full => f.withPrefix(p).withSuffix(s) + case c: For3Use2_13 => c.withPrefix(p).withSuffix(s) + case c: For2_13Use3 => c.withPrefix(p).withSuffix(s) + } + private[sbt] def patchFun(fullVersion: String): String = { val BinCompatV = """(\d+)\.(\d+)\.(\d+)(-\w+)??-bin(-.*)?""".r fullVersion match { diff --git a/core/src/main/scala/sbt/librarymanagement/ModuleIDExtra.scala b/core/src/main/scala/sbt/librarymanagement/ModuleIDExtra.scala index f98f46aae..c5191467c 100644 --- a/core/src/main/scala/sbt/librarymanagement/ModuleIDExtra.scala +++ b/core/src/main/scala/sbt/librarymanagement/ModuleIDExtra.scala @@ -71,8 +71,28 @@ private[librarymanagement] abstract class ModuleIDExtra { ) def cross(v: Boolean): ModuleID = cross(if (v) CrossVersion.binary else Disabled()) - /** Specifies the cross-version behavior for this module. See [CrossVersion] for details.*/ - def cross(v: CrossVersion): ModuleID = withCrossVersion(v) + /** + * Specifies the cross-version behavior for this module. See [CrossVersion] for details. + * Unlike `withCrossVersion(...)`, `cross(...)` will preserve the prefix and suffix + * values from the existing `crossVersion` value. + * + * {{{ + * ModuleID("com.example", "foo", "1.0") + * .cross(CrossVersion.binaryWith("sjs1_", "")) + * .cross(CrossVersion.for3Use2_13) + * }}} + * + * This allows `.cross(...)` to play well with `%%%` operator provided by sbt-platform-deps. + */ + def cross(v: CrossVersion): ModuleID = + withCrossVersion(CrossVersion.getPrefixSuffix(this.crossVersion) match { + case ("", "") => v + case (prefix, suffix) => + CrossVersion.getPrefixSuffix(v) match { + case ("", "") => CrossVersion.setPrefixSuffix(v, prefix, suffix) + case _ => v + } + }) // () required for chaining /** Do not follow dependencies of this module. Synonym for `intransitive`.*/ diff --git a/core/src/test/scala/sbt/librarymanagement/ModuleIdTest.scala b/core/src/test/scala/sbt/librarymanagement/ModuleIdTest.scala index 1698f317a..1c962aac2 100644 --- a/core/src/test/scala/sbt/librarymanagement/ModuleIdTest.scala +++ b/core/src/test/scala/sbt/librarymanagement/ModuleIdTest.scala @@ -1,31 +1,49 @@ package sbt.librarymanagement -import sbt.internal.librarymanagement.UnitSpec import sjsonnew.support.scalajson.unsafe.{ Converter, CompactPrinter, Parser } -class ModuleIdTest extends UnitSpec { - val expectedJson = - """{"organization":"com.acme","name":"foo","revision":"1","isChanging":false,"isTransitive":true,"isForce":false,"explicitArtifacts":[],"inclusions":[],"exclusions":[],"extraAttributes":{},"crossVersion":{"type":"Disabled"}}""" - "Module Id" should "return cross-disabled module id as equal to a copy" in { - ModuleID("com.acme", "foo", "1") shouldBe ModuleID("com.acme", "foo", "1") +object ModuleIdTest extends verify.BasicTestSuite { + test("Module Id should return cross-disabled module id as equal to a copy") { + assert(ModuleID("com.acme", "foo", "1") == ModuleID("com.acme", "foo", "1")) } - it should "return cross-full module id as equal to a copy" in { - (ModuleID("com.acme", "foo", "1") cross CrossVersion.full) shouldBe - (ModuleID("com.acme", "foo", "1") cross CrossVersion.full) + + test("it should return cross-full module id as equal to a copy") { + assert( + ModuleID("com.acme", "foo", "1").cross(CrossVersion.full) == + ModuleID("com.acme", "foo", "1").cross(CrossVersion.full) + ) } - it should "return cross-binary module id as equal to a copy" in { - (ModuleID("com.acme", "foo", "1") cross CrossVersion.binary) shouldBe - (ModuleID("com.acme", "foo", "1") cross CrossVersion.binary) + + test("it should return cross-binary module id as equal to a copy") { + assert( + ModuleID("com.acme", "foo", "1").cross(CrossVersion.binary) == + ModuleID("com.acme", "foo", "1").cross(CrossVersion.binary) + ) } - it should "format itself into JSON" in { + + test("it should format itself into JSON") { import LibraryManagementCodec._ val json = Converter.toJson(ModuleID("com.acme", "foo", "1")).get assert(CompactPrinter(json) == expectedJson) } - it should "thaw back from JSON" in { + + test("it should thaw back from JSON") { import LibraryManagementCodec._ val json = Parser.parseUnsafe(expectedJson) val m = Converter.fromJsonUnsafe[ModuleID](json) assert(m == ModuleID("com.acme", "foo", "1")) } + + test("cross(...) should compose prefix with the existing value") { + assert( + ModuleID("com.acme", "foo", "1") + .cross(CrossVersion.binaryWith("sjs1_", "")) + .cross(CrossVersion.for3Use2_13) + == + ModuleID("com.acme", "foo", "1").cross(CrossVersion.for3Use2_13With("sjs1_", "")) + ) + } + + def expectedJson = + """{"organization":"com.acme","name":"foo","revision":"1","isChanging":false,"isTransitive":true,"isForce":false,"explicitArtifacts":[],"inclusions":[],"exclusions":[],"extraAttributes":{},"crossVersion":{"type":"Disabled"}}""" } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 4dcecc8c6..6e8de8452 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -53,7 +53,7 @@ object Dependencies { val scalaCompiler = Def.setting { "org.scala-lang" % "scala-compiler" % scalaVersion.value } val scalaXml = "org.scala-lang.modules" %% "scala-xml" % "1.2.0" val scalaTest = "org.scalatest" %% "scalatest" % "3.2.0" - val scalaVerify = "com.eed3si9n.verify" %% "verify" % "0.1.0" + val scalaVerify = "com.eed3si9n.verify" %% "verify" % "1.0.0" val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.14.0" val sjsonnew = Def.setting { "com.eed3si9n" %% "sjson-new-core" % contrabandSjsonNewVersion.value