Merge pull request #8962 from eed3si9n/bport/coursier-update

[2.0.x] bport: Update to Coursier 2.1.25-M24
This commit is contained in:
eugene yokota 2026-03-22 22:39:13 -04:00 committed by GitHub
commit 8f41553a32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 422 additions and 132 deletions

View File

@ -726,6 +726,7 @@ lazy val mainProj = (project in file("main"))
launcherInterface, launcherInterface,
caffeine, caffeine,
scala3Library, scala3Library,
scalaCollectionCompat,
), ),
libraryDependencies ++= List(scalaPar), libraryDependencies ++= List(scalaPar),
contrabandSettings, contrabandSettings,
@ -1293,6 +1294,7 @@ lazy val lmCoursierShaded = project
"coursier", "coursier",
"org.fusesource", "org.fusesource",
"macrocompat", "macrocompat",
"io.github.alexarchambault.isterminal",
"io.github.alexarchambault.windowsansi", "io.github.alexarchambault.windowsansi",
"concurrentrefhashmap", "concurrentrefhashmap",
"com.github.ghik", "com.github.ghik",
@ -1302,6 +1304,7 @@ lazy val lmCoursierShaded = project
"com.jcraft", "com.jcraft",
"com.lmax", "com.lmax",
"org.apache.commons", "org.apache.commons",
"org.apache.tika",
"org.apache.xbean", "org.apache.xbean",
"org.codehaus", "org.codehaus",
"org.iq80", "org.iq80",
@ -1328,7 +1331,7 @@ lazy val lmCoursierShaded = project
case PathList("com", "typesafe") => MergeStrategy.discard case PathList("com", "typesafe") => MergeStrategy.discard
case PathList("gigahorse") => MergeStrategy.discard case PathList("gigahorse") => MergeStrategy.discard
case PathList("jline") => MergeStrategy.discard case PathList("jline") => MergeStrategy.discard
case PathList("scala") => MergeStrategy.discard case PathList("scala", _*) => MergeStrategy.discard
case PathList("sjsonnew") => MergeStrategy.discard case PathList("sjsonnew") => MergeStrategy.discard
case PathList("xsbti") => MergeStrategy.discard case PathList("xsbti") => MergeStrategy.discard
case PathList("META-INF", "native", _*) => MergeStrategy.first case PathList("META-INF", "native", _*) => MergeStrategy.first

View File

