From fdccd8a5691ed906db21256515ca6af1757838dd Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:41:30 -0700 Subject: [PATCH] [2.x] Prefer local ScalaModuleInfo over global config (#9028) Otherwise, a user will never be able to download scala library at any other version. --- .../CoursierDependencyResolution.scala | 16 +- .../CoursierDependencyResolutionTests.scala | 143 ++++++++++++++++-- 2 files changed, 138 insertions(+), 21 deletions(-) diff --git a/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala b/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala index fdb562e5f..930ecd592 100644 --- a/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala +++ b/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala @@ -146,13 +146,15 @@ class CoursierDependencyResolution( } } - val soOpt = conf.scalaOrganization - .map(Organization(_)) - .orElse(module0.scalaModuleInfo.map(m => Organization(m.scalaOrganization))) + val soOpt = module0.scalaModuleInfo + .map(_.scalaOrganization) + .orElse(conf.scalaOrganization) + .map(Organization.apply) val so = soOpt.getOrElse(Organization("org.scala-lang")) - val sv = conf.scalaVersion - .orElse(module0.scalaModuleInfo.map(_.scalaFullVersion)) + val sv = module0.scalaModuleInfo + .map(_.scalaFullVersion) + .orElse(conf.scalaVersion) // FIXME Manage to do stuff below without a scala version? .getOrElse(scala.util.Properties.versionNumberString) @@ -273,11 +275,13 @@ class CoursierDependencyResolution( (coursier.Organization(strOrg), coursier.ModuleName(strName)) }.toSet + val autoScalaLib = + conf.autoScalaLibrary && module0.scalaModuleInfo.forall(_.overrideScalaVersion) val resolutionParams = ResolutionParams( dependencies = dependencies, fallbackDependencies = conf.fallbackDependencies, orderedConfigs = orderedConfigs, - autoScalaLibOpt = if (conf.autoScalaLibrary) Some((so, sv)) else None, + autoScalaLibOpt = if (autoScalaLib) Some((so, sv)) else None, mainRepositories = mainRepositories, parentProjectCache = Map.empty, interProjectDependencies = interProjectDependencies, diff --git a/lm-coursier/src/test/scala/lmcoursier/CoursierDependencyResolutionTests.scala b/lm-coursier/src/test/scala/lmcoursier/CoursierDependencyResolutionTests.scala index 462e434bb..ed258e8ab 100644 --- a/lm-coursier/src/test/scala/lmcoursier/CoursierDependencyResolutionTests.scala +++ b/lm-coursier/src/test/scala/lmcoursier/CoursierDependencyResolutionTests.scala @@ -2,16 +2,32 @@ package lmcoursier import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec -import sbt.librarymanagement.ModuleID -import sbt.librarymanagement.UpdateConfiguration -import sbt.librarymanagement.UnresolvedWarningConfiguration +import sbt.librarymanagement.* import sbt.util.Logger -import sbt.librarymanagement.ModuleInfo -import sbt.librarymanagement.ModuleDescriptorConfiguration -import sbt.librarymanagement.Configuration class CoursierDependencyResolutionTests extends AnyPropSpec with Matchers { + private val logger: Logger = new Logger { + def log(level: sbt.util.Level.Value, message: => String): Unit = + System.err.println(s"${level.id} $message") + def success(message: => String): Unit = + System.err.println(message) + def trace(t: => Throwable): Unit = + System.err.println(s"trace $t") + } + + private val conf211 = + CoursierConfiguration().withAutoScalaLibrary(true).withScalaVersion(Some("2.11.12")) + private val scalaModule212 = ModuleID("org.scala-lang", "scala-library", "2.12.21") + private val scalaModuleInfo213 = ScalaModuleInfo( + "2.13.18", + "2.13", + Vector.empty, + checkExplicit = false, + filterImplicit = false, + overrideScalaVersion = false + ) + property("missingOk from passed UpdateConfiguration") { val depRes = CoursierDependencyResolution(CoursierConfiguration().withAutoScalaLibrary(false)) @@ -27,15 +43,6 @@ class CoursierDependencyResolutionTests extends AnyPropSpec with Matchers { .withConfigurations(Vector(Configuration.of("Compile", "compile"))) val module = depRes.moduleDescriptor(desc) - val logger: Logger = new Logger { - def log(level: sbt.util.Level.Value, message: => String): Unit = - System.err.println(s"${level.id} $message") - def success(message: => String): Unit = - System.err.println(message) - def trace(t: => Throwable): Unit = - System.err.println(s"trace $t") - } - depRes .update(module, UpdateConfiguration(), UnresolvedWarningConfiguration(), logger) .fold(w => (), rep => sys.error(s"Expected resolution to fail, got report $rep")) @@ -50,4 +57,110 @@ class CoursierDependencyResolutionTests extends AnyPropSpec with Matchers { .fold(w => throw w.resolveException, identity) } + property("get scalalib at global version, no scalaModuleInfo") { + + val depRes = CoursierDependencyResolution(conf211) + + val desc = ModuleDescriptorConfiguration(ModuleID("test", "foo", "1.0"), ModuleInfo("foo")) + .withDependencies(Vector(scalaModule212.withConfigurations(Some("compile")))) + .withConfigurations(Vector(Configuration.of("Compile", "compile"))) + val module = depRes.moduleDescriptor(desc) + + depRes.update(module, UpdateConfiguration(), UnresolvedWarningConfiguration(), logger) match { + case Left(x) => throw x.resolveException + case Right(x) => + x.allModules.collect { + case m: ModuleID if m.organization == scalaModule212.organization && m.name == m.name => + m.revision + } should (contain(conf211.scalaVersion.get) and have length 1) // from config + } + } + + property("get scalalib at local version, scalaModuleInfo:overrideScalaVersion") { + + val depRes = CoursierDependencyResolution(conf211) + + val desc = ModuleDescriptorConfiguration(ModuleID("test", "foo", "1.0"), ModuleInfo("foo")) + .withDependencies(Vector(scalaModule212.withConfigurations(Some("compile")))) + .withConfigurations(Vector(Configuration.of("Compile", "compile"))) + .withScalaModuleInfo(scalaModuleInfo213.withOverrideScalaVersion(true)) + val module = depRes.moduleDescriptor(desc) + + depRes.update(module, UpdateConfiguration(), UnresolvedWarningConfiguration(), logger) match { + case Left(x) => throw x.resolveException + case Right(x) => + x.allModules.collect { + case m: ModuleID if m.organization == scalaModule212.organization && m.name == m.name => + m.revision + } should ( + contain(scalaModuleInfo213.scalaFullVersion) and have length 1 + ) // from autoScalaLib + } + } + + property("get scalalib at local version, scalaModuleInfo:!overrideScalaVersion") { + + val depRes = CoursierDependencyResolution(conf211) + val desc = ModuleDescriptorConfiguration(ModuleID("test", "foo", "1.0"), ModuleInfo("foo")) + .withDependencies(Vector(scalaModule212.withConfigurations(Some("compile")))) + .withConfigurations(Vector(Configuration.of("Compile", "compile"))) + .withScalaModuleInfo(scalaModuleInfo213.withOverrideScalaVersion(false)) + val module = depRes.moduleDescriptor(desc) + + depRes.update(module, UpdateConfiguration(), UnresolvedWarningConfiguration(), logger) match { + case Left(x) => throw x.resolveException + case Right(x) => + x.allModules.collect { + case m: ModuleID if m.organization == scalaModule212.organization && m.name == m.name => + m.revision + } should (contain(scalaModule212.revision) and have length 1) // from dependency + } + } + + property( + "get scalalib at local version, scalaModuleInfo:overrideScalaVersion, no explicit scala lib" + ) { + + val depRes = CoursierDependencyResolution(conf211) + val coursierModule212 = ModuleID("io.get-coursier", "coursier_2.12", "2.1.24") + val desc = ModuleDescriptorConfiguration(ModuleID("test", "foo", "1.0"), ModuleInfo("foo")) + .withDependencies(Vector(coursierModule212.withConfigurations(Some("compile")))) + .withConfigurations(Vector(Configuration.of("Compile", "compile"))) + .withScalaModuleInfo(scalaModuleInfo213.withOverrideScalaVersion(true)) + val module = depRes.moduleDescriptor(desc) + + depRes.update(module, UpdateConfiguration(), UnresolvedWarningConfiguration(), logger) match { + case Left(x) => throw x.resolveException + case Right(x) => + x.allModules.collect { + case m: ModuleID if m.organization == scalaModule212.organization && m.name == m.name => + m.revision + } should ( + contain(scalaModuleInfo213.scalaFullVersion) and have length 1 + ) // from autoScalaLib + } + } + + property( + "get scalalib at local version, scalaModuleInfo:!overrideScalaVersion, no explicit scala lib" + ) { + + val depRes = CoursierDependencyResolution(conf211) + val coursierModule212 = ModuleID("io.get-coursier", "coursier_2.12", "2.1.24") + val desc = ModuleDescriptorConfiguration(ModuleID("test", "foo", "1.0"), ModuleInfo("foo")) + .withDependencies(Vector(coursierModule212.withConfigurations(Some("compile")))) + .withConfigurations(Vector(Configuration.of("Compile", "compile"))) + .withScalaModuleInfo(scalaModuleInfo213.withOverrideScalaVersion(false)) + val module = depRes.moduleDescriptor(desc) + + depRes.update(module, UpdateConfiguration(), UnresolvedWarningConfiguration(), logger) match { + case Left(x) => throw x.resolveException + case Right(x) => + x.allModules.collect { + case m: ModuleID if m.organization == scalaModule212.organization && m.name == m.name => + CrossVersion.binaryScalaVersion(m.revision) + } should (contain("2.12") and have length 1) // from transitive dependency + } + } + }