From 8c76375eab0a2fd37fee669d7b13222b637403c9 Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Mon, 17 Aug 2026 23:04:57 -0700 Subject: [PATCH] [2.x] fix: Name the platform in CrossVersion(module, scalaModuleInfo) (#9620) **Problem** CrossVersion(module, scalaModuleInfo) is handed everything needed to name an artifact, including ScalaModuleInfo.platform, and the name it returns is what a caller publishes or resolves under. Nothing covers what it does with the platform. **Solution** Name the artifact in full: platform suffix before cross suffix, matching the coordinate (sbt/sbt#9117), through addPlatformSuffix, which already knows that jvm contributes no suffix. Generated-by: Claude Opus 5 --- .../librarymanagement/CrossVersionExtra.scala | 10 +++++- .../librarymanagement/CrossVersionTest.scala | 36 +++++++++++++++++++ .../librarymanagement/IvyActions.scala | 14 ++------ .../scala/sbt/internal/PomGenerator.scala | 7 +--- 4 files changed, 48 insertions(+), 19 deletions(-) diff --git a/lm-core/src/main/scala/sbt/librarymanagement/CrossVersionExtra.scala b/lm-core/src/main/scala/sbt/librarymanagement/CrossVersionExtra.scala index aafc4a1c2..7fe5ed64b 100644 --- a/lm-core/src/main/scala/sbt/librarymanagement/CrossVersionExtra.scala +++ b/lm-core/src/main/scala/sbt/librarymanagement/CrossVersionExtra.scala @@ -139,9 +139,17 @@ private[librarymanagement] abstract class CrossVersionFunctions { append(c.prefix + compat + c.suffix) } - /** Constructs the cross-version function defined by `module` and `is`, if one is configured. */ + /** + * Constructs the cross-version function defined by `module` and `is`, if one is configured. + * + * The function names the artifact in full, platform suffix before cross suffix, matching the + * coordinate (sbt/sbt#9117). On sbt 1 the platform was a prefix inside the CrossVersion itself, + * so callers rebuilding a name from it got the platform for free; sbt 2 keeps the platform in a + * field of its own, and this is where the two are put back together. + */ def apply(module: ModuleID, is: ScalaModuleInfo): Option[String => String] = CrossVersion(module.crossVersion, is.scalaFullVersion, is.scalaBinaryVersion) + .map(cross => name => cross(addPlatformSuffix(name, module.platformOpt, is.platform))) /** Constructs the cross-version function defined by `module` and `is`, if one is configured. */ def apply(module: ModuleID, is: Option[ScalaModuleInfo]): Option[String => String] = diff --git a/lm-core/src/test/scala/sbt/librarymanagement/CrossVersionTest.scala b/lm-core/src/test/scala/sbt/librarymanagement/CrossVersionTest.scala index c0ccf4a63..3bb8b59c4 100644 --- a/lm-core/src/test/scala/sbt/librarymanagement/CrossVersionTest.scala +++ b/lm-core/src/test/scala/sbt/librarymanagement/CrossVersionTest.scala @@ -2,6 +2,7 @@ package sbt.librarymanagement import sbt.internal.librarymanagement.UnitSpec import CrossVersion.* +import sbt.librarymanagement.syntax.* import scala.annotation.nowarn class CrossVersionTest extends UnitSpec { @@ -413,4 +414,39 @@ class CrossVersionTest extends UnitSpec { "artefact_3" ) } + + private def scalaInfo(platform: Option[String]) = ScalaModuleInfo( + "2.13.15", + "2.13", + Vector.empty, + true, + false, + true, + "org.scala-lang", + Vector.empty, + platform, + ) + + private def named(module: ModuleID, platform: Option[String]) = + CrossVersion(module, scalaInfo(platform)).map(_(module.name)) + + "CrossVersion(module, scalaModuleInfo)" should "name a JVM artifact with the cross suffix" in { + named("com.example" %% "foo" % "1.0", Some("jvm")) shouldBe Some("foo_2.13") + } + it should "put the platform suffix before the cross suffix" in { + named("com.example" %% "foo" % "1.0", Some("sjs1")) shouldBe Some("foo_sjs1_2.13") + } + it should "take the platform from the module over the project" in { + val m = ("com.example" %% "foo" % "1.0").platform("native0.5") + named(m, Some("sjs1")) shouldBe Some("foo_native0.5_2.13") + } + it should "add no platform suffix when there is no platform" in { + named("com.example" %% "foo" % "1.0", None) shouldBe Some("foo_2.13") + } + it should "name nothing when the module is not cross-versioned" in { + named( + ("com.example" % "foo" % "1.0").withCrossVersion(CrossVersion.disabled), + Some("sjs1") + ) shouldBe None + } } diff --git a/lm-ivy/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala b/lm-ivy/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala index bf726718c..0048a79eb 100644 --- a/lm-ivy/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala +++ b/lm-ivy/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala @@ -234,18 +234,8 @@ object IvyActions { } private def crossVersionMap(moduleSettings: ModuleSettings): Option[String => String] = moduleSettings match { - case i: InlineConfiguration => - // Platform suffix before cross suffix, matching the coordinate (sbt/sbt#9117). - CrossVersion(i.module, i.scalaModuleInfo).map { fn => (name: String) => - fn( - CrossVersion.addPlatformSuffix( - name, - i.module.platformOpt, - i.scalaModuleInfo.flatMap(_.platform) - ) - ) - } - case _ => None + case i: InlineConfiguration => CrossVersion(i.module, i.scalaModuleInfo) + case _ => None } def mapArtifacts( module: ModuleDescriptor, diff --git a/main/src/main/scala/sbt/internal/PomGenerator.scala b/main/src/main/scala/sbt/internal/PomGenerator.scala index 5744194c0..5fc7c1ed8 100644 --- a/main/src/main/scala/sbt/internal/PomGenerator.scala +++ b/main/src/main/scala/sbt/internal/PomGenerator.scala @@ -61,14 +61,9 @@ private[sbt] object PomGenerator: private def crossVersionDep(dep: ModuleID, scalaInfo: Option[ScalaModuleInfo]): ModuleID = - // Platform suffix before cross suffix, matching the coordinate (sbt/sbt#9117). - val base = dep.crossVersion match - case _: Disabled => dep.name - case _ => - CrossVersion.addPlatformSuffix(dep.name, dep.platformOpt, scalaInfo.flatMap(_.platform)) val crossFn = CrossVersion(dep, scalaInfo) val crossDep = crossFn match - case Some(fn) => dep.withName(fn(base)).withCrossVersion(CrossVersion.disabled) + case Some(fn) => dep.withName(fn(dep.name)).withCrossVersion(CrossVersion.disabled) case None => dep if crossDep.exclusions.isEmpty || scalaInfo.isEmpty then crossDep else