@ -4,10 +4,9 @@ import java.io.File
import java.net.{ URI, URLClassLoader } import java.net.{ URI, URLClassLoader }
import coursier.{ Organization, Resolution } import coursier.{ Organization, Resolution }
import coursier.core.{ Classifier, Configuration } import coursier.core.{ Classifier, Configuration, Dependency, VariantPublication, Publication }
import coursier.cache.CacheDefaults import coursier.cache.CacheDefaults
import coursier.util.Artifact import coursier.util.Artifact
import coursier.internal.Typelevel
import lmcoursier.definitions.ToCoursier import lmcoursier.definitions.ToCoursier
import lmcoursier.internal.{ import lmcoursier.internal.{
ArtifactsParams, ArtifactsParams,
@ -28,9 +27,9 @@ import lmcoursier.syntax.*
import sbt.internal.librarymanagement.IvySbt import sbt.internal.librarymanagement.IvySbt
import sbt.librarymanagement.* import sbt.librarymanagement.*
import sbt.util.Logger import sbt.util.Logger
import coursier.core.{ BomDependency, Dependency, Publication } import coursier.core.BomDependency
import scala.annotation.nowarn
import scala.util.control.NonFatal import scala.util.control.NonFatal
import scala.util.{ Try, Failure } import scala.util.{ Try, Failure }
class CoursierDependencyResolution( class CoursierDependencyResolution(
@ -151,10 +150,11 @@ class CoursierDependencyResolution(
sys.error(s"unrecognized ModuleDescriptor type: $module") sys.error(s"unrecognized ModuleDescriptor type: $module")
} }
val so = conf.scalaOrganization val soOpt = conf.scalaOrganization
.map(Organization(_)) .map(Organization(_))
.orElse(module0.scalaModuleInfo.map(m => Organization(m.scalaOrganization))) .orElse(module0.scalaModuleInfo.map(m => Organization(m.scalaOrganization)))
.getOrElse(Organization("org.scala-lang"))
val so = soOpt.getOrElse(Organization("org.scala-lang"))
val sv = conf.scalaVersion val sv = conf.scalaVersion
.orElse(module0.scalaModuleInfo.map(_.scalaFullVersion)) .orElse(module0.scalaModuleInfo.map(_.scalaFullVersion))
// FIXME Manage to do stuff below without a scala version? // FIXME Manage to do stuff below without a scala version?
@ -230,7 +230,9 @@ class CoursierDependencyResolution(
optionalCrossVer = true, optionalCrossVer = true,
projectPlatform = projectPlatform projectPlatform = projectPlatform
) )
BomDependency(ToCoursier.module(mod), ver, Configuration.empty) (BomDependency(ToCoursier.module(mod), ver, Configuration.empty): @nowarn(
"msg=BomDependency is deprecated"
))
} }
// Coursier fills version from BOM only when versionConstraint is empty (Resolution.processedRootDependencies). // Coursier fills version from BOM only when versionConstraint is empty (Resolution.processedRootDependencies).
// So for deps with "*" or "" and BOMs present, pass empty version so BOM can supply it (sbt#4531). // So for deps with "*" or "" and BOMs present, pass empty version so BOM can supply it (sbt#4531).
@ -261,8 +263,6 @@ class CoursierDependencyResolution(
(ToCoursier.configuration(config), extends0.map(ToCoursier.configuration)) (ToCoursier.configuration(config), extends0.map(ToCoursier.configuration))
} }
val typelevel = so == Typelevel.typelevelOrg
val cache0 = coursier.cache val cache0 = coursier.cache
.FileCache() .FileCache()
.withLocation(cache) .withLocation(cache)
@ -295,9 +295,15 @@ class CoursierDependencyResolution(
.ResolutionParams() .ResolutionParams()
.withMaxIterations(conf.maxIterations) .withMaxIterations(conf.maxIterations)
.withProfiles(conf.mavenProfiles.toSet) .withProfiles(conf.mavenProfiles.toSet)
.withForceVersion(conf.forceVersions.map { (k, v) => (ToCoursier.module(k), v) }.toMap) .withForceVersion0(
.withTypelevel(typelevel) conf.forceVersions
.withReconciliation(ToCoursier.reconciliation(conf.reconciliation)) .map: (k, v) =>
(ToCoursier.module(k), ToCoursier.versionConstraint(v))
.toMap
)
.withScalaOrganizationOverride(soOpt)
.withReconciliation0(conf.reconciliation.map: (k, v) =>
ToCoursier.moduleMatchers(k) -> ToCoursier.constraintReconciliation(v))
.withExclusions(excludeDependencies) .withExclusions(excludeDependencies)
.withRules(ToCoursier.sameVersions(conf.sameVersions)), .withRules(ToCoursier.sameVersions(conf.sameVersions)),
strictOpt = conf.strict.map(ToCoursier.strict), strictOpt = conf.strict.map(ToCoursier.strict),
@ -332,7 +338,9 @@ class CoursierDependencyResolution(
def updateParams( def updateParams(
resolutions: Map[Configuration, Resolution], resolutions: Map[Configuration, Resolution],
artifacts: Seq[(Dependency, Publication, Artifact, Option[File])] artifacts: Seq[
(Dependency, Either[VariantPublication, Publication], Artifact, Option[File])
]
) = ) =
UpdateParams( UpdateParams(
thisModule = (ToCoursier.module(mod), ver), thisModule = (ToCoursier.module(mod), ver),
@ -359,7 +367,7 @@ class CoursierDependencyResolution(
conf.lockFile, conf.lockFile,
conf.scalaVersion conf.scalaVersion
) )
artifactResult <- lockDataOpt match { artifactResult0 <- lockDataOpt match {
case Some(lockData) => case Some(lockData) =>
LockedArtifactsRun.fetchFromLockFile(lockData, cache0, verbosityLevel, log) match { LockedArtifactsRun.fetchFromLockFile(lockData, cache0, verbosityLevel, log) match {
case Right(arts) => Right(arts) case Right(arts) => Right(arts)
@ -368,35 +376,43 @@ class CoursierDependencyResolution(
log.warn(s"Failed to fetch from lock file: $err, falling back to normal fetch") log.warn(s"Failed to fetch from lock file: $err, falling back to normal fetch")
} }
ArtifactsRun(artifactsParams(resolutions), verbosityLevel, log) ArtifactsRun(artifactsParams(resolutions), verbosityLevel, log)
.map(_.fullDetailedArtifacts) .map(_.fullDetailedArtifacts0)
} }
case None => case None =>
ArtifactsRun(artifactsParams(resolutions), verbosityLevel, log) ArtifactsRun(artifactsParams(resolutions), verbosityLevel, log)
.map(_.fullDetailedArtifacts) .map(_.fullDetailedArtifacts0)
} }
} yield { } yield {
val artifactResult = artifactResult0.map {
case (d, p: Publication, a, o) =>
(d, (Right(p): Either[VariantPublication, Publication]), a, o)
case (d, p: Either[VariantPublication, Publication], a, o) => (d, p, a, o)
}
val updateParams0 = updateParams(resolutions, artifactResult) val updateParams0 = updateParams(resolutions, artifactResult)
val report = UpdateRun.update(updateParams0, verbosityLevel, log) val report = UpdateRun.update(updateParams0, verbosityLevel, log)
if (lockDataOpt.isEmpty) { if (lockDataOpt.isEmpty) {
conf.lockFile.foreach { lockFile => conf.lockFile.foreach: lockFile =>
val artifactMap = artifactResult val artifactMap = artifactResult
.groupBy(_._1) .groupBy(_._1)
.view .view
.mapValues(_.map { case (_, pub, art, _) => .mapValues(_.map {
val originalUrl = case (_, Right(pub), art, _) =>
lmcoursier.internal.CacheUrlConversion.cacheFileToOriginalUrl(art.url, cache) val originalUrl =
(originalUrl, pub.classifier.value, pub.ext.value) lmcoursier.internal.CacheUrlConversion.cacheFileToOriginalUrl(art.url, cache)
(originalUrl, pub.classifier.value, pub.ext.value)
case (_, Left(pub), art, _) =>
sys.error("unsupported")
}) })
.toMap .toMap
val lockData = ResolutionSerializer.extractLockFileData( ResolutionSerializer.extractLockFileData(
resolutions, resolutions,
resolutionParams, resolutionParams,
conf.scalaVersion, conf.scalaVersion,
"2.0.0", "2.0.0",
artifactMap artifactMap
) ) match
LockFile.write(lockFile, lockData) case Right(lockData) => LockFile.write(lockFile, lockData)
} case Left(err) => throw err
} }
report report
} }
@ -410,25 +426,30 @@ class CoursierDependencyResolution(
private type DependencyKey = (coursier.core.Module, String) private type DependencyKey = (coursier.core.Module, String)
private def dependencyKey(dependency: Dependency): DependencyKey = private def dependencyKey(dependency: Dependency): DependencyKey =
dependency.module -> dependency.version dependency.module -> dependency.versionConstraint.asString
private def sortDependencies(dependencies: Seq[Dependency]): Vector[Dependency] = private def sortDependencies(dependencies: Seq[Dependency]): Vector[Dependency] =
dependencies.toVector.sortBy { dep => dependencies.toVector.sortBy { dep =>
(dep.module.organization.value, dep.module.name.value, dep.version) (dep.module.organization.value, dep.module.name.value, dep.versionConstraint.asString)
} }
private def safeDependenciesOf( private def safeDependenciesOf(
resolution: Resolution, resolution: Resolution,
dependency: Dependency dependency: Dependency
): Vector[Dependency] = ): Vector[Dependency] =
try sortDependencies(resolution.dependenciesOf(dependency, false, false)) try
resolution.dependenciesOf0(dependency, false, false) match
case Right(deps) => sortDependencies(deps)
case Left(_) => Vector.empty
catch { catch {
case NonFatal(_) => Vector.empty case NonFatal(_) => Vector.empty
} }
private def pathScore(path: Vector[Dependency]): (Int, String) = private def pathScore(path: Vector[Dependency]): (Int, String) =
path.size -> path path.size -> path
.map(dep => s"${dep.module.organization.value}:${dep.module.name.value}:${dep.version}") .map(dep =>
s"${dep.module.organization.value}:${dep.module.name.value}:${dep.versionConstraint.asString}"
)
.mkString("->") .mkString("->")
private def betterPath( private def betterPath(
@ -488,7 +509,9 @@ class CoursierDependencyResolution(
} }
.getOrElse(Vector(failedDependency)) .getOrElse(Vector(failedDependency))
normalizedRootModule +: resolvedPath.map(dep => toModuleId(dep.module, dep.version)) normalizedRootModule +: resolvedPath.map(dep =>
toModuleId(dep.module, dep.versionConstraint.asString)
)
} }
private def failedPaths( private def failedPaths(
@ -497,8 +520,8 @@ class CoursierDependencyResolution(
downloadErrors: Seq[coursier.error.ResolutionError.CantDownloadModule] downloadErrors: Seq[coursier.error.ResolutionError.CantDownloadModule]
): Map[ModuleID, Seq[ModuleID]] = ): Map[ModuleID, Seq[ModuleID]] =
downloadErrors.map { err => downloadErrors.map { err =>
val failedDependency = Dependency(err.module, err.version) val failedDependency = (Dependency(err.module, err.versionConstraint.asString): @nowarn)
val failedModule = toModuleId(err.module, err.version) val failedModule = toModuleId(err.module, err.versionConstraint.asString)
failedModule -> resolvePath(resolution, failedDependency, rootModule) failedModule -> resolvePath(resolution, failedDependency, rootModule)
}.toMap }.toMap
@ -537,7 +560,12 @@ class CoursierDependencyResolution(
val r = new ResolveException( val r = new ResolveException(
downloadErrors.map(_.getMessage), downloadErrors.map(_.getMessage),
downloadErrors.map { err => downloadErrors.map { err =>
toModuleId(err.module, err.version) ModuleID(
err.module.organization.value,
err.module.name.value,
err.versionConstraint.asString,
)
.withExtraAttributes(err.module.attributes)
}, },
resolvedPaths resolvedPaths
) )

View File

@ -1,7 +1,10 @@
package lmcoursier.definitions package lmcoursier.definitions
import coursier.core.Overrides
import coursier.version.{ ConstraintReconciliation, VersionConstraint, Version }
import lmcoursier.credentials.{ Credentials, DirectCredentials, FileCredentials } import lmcoursier.credentials.{ Credentials, DirectCredentials, FileCredentials }
import sbt.librarymanagement.InclExclRule import sbt.librarymanagement.InclExclRule
import scala.annotation.nowarn
// TODO Make private[lmcoursier] // TODO Make private[lmcoursier]
// private[coursier] // private[coursier]
@ -10,6 +13,12 @@ object ToCoursier {
def configuration(configuration: Configuration): coursier.core.Configuration = def configuration(configuration: Configuration): coursier.core.Configuration =
coursier.core.Configuration(configuration.value) coursier.core.Configuration(configuration.value)
def configurationBased(c: Configuration): coursier.core.VariantSelector =
coursier.core.VariantSelector.ConfigurationBased(configuration(c))
def variantConfiguration(c: Configuration): coursier.core.Variant =
coursier.core.Variant.Configuration(configuration(c))
def publication(publication: Publication): coursier.core.Publication = def publication(publication: Publication): coursier.core.Publication =
coursier.core.Publication( coursier.core.Publication(
publication.name, publication.name,
@ -52,6 +61,7 @@ object ToCoursier {
includeByDefault = matcher.includeByDefault includeByDefault = matcher.includeByDefault
) )
@nowarn
def reconciliation(r: Reconciliation): coursier.core.Reconciliation = def reconciliation(r: Reconciliation): coursier.core.Reconciliation =
r match { r match {
case Reconciliation.Default => coursier.core.Reconciliation.Default case Reconciliation.Default => coursier.core.Reconciliation.Default
@ -60,11 +70,22 @@ object ToCoursier {
case Reconciliation.SemVer => coursier.core.Reconciliation.SemVer case Reconciliation.SemVer => coursier.core.Reconciliation.SemVer
} }
@nowarn
def reconciliation( def reconciliation(
rs: Vector[(ModuleMatchers, Reconciliation)] rs: Vector[(ModuleMatchers, Reconciliation)]
): Vector[(coursier.util.ModuleMatchers, coursier.core.Reconciliation)] = ): Vector[(coursier.util.ModuleMatchers, coursier.core.Reconciliation)] =
rs map { (m, r) => (moduleMatchers(m), reconciliation(r)) } rs map { (m, r) => (moduleMatchers(m), reconciliation(r)) }
def constraintReconciliation(r: Reconciliation): coursier.version.ConstraintReconciliation =
r match
case Reconciliation.Default => ConstraintReconciliation.Default
case Reconciliation.Relaxed => ConstraintReconciliation.Relaxed
case Reconciliation.Strict => ConstraintReconciliation.Strict
case Reconciliation.SemVer => ConstraintReconciliation.SemVer
def versionConstraint(v: String): VersionConstraint =
VersionConstraint(v)
def sameVersions( def sameVersions(
sv: Seq[Set[InclExclRule]] sv: Seq[Set[InclExclRule]]
): Seq[(coursier.params.rule.SameVersion, coursier.params.rule.RuleResolution)] = ): Seq[(coursier.params.rule.SameVersion, coursier.params.rule.RuleResolution)] =
@ -74,11 +95,14 @@ object ToCoursier {
coursier.params.rule.SameVersion(matchers) -> coursier.params.rule.RuleResolution.TryResolve coursier.params.rule.SameVersion(matchers) -> coursier.params.rule.RuleResolution.TryResolve
} }
def version(v: String): Version =
Version(v)
def dependency(dependency: Dependency): coursier.core.Dependency = def dependency(dependency: Dependency): coursier.core.Dependency =
coursier.core.Dependency( coursier.core.Dependency(
module(dependency.module), module(dependency.module),
dependency.version, versionConstraint(dependency.version),
configuration(dependency.configuration), configurationBased(dependency.configuration),
dependency.exclusions.map { (org, name) => dependency.exclusions.map { (org, name) =>
(coursier.core.Organization(org.value), coursier.core.ModuleName(name.value)) (coursier.core.Organization(org.value), coursier.core.ModuleName(name.value))
}, },
@ -89,27 +113,27 @@ object ToCoursier {
def project(project: Project): coursier.core.Project = def project(project: Project): coursier.core.Project =
coursier.core.Project( coursier.core.Project(
module(project.module), module = module(project.module),
project.version, version0 = version(project.version),
project.dependencies.map { (conf, dep) => dependencies0 = project.dependencies.map { (conf, dep) =>
configuration(conf) -> dependency(dep) variantConfiguration(conf) -> dependency(dep)
}, },
project.configurations.map { (k, l) => configurations = project.configurations.map { (k, l) =>
configuration(k) -> l.map(configuration) configuration(k) -> l.map(configuration)
}, },
None, parent0 = None,
Nil, dependencyManagement0 = Nil,
project.properties, properties = project.properties,
Nil, profiles = Nil,
None, versions = None,
None, snapshotVersioning = None,
project.packagingOpt.map(t => coursier.core.Type(t.value)), packagingOpt = project.packagingOpt.map(t => coursier.core.Type(t.value)),
relocated = false, relocated = false,
None, actualVersionOpt0 = None,
project.publications.map { (conf, pub) => publications0 = project.publications.map { (conf, pub) =>
configuration(conf) -> publication(pub) variantConfiguration(conf) -> publication(pub)
}, },
coursier.core.Info( info = coursier.core.Info(
project.info.description, project.info.description,
project.info.homePage, project.info.homePage,
project.info.licenses, project.info.licenses,
@ -130,8 +154,11 @@ object ToCoursier {
dt.second dt.second
) )
}, },
None // TODO Add scm field in lmcoursier.definitions.Info? None, // TODO Add scm field in lmcoursier.definitions.Info?
) ),
overrides = Overrides.empty,
variants = Map.empty,
variantPublications = Map.empty,
) )
def credentials(credentials: Credentials): coursier.credentials.Credentials = def credentials(credentials: Credentials): coursier.credentials.Credentials =

View File

@ -14,14 +14,15 @@ object BuildClock {
): String = { ): String = {
val digest = MessageDigest.getInstance("SHA-1") val digest = MessageDigest.getInstance("SHA-1")
dependencies.sortBy(d => (d._1.value, d._2.module.toString, d._2.version)).foreach { dependencies
case (config, dep) => .sortBy(d => (d._1.value, d._2.module.toString, d._2.versionConstraint.asString))
.foreach { case (config, dep) =>
digest.update(config.value.getBytes("UTF-8")) digest.update(config.value.getBytes("UTF-8"))
digest.update(dep.module.organization.value.getBytes("UTF-8")) digest.update(dep.module.organization.value.getBytes("UTF-8"))
digest.update(dep.module.name.value.getBytes("UTF-8")) digest.update(dep.module.name.value.getBytes("UTF-8"))
digest.update(dep.version.getBytes("UTF-8")) digest.update(dep.versionConstraint.asString.getBytes("UTF-8"))
digest.update(dep.configuration.value.getBytes("UTF-8")) digest.update(dep.variantSelector.repr.getBytes("UTF-8"))
} }
repositories.foreach { repo => repositories.foreach { repo =>
digest.update(repo.toString.getBytes("UTF-8")) digest.update(repo.toString.getBytes("UTF-8"))
@ -33,9 +34,9 @@ object BuildClock {
digest.update(params.params.maxIterations.toString.getBytes("UTF-8")) digest.update(params.params.maxIterations.toString.getBytes("UTF-8"))
params.params.forceVersion.toSeq.sortBy(_._1.toString).foreach { case (mod, ver) => params.params.forceVersion0.toSeq.sortBy(_._1.toString).foreach { case (mod, ver) =>
digest.update(mod.toString.getBytes("UTF-8")) digest.update(mod.toString.getBytes("UTF-8"))
digest.update(ver.getBytes("UTF-8")) digest.update(ver.asString.getBytes("UTF-8"))
} }
params.params.exclusions.toSeq.sortBy(e => (e._1.value, e._2.value)).foreach { params.params.exclusions.toSeq.sortBy(e => (e._1.value, e._2.value)).foreach {

View File

@ -7,7 +7,11 @@ import coursier.util.{ EitherT, Monad }
final case class InterProjectRepository(projects: Seq[Project]) extends Repository { final case class InterProjectRepository(projects: Seq[Project]) extends Repository {
private val map = projects private val map = projects
.map(proj => proj.moduleVersion -> proj) .map(proj =>
(proj.moduleVersion0 match
case (m, v) => (m, v.asString)
) -> proj
)
.toMap .toMap
def find[F[_]]( def find[F[_]](

View File

@ -2,6 +2,7 @@ package lmcoursier.internal
import coursier.cache.FileCache import coursier.cache.FileCache
import coursier.core.{ Classifier, Dependency, Extension, Publication, Type } import coursier.core.{ Classifier, Dependency, Extension, Publication, Type }
import coursier.version.VersionConstraint
import coursier.util.Artifact import coursier.util.Artifact
import sbt.util.Logger import sbt.util.Logger
@ -36,7 +37,7 @@ object LockedArtifactsRun {
val dependency = Dependency( val dependency = Dependency(
module = module, module = module,
version = depLock.version version = VersionConstraint(depLock.version)
) )
val classifier = Classifier(artLock.classifier.getOrElse("")) val classifier = Classifier(artLock.classifier.getOrElse(""))

View File

@ -0,0 +1,70 @@
package lmcoursier.internal
import coursier.core.*
import coursier.core.Resolution as CoreResolution
import coursier.{ Dependency, Resolution }
import scala.annotation.tailrec
/**
* Detects cyclic Maven / Gradle relocation chains that make
* `coursier.graph.DependencyTree` loop forever (see sbt#8917, coursier#3578).
*
* Mirrors one step of `coursier.graph.DependencyTree.Node.relocation` so we
* only skip `Conflict` when Coursier would spin on the same graph.
*/
private[internal] object RelocationCycleDetector:
type Mvc = CoreResolution.ModuleVersionConstraint
private def oneRelocationStep(resolution: Resolution, dep: Dependency): Option[Mvc] =
resolution.reconciledVersions
.get(dep.module)
.flatMap: recon =>
val dep0 =
if dep.versionConstraint == recon then dep
else dep.withVersionConstraint(recon)
resolution.projectCache0
.get(dep0.moduleVersionConstraint)
.flatMap:
case (_, proj) =>
val mavenRelocatedOpt =
if proj.relocated && proj.dependencies0.lengthCompare(1) == 0 then
Some(proj.dependencies0.head._2)
else None
def gradleRelocated: Option[Dependency] =
dep0.variantSelector match
case attr: VariantSelector.AttributesBased =>
if proj.variants.isEmpty then None
else
proj.variantFor(attr) match
case Left(_) => None
case Right(variant) => proj.isRelocatedVariant(variant)
case _: VariantSelector.ConfigurationBased => None
mavenRelocatedOpt
.orElse(gradleRelocated)
.map: relocatedTo =>
val relocatedTo0 =
if relocatedTo.variantSelector.isEmpty then
relocatedTo.withVariantSelector(dep0.variantSelector)
else relocatedTo
relocatedTo0.moduleVersionConstraint
/** When true, `coursier.graph.Conflict(resolution)` can run indefinitely. */
def hasRelocationCycle(resolution: Resolution): Boolean =
resolution.isDone && resolution.conflicts.isEmpty && resolution.errors0.isEmpty && {
val keys = resolution.projectCache0.keySet
keys.exists: start =>
@tailrec
def walk(visited: Set[Mvc], cur: Option[Mvc]): Boolean =
cur match
case None => false
case Some(mvc) =>
if visited.contains(mvc) then true
else
val dep = Dependency(module = mvc._1, version = mvc._2)
walk(visited + mvc, oneRelocationStep(resolution, dep))
walk(Set.empty, Some(start))
}
end RelocationCycleDetector

View File

@ -12,6 +12,7 @@ import coursier.params.rule.RuleResolution
import coursier.util.Task import coursier.util.Task
import sbt.util.Logger import sbt.util.Logger
import scala.annotation.nowarn
import scala.concurrent.duration.FiniteDuration import scala.concurrent.duration.FiniteDuration
import scala.collection.mutable import scala.collection.mutable
@ -44,6 +45,7 @@ object ResolutionRun {
val printOptionalMessage = verbosityLevel >= 0 && verbosityLevel <= 1 val printOptionalMessage = verbosityLevel >= 0 && verbosityLevel <= 1
@nowarn
def depsRepr(deps: Seq[(Configuration, Dependency)]) = def depsRepr(deps: Seq[(Configuration, Dependency)]) =
deps deps
.map { (config, dep) => .map { (config, dep) =>
@ -89,6 +91,7 @@ object ResolutionRun {
if (verbosityLevel >= 2) if (verbosityLevel >= 2)
log.info(initialMessage) log.info(initialMessage)
@nowarn
val resolveTask: Resolve[Task] = { val resolveTask: Resolve[Task] = {
Resolve() Resolve()
// re-using various caches from a resolution of a configuration we extend // re-using various caches from a resolution of a configuration we extend
@ -141,12 +144,7 @@ object ResolutionRun {
resolveTask.io.attempt resolveTask.io.attempt
.flatMap { .flatMap {
case Left(e: ResolutionError) => case Left(e: ResolutionError) =>
val hasConnectionTimeouts = e.errors.exists { if (isTransientResolutionError(e))
case err: CantDownloadModule =>
err.perRepositoryErrors.exists(_.contains("Connection timed out"))
case _ => false
}
if (hasConnectionTimeouts)
if (attempt + 1 >= maxAttempts) { if (attempt + 1 >= maxAttempts) {
log.error(s"Failed, maximum iterations ($maxAttempts) reached") log.error(s"Failed, maximum iterations ($maxAttempts) reached")
Task.point(Left(e)) Task.point(Left(e))
@ -173,6 +171,7 @@ object ResolutionRun {
} }
} }
@nowarn
def resolutions( def resolutions(
params: ResolutionParams, params: ResolutionParams,
verbosityLevel: Int, verbosityLevel: Int,
@ -276,4 +275,16 @@ object ResolutionRun {
} }
private lazy val retryScheduler = ThreadUtil.fixedScheduledThreadPool(1) private lazy val retryScheduler = ThreadUtil.fixedScheduledThreadPool(1)
private[internal] def isTransientResolutionError(e: ResolutionError): Boolean =
e.errors.exists {
case err: CantDownloadModule => isTimeout(err) || isServerError(err)
case _ => false
}
private def isTimeout(err: CantDownloadModule): Boolean =
err.perRepositoryErrors.exists(_.contains("Connection timed out"))
private def isServerError(err: CantDownloadModule): Boolean =
err.perRepositoryErrors.exists(_.contains("Server returned HTTP response code: 5"))
} }

View File

@ -2,6 +2,9 @@ package lmcoursier.internal
import coursier.{ Project, Resolution } import coursier.{ Project, Resolution }
import coursier.core.{ ArtifactSource, Configuration, Dependency, Info, Module } import coursier.core.{ ArtifactSource, Configuration, Dependency, Info, Module }
import coursier.error.DependencyError
import coursier.version.VersionConstraint
import scala.annotation.nowarn
import scala.collection.immutable.Seq import scala.collection.immutable.Seq
object ResolutionSerializer { object ResolutionSerializer {
@ -12,7 +15,7 @@ object ResolutionSerializer {
scalaVersion: Option[String], scalaVersion: Option[String],
sbtVersion: String, sbtVersion: String,
artifactMap: Map[Dependency, Seq[(String, String, String)]] artifactMap: Map[Dependency, Seq[(String, String, String)]]
): LockFileData = { ): Either[DependencyError, LockFileData] =
val buildClock = BuildClock.compute( val buildClock = BuildClock.compute(
params.dependencies, params.dependencies,
params.mainRepositories, params.mainRepositories,
@ -20,70 +23,79 @@ object ResolutionSerializer {
params params
) )
val configurations = resolutions.toSeq val configurations0 = resolutions.toVector
.sortBy(_._1.value) .sortBy(_._1.value)
.map { case (config, resolution) => .map { case (config, resolution) =>
val dependencies = extractDependencies(resolution, config, artifactMap) extractDependencies(resolution, config, artifactMap) match
ConfigurationLock(config.value, dependencies.toVector) case Right(dependencies) => Right(ConfigurationLock(config.value, dependencies.toVector))
case Left(err) => Left(err)
} }
.toVector traverseEither(configurations0).map: configurations =>
val metadata = LockFileMetadata(
sbtVersion = sbtVersion,
scalaVersion = scalaVersion
)
val metadata = LockFileMetadata( LockFileData(
sbtVersion = sbtVersion, version = LockFileConstants.currentVersion,
scalaVersion = scalaVersion buildClock = buildClock,
) configurations = configurations,
metadata = metadata
LockFileData( )
version = LockFileConstants.currentVersion,
buildClock = buildClock,
configurations = configurations,
metadata = metadata
)
}
private def extractDependencies( private def extractDependencies(
resolution: Resolution, resolution: Resolution,
config: Configuration, config: Configuration,
artifactMap: Map[Dependency, Seq[(String, String, String)]] artifactMap: Map[Dependency, Seq[(String, String, String)]]
): Seq[DependencyLock] = { ): Either[DependencyError, Vector[DependencyLock]] =
val dependencies = resolution.minDependencies val dependencies = resolution.minDependencies
val xs = dependencies.toVector
.sortBy(d => (d.module.toString, d.versionConstraint.asString))
.map: dep =>
val resolvedVersion: String = resolution.retainedVersions
.get(dep.module) match
case Some(v) => s"$v"
case None => s"${dep.versionConstraint.asString}"
dependencies.toSeq.sortBy(d => (d.module.toString, d.version)).map { dep => resolution
val resolvedVersion: String = resolution.retainedVersions .dependenciesOf0(dep, withRetainedVersions = true)
.get(dep.module) match { .map: transitives0 =>
case Some(v) => s"$v" val transitives = transitives0
case None => s"${dep.version}" .map: d =>
} s"${d.module.organization.value}:${d.module.name.value}:${d.versionConstraint.asString}"
.sorted
val transitives = resolution val artifacts =
.dependenciesOf(dep, withRetainedVersions = true) artifactMap.getOrElse(dep, Seq.empty).map { case (url, classifier, ext) =>
.map(d => s"${d.module.organization.value}:${d.module.name.value}:${d.version}") ArtifactLock(
.sorted url = url,
classifier = if (classifier.isEmpty) None else Some(classifier),
extension = ext,
tpe = dep.attributes.`type`.value
)
}
val artifacts = artifactMap.getOrElse(dep, Seq.empty).map { case (url, classifier, ext) => DependencyLock(
ArtifactLock( organization = dep.module.organization.value,
url = url, name = dep.module.name.value,
classifier = if (classifier.isEmpty) None else Some(classifier), version = resolvedVersion,
extension = ext, configuration = dep.variantSelector.repr,
tpe = dep.attributes.`type`.value classifier = dep.attributes.classifier.value match {
) case "" => None
} case c => Some(c)
},
tpe = dep.attributes.`type`.value,
transitives = transitives.toVector,
artifacts = artifacts.toVector
)
traverseEither(xs)
DependencyLock( private def traverseEither[A1, A2](xs: Vector[Either[A1, A2]]): Either[A1, Vector[A2]] =
organization = dep.module.organization.value, xs.foldLeft(Right(Vector.empty): Either[A1, Vector[A2]]) {
name = dep.module.name.value, case (Left(acc), x) => Left(acc)
version = resolvedVersion, case (Right(acc), Left(x)) => Left(x)
configuration = dep.configuration.value, case (Right(acc), Right(x)) => Right(acc ++ Vector(x))
classifier = dep.attributes.classifier.value match {
case "" => None
case c => Some(c)
},
tpe = dep.attributes.`type`.value,
transitives = transitives.toVector,
artifacts = artifacts.toVector
)
} }
}
def reconstructResolutions( def reconstructResolutions(
lockFileData: LockFileData, lockFileData: LockFileData,
@ -120,7 +132,7 @@ object ResolutionSerializer {
coursier.ModuleName(depLock.name), coursier.ModuleName(depLock.name),
Map.empty[String, String] Map.empty[String, String]
), ),
depLock.version VersionConstraint(depLock.version),
) )
}.toSet }.toSet
@ -131,7 +143,7 @@ object ResolutionSerializer {
coursier.ModuleName(depLock.name), coursier.ModuleName(depLock.name),
Map.empty[String, String] Map.empty[String, String]
) )
val project = Project( val project = (Project(
module = module, module = module,
version = depLock.version, version = depLock.version,
dependencies = Seq.empty, dependencies = Seq.empty,
@ -147,15 +159,15 @@ object ResolutionSerializer {
actualVersionOpt = None, actualVersionOpt = None,
publications = Seq.empty, publications = Seq.empty,
info = Info.empty info = Info.empty
) ): @nowarn)
(module, depLock.version) -> (EmptyArtifactSource, project) (module, depLock.version) -> (EmptyArtifactSource, project)
}.toMap }.toMap
Resolution() (Resolution()
.withRootDependencies(rootDeps) .withRootDependencies(rootDeps)
.withDependencies(dependencies) .withDependencies(dependencies)
.withForceVersions(forceVersions ++ params.params.forceVersion) .withForceVersions(forceVersions ++ params.params.forceVersion)
.withProjectCache(projectCache) .withProjectCache(projectCache): @nowarn)
} }
private object EmptyArtifactSource extends ArtifactSource { private object EmptyArtifactSource extends ArtifactSource {

View File

@ -4,14 +4,23 @@ import java.io.File
import java.util.{ Collections, GregorianCalendar, WeakHashMap } import java.util.{ Collections, GregorianCalendar, WeakHashMap }
import coursier.cache.CacheUrl import coursier.cache.CacheUrl
import coursier.{ Attributes, Dependency, Module, Project, Resolution } import coursier.{ Attributes, Dependency, Module, Project, Resolution }
import coursier.core.{ Classifier, Configuration, Extension, Info, Publication, Type } import coursier.core.{
Classifier,
Configuration,
Extension,
Info,
MinimizedExclusions,
Publication,
Type,
VariantPublication
}
import coursier.maven.MavenAttributes import coursier.maven.MavenAttributes
import coursier.util.Artifact import coursier.util.Artifact
import sbt.librarymanagement.{ Artifact as _, Configuration as _, * } import sbt.librarymanagement.{ Artifact as _, Configuration as _, * }
import sbt.util.Logger import sbt.util.Logger
import scala.annotation.nowarn
import scala.annotation.tailrec import scala.annotation.tailrec
import coursier.core.MinimizedExclusions
private[internal] object SbtUpdateReport { private[internal] object SbtUpdateReport {
@ -32,6 +41,7 @@ private[internal] object SbtUpdateReport {
private def infoProperties(project: Project): Seq[(String, String)] = private def infoProperties(project: Project): Seq[(String, String)] =
project.properties.filter(_._1.startsWith("info.")) project.properties.filter(_._1.startsWith("info."))
@nowarn
private val moduleId = caching[(Dependency, String, Map[String, String]), ModuleID] { private val moduleId = caching[(Dependency, String, Map[String, String]), ModuleID] {
(dependency, version, extraProperties) => (dependency, version, extraProperties) =>
val mod = sbt.librarymanagement.ModuleID( val mod = sbt.librarymanagement.ModuleID(
@ -75,6 +85,7 @@ private[internal] object SbtUpdateReport {
.withExtraAttributes(module.attributes ++ extraProperties) .withExtraAttributes(module.attributes ++ extraProperties)
} }
@nowarn
private val moduleReport = caching[ private val moduleReport = caching[
( (
Dependency, Dependency,
@ -138,13 +149,16 @@ private[internal] object SbtUpdateReport {
.withCallers(callers.toVector) .withCallers(callers.toVector)
} }
@nowarn
private def moduleReports( private def moduleReports(
thisModule: (Module, String), thisModule: (Module, String),
res: Resolution, res: Resolution,
interProjectDependencies: Seq[Project], interProjectDependencies: Seq[Project],
classifiersOpt: Option[Seq[Classifier]], classifiersOpt: Option[Seq[Classifier]],
artifactFileOpt: (Module, String, Attributes, Artifact) => Option[File], artifactFileOpt: (Module, String, Attributes, Artifact) => Option[File],
fullArtifactsOpt: Option[Map[(Dependency, Publication, Artifact), Option[File]]], fullArtifactsOpt: Option[
Map[(Dependency, Either[VariantPublication, Publication], Artifact), Option[File]]
],
log: Logger, log: Logger,
includeSignatures: Boolean, includeSignatures: Boolean,
classpathOrder: Boolean, classpathOrder: Boolean,
@ -164,7 +178,7 @@ private[internal] object SbtUpdateReport {
deps.map { (d, p, a) => deps.map { (d, p, a) =>
val d0 = d.withAttributes(d.attributes.withClassifier(p.classifier)) val d0 = d.withAttributes(d.attributes.withClassifier(p.classifier))
val a0 = if (missingOk) a.withOptional(true) else a val a0 = if (missingOk) a.withOptional(true) else a
val f = map.get((d0, p, a0)).flatten val f = map.get((d0, Right(p), a0)).flatten
(d, p, a0, f) // not d0 (d, p, a0, f) // not d0
} }
case None => case None =>
@ -311,6 +325,7 @@ private[internal] object SbtUpdateReport {
} }
} }
@nowarn
def apply( def apply(
thisModule: (Module, String), thisModule: (Module, String),
configDependencies: Map[Configuration, Seq[Dependency]], configDependencies: Map[Configuration, Seq[Dependency]],
@ -318,7 +333,9 @@ private[internal] object SbtUpdateReport {
interProjectDependencies: Vector[Project], interProjectDependencies: Vector[Project],
classifiersOpt: Option[Seq[Classifier]], classifiersOpt: Option[Seq[Classifier]],
artifactFileOpt: (Module, String, Attributes, Artifact) => Option[File], artifactFileOpt: (Module, String, Attributes, Artifact) => Option[File],
fullArtifactsOpt: Option[Map[(Dependency, Publication, Artifact), Option[File]]], fullArtifactsOpt: Option[
Map[(Dependency, Either[VariantPublication, Publication], Artifact), Option[File]]
],
log: Logger, log: Logger,
includeSignatures: Boolean, includeSignatures: Boolean,
classpathOrder: Boolean, classpathOrder: Boolean,
@ -327,6 +344,22 @@ private[internal] object SbtUpdateReport {
classLoaders: Seq[ClassLoader], classLoaders: Seq[ClassLoader],
): UpdateReport = { ): UpdateReport = {
val skipConflictDetail: Map[Configuration, Boolean] =
resolutions.map { case (cfg, subRes) =>
cfg -> RelocationCycleDetector.hasRelocationCycle(subRes)
}.toMap
val configsWithRelocationCycle =
skipConflictDetail.collect { case (cfg, true) => cfg.value }.toSeq.sorted
if (configsWithRelocationCycle.nonEmpty) {
log.warn(
"Skipping dependency conflict detail for configuration(s) " +
configsWithRelocationCycle.mkString(", ") +
": cyclic Maven or Gradle relocations in the resolved graph. " +
"Resolution succeeded; eviction detail may be incomplete. " +
"See https://github.com/sbt/sbt/issues/8917"
)
}
val configReports = resolutions.map { (config, subRes) => val configReports = resolutions.map { (config, subRes) =>
val reports = moduleReports( val reports = moduleReports(
thisModule, thisModule,
@ -361,8 +394,13 @@ private[internal] object SbtUpdateReport {
OrganizationArtifactReport(rep.module.organization, rep.module.name, Vector(rep)) OrganizationArtifactReport(rep.module.organization, rep.module.name, Vector(rep))
} }
def conflicts: Seq[coursier.graph.Conflict] =
try
if (skipConflictDetail.getOrElse(config, false)) Nil
else coursier.graph.Conflict(subRes)
catch case e: Throwable if missingOk => Nil
val evicted = for { val evicted = for {
c <- coursier.graph.Conflict(subRes) c <- conflicts
// ideally, forceVersions should be taken into account by coursier.core.Resolution itself, when // ideally, forceVersions should be taken into account by coursier.core.Resolution itself, when
// it computes transitive dependencies. It only handles forced versions at a global level for now, // it computes transitive dependencies. It only handles forced versions at a global level for now,
// rather than handing them for each dependency (where each dependency could have its own forced // rather than handing them for each dependency (where each dependency could have its own forced

View File

@ -7,6 +7,7 @@ import coursier.cache.{ ConnectionBuilder, FileCache }
import coursier.core.* import coursier.core.*
import coursier.util.{ Artifact, EitherT, Monad } import coursier.util.{ Artifact, EitherT, Monad }
import scala.annotation.nowarn
import scala.util.Try import scala.util.Try
object TemporaryInMemoryRepository { object TemporaryInMemoryRepository {
@ -134,6 +135,7 @@ final class TemporaryInMemoryRepository private (
val cacheOpt: Option[FileCache[Nothing]] val cacheOpt: Option[FileCache[Nothing]]
) extends Repository { ) extends Repository {
@nowarn
def find[F[_]]( def find[F[_]](
module: Module, module: Module,
version: String, version: String,
@ -183,6 +185,7 @@ final class TemporaryInMemoryRepository private (
EitherT(F.map(F.point(()))(_ => res)) EitherT(F.map(F.point(()))(_ => res))
} }
@nowarn
def artifacts( def artifacts(
dependency: Dependency, dependency: Dependency,
project: Project, project: Project,

View File

@ -9,7 +9,9 @@ import coursier.util.Artifact
final case class UpdateParams( final case class UpdateParams(
thisModule: (Module, String), thisModule: (Module, String),
artifacts: Map[Artifact, File], artifacts: Map[Artifact, File],
fullArtifacts: Option[Map[(Dependency, Publication, Artifact), Option[File]]], fullArtifacts: Option[
Map[(Dependency, Either[VariantPublication, Publication], Artifact), Option[File]]
],
classifiers: Option[Seq[Classifier]], classifiers: Option[Seq[Classifier]],
configs: Map[Configuration, Set[Configuration]], configs: Map[Configuration, Set[Configuration]],
dependencies: Seq[(Configuration, Dependency)], dependencies: Seq[(Configuration, Dependency)],

View File

@ -6,11 +6,13 @@ import coursier.core.*
import coursier.util.Print import coursier.util.Print
import sbt.librarymanagement.UpdateReport import sbt.librarymanagement.UpdateReport
import sbt.util.Logger import sbt.util.Logger
import scala.annotation.nowarn
// private[coursier] // private[coursier]
object UpdateRun { object UpdateRun {
// Move back to coursier.util (in core module) after 1.0? // Move back to coursier.util (in core module) after 1.0?
@nowarn
private def allDependenciesByConfig( private def allDependenciesByConfig(
res: Map[Configuration, Resolution], res: Map[Configuration, Resolution],
depsByConfig: Map[Configuration, Seq[Dependency]], depsByConfig: Map[Configuration, Seq[Dependency]],
@ -33,6 +35,7 @@ object UpdateRun {
} }
// Move back to coursier.util (in core module) after 1.0? // Move back to coursier.util (in core module) after 1.0?
@nowarn
private def dependenciesWithConfig( private def dependenciesWithConfig(
res: Map[Configuration, Resolution], res: Map[Configuration, Resolution],
depsByConfig: Map[Configuration, Seq[Dependency]], depsByConfig: Map[Configuration, Seq[Dependency]],
@ -48,6 +51,7 @@ object UpdateRun {
} }
.toSet .toSet
@nowarn
def update( def update(
params: UpdateParams, params: UpdateParams,
verbosityLevel: Int, verbosityLevel: Int,

View File

@ -296,6 +296,19 @@ final class ResolutionSpec extends AnyPropSpec with Matchers {
assert(resolution.isRight) assert(resolution.isRight)
} }
property("resolve sbt-site 1.4.1 (sbt #8917)") {
val pluginAttributes = Map("scalaVersion" -> "2.12", "sbtVersion" -> "1.0")
val dependencies =
Vector(("com.typesafe.sbt" % "sbt-site" % "1.4.1").withExtraAttributes(pluginAttributes))
val coursierModule = module(lmEngine, stubModule, dependencies, Some("2.12.4"))
val resolution =
lmEngine.update(coursierModule, UpdateConfiguration(), UnresolvedWarningConfiguration(), log)
assert(resolution.isRight)
val compileConfig =
resolution.toOption.get.configurations.find(_.configuration == Compile.toConfigRef).get
compileConfig.modules.map(_.module.name) should not be empty
}
property("resolve licenses from parent poms") { property("resolve licenses from parent poms") {
val dependencies = val dependencies =
Vector(("org.apache.commons" % "commons-compress" % "1.26.2")) Vector(("org.apache.commons" % "commons-compress" % "1.26.2"))

View File

@ -0,0 +1,10 @@
package lmcoursier.internal
import coursier.Resolution
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
class RelocationCycleDetectorSpec extends AnyFunSuite with Matchers:
test("incomplete resolution is not treated as having a relocation cycle"):
RelocationCycleDetector.hasRelocationCycle(Resolution()) shouldBe false

View File

@ -0,0 +1,39 @@
package lmcoursier.internal
import coursier.core.{ Module, ModuleName, Organization, Resolution }
import coursier.error.ResolutionError.CantDownloadModule
import coursier.version.VersionConstraint
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
class ResolutionRunSpec extends AnyFunSuite with Matchers:
private def cantDownload(errors: String*): CantDownloadModule =
new CantDownloadModule(
Resolution(),
Module(Organization("org"), ModuleName("mod"), Map.empty),
VersionConstraint("1.0"),
errors.toSeq
)
test("503 is a transient resolution error"):
val err = cantDownload(
"Server returned HTTP response code: 503 for URL: https://repo.example.com/org/mod/1.0/mod-1.0.pom"
)
ResolutionRun.isTransientResolutionError(err) shouldBe true
test("500 is a transient resolution error"):
val err = cantDownload(
"Server returned HTTP response code: 500 for URL: https://repo.example.com/org/mod/1.0/mod-1.0.pom"
)
ResolutionRun.isTransientResolutionError(err) shouldBe true
test("connection timeout is a transient resolution error"):
val err = cantDownload("Connection timed out")
ResolutionRun.isTransientResolutionError(err) shouldBe true
test("404 is not a transient resolution error"):
val err = cantDownload(
"Server returned HTTP response code: 404 for URL: https://repo.example.com/org/mod/1.0/mod-1.0.pom"
)
ResolutionRun.isTransientResolutionError(err) shouldBe false

View File

@ -99,6 +99,7 @@ object Dependencies {
val scalaXml = "org.scala-lang.modules" %% "scala-xml" % "2.4.0" val scalaXml = "org.scala-lang.modules" %% "scala-xml" % "2.4.0"
val scalaParsers = "org.scala-lang.modules" %% "scala-parser-combinators" % "2.4.0" val scalaParsers = "org.scala-lang.modules" %% "scala-parser-combinators" % "2.4.0"
val scalaPar = "org.scala-lang.modules" %% "scala-parallel-collections" % "1.2.0" val scalaPar = "org.scala-lang.modules" %% "scala-parallel-collections" % "1.2.0"
val scalaCollectionCompat = "org.scala-lang.modules" %% "scala-collection-compat" % "2.14.0"
val caffeine = "com.github.ben-manes.caffeine" % "caffeine" % "2.8.5" val caffeine = "com.github.ben-manes.caffeine" % "caffeine" % "2.8.5"
@ -112,7 +113,7 @@ object Dependencies {
// lm-coursier dependencies // lm-coursier dependencies
val dataclassScalafixVersion = "0.3.0" val dataclassScalafixVersion = "0.3.0"
val coursierVersion = "2.1.23" val coursierVersion = "2.1.25-M24"
val coursier = ("io.get-coursier" %% "coursier" % coursierVersion) val coursier = ("io.get-coursier" %% "coursier" % coursierVersion)
.cross(CrossVersion.for3Use2_13) .cross(CrossVersion.for3Use2_13)

View File

@ -0,0 +1,11 @@
@transient
lazy val check = taskKey[Unit]("check")
scalaVersion := "3.8.3-RC1-bin-20260218-bb6fc60-NIGHTLY"
scalaOrganization := "ch.epfl.lara"
libraryDependencies += "com.lihaoyi" %% "fansi" % "0.5.0"
check := {
val cp = (Compile / fullClasspath).value.map(_.data)
cp.foreach: x =>
assert(!x.toString.contains("org/scala-lang"), s"$x contains org/scala-lang")
}

View File

@ -0,0 +1 @@
> check

View File

@ -4,6 +4,16 @@ ThisBuild / scalaVersion := "2.11.12"
libraryDependencies += "org.scala-lang" %% "scala-actors-migration" % "1.1.0" libraryDependencies += "org.scala-lang" %% "scala-actors-migration" % "1.1.0"
libraryDependencies += "org.scala-lang" %% "scala-pickling" % "0.9.1" libraryDependencies += "org.scala-lang" %% "scala-pickling" % "0.9.1"
// This is a workaround for https://github.com/coursier/coursier/issues/3525
// [info] [error] lmcoursier.internal.shaded.coursier.params.rule.SameVersion$CantForceSameVersion:
// Can't force version 2.11.12 for modules org.scala-lang:scala-compiler, org.scala-lang:scala-reflect, org.scala-lang:scala-library
// ....
// [info] [error] Caused by: lmcoursier.internal.shaded.coursier.params.rule.SameVersion$SameVersionConflict:
// Unsatisfied rule SameVersion(HashSet(ModuleMatcher(org.scala-lang:scala-compiler), ModuleMatcher(org.scala-lang:scalap),
// ModuleMatcher(org.scala-lang:scala-actors), ModuleMatcher(org.scala-lang:scala-reflect), ModuleMatcher(org.scala-lang:scala-library))):
// Found versions 2.11.0, 2.11.12 for org.scala-lang:scala-actors, org.scala-lang:scala-compiler, org.scala-lang:scala-library, org.scala-lang:scala-reflect
libraryDependencies += "org.scala-lang" % "scala-actors" % "2.11.12"
lazy val check = taskKey[Unit]("Runs the check") lazy val check = taskKey[Unit]("Runs the check")
check := { check := {

View File

@ -53,6 +53,7 @@ lazy val root = (project in file("."))
"org.jline:jline-terminal-jni", "org.jline:jline-terminal-jni",
"org.reactivestreams:reactive-streams", "org.reactivestreams:reactive-streams",
"org.scala-lang.modules:scala-asm", "org.scala-lang.modules:scala-asm",
"org.scala-lang.modules:scala-collection-compat_3",
"org.scala-lang.modules:scala-parallel-collections_3", "org.scala-lang.modules:scala-parallel-collections_3",
"org.scala-lang.modules:scala-parser-combinators_3", "org.scala-lang.modules:scala-parser-combinators_3",
"org.scala-lang.modules:scala-xml_3", "org.scala-lang.modules:scala-xml_3",