mirror of https://github.com/sbt/sbt.git
[2.x] refactor: Remove Ivy from update path and decouple lm-coursier from lm-ivy (#8832)
- **Remove `ivyModule` from `updateTask0`**: Replace `IvySbt#Module` with `moduleSettings` + `DependencyResolution.moduleDescriptor()`, eliminating the Ivy dependency in the update path. - **Replace direct Ivy usage in `Load.scala` and `TemplateCommandUtil`**: Use Coursier's `DependencyResolution` API for plugin bootstrapping and template resolution instead of constructing `IvySbt` instances directly. - **Break `lm-coursier`'s dependency on `lm-ivy`**: Remove `IvySbt#Module` pattern match from `CoursierDependencyResolution`, replace `IBiblioResolver` usage in `Resolvers` with reflection, and switch build dependencies from `lmIvy` to `lmCore`. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
dfa5e31571
commit
0d01cc0b10
11
build.sbt
11
build.sbt
|
|
@ -1239,7 +1239,7 @@ lazy val lmCoursierDefinitions = project
|
|||
conflictWarning := ConflictWarning.disable,
|
||||
Utils.noPublish,
|
||||
)
|
||||
.dependsOn(lmIvy % "provided")
|
||||
.dependsOn(lmCore % "provided")
|
||||
|
||||
lazy val lmCoursierDependencies = Def.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
|
|
@ -1265,12 +1265,7 @@ lazy val lmCoursier = project
|
|||
contrabandSettings,
|
||||
Compile / sourceGenerators += Utils.dataclassGen(lmCoursierDefinitions).taskValue,
|
||||
)
|
||||
.dependsOn(
|
||||
// We depend on lmIvy rather than just lmCore to handle the ModuleDescriptor
|
||||
// passed to DependencyResolutionInterface.update, which is an IvySbt#Module
|
||||
// (seems DependencyResolutionInterface.moduleDescriptor is ignored).
|
||||
lmIvy
|
||||
)
|
||||
.dependsOn(lmCore)
|
||||
|
||||
lazy val lmCoursierShaded = project
|
||||
.in(file("lm-coursier/target/shaded-module"))
|
||||
|
|
@ -1340,7 +1335,7 @@ lazy val lmCoursierShaded = project
|
|||
oldStrategy(x)
|
||||
}
|
||||
)
|
||||
.dependsOn(lmIvy % "provided")
|
||||
.dependsOn(lmCore % "provided")
|
||||
|
||||
lazy val lmCoursierShadedPublishing = project
|
||||
.in(file("lm-coursier/target/shaded-publishing-module"))
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import lmcoursier.internal.{
|
|||
UpdateRun
|
||||
}
|
||||
import lmcoursier.syntax.*
|
||||
import sbt.internal.librarymanagement.IvySbt
|
||||
import sbt.librarymanagement.*
|
||||
import sbt.util.Logger
|
||||
import coursier.core.{ BomDependency, Dependency, Publication }
|
||||
|
|
@ -139,15 +138,12 @@ class CoursierDependencyResolution(
|
|||
|
||||
val module0 = module match {
|
||||
case c: CoursierModuleDescriptor =>
|
||||
// seems not to happen, not sure what DependencyResolutionInterface.moduleDescriptor is for
|
||||
c.descriptor
|
||||
case i: IvySbt#Module =>
|
||||
i.moduleSettings match {
|
||||
case other =>
|
||||
other.moduleSettings match {
|
||||
case d: ModuleDescriptorConfiguration => d
|
||||
case other => sys.error(s"unrecognized module settings: $other")
|
||||
case s => sys.error(s"unrecognized module settings: $s")
|
||||
}
|
||||
case _ =>
|
||||
sys.error(s"unrecognized ModuleDescriptor type: $module")
|
||||
}
|
||||
|
||||
val so = conf.scalaOrganization
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@ private[lmcoursier] final case class CoursierModuleDescriptor(
|
|||
def scalaModuleInfo: Option[ScalaModuleInfo] =
|
||||
descriptor.scalaModuleInfo
|
||||
|
||||
def moduleSettings: CoursierModuleSettings =
|
||||
CoursierModuleSettings()
|
||||
def moduleSettings: ModuleDescriptorConfiguration =
|
||||
descriptor
|
||||
|
||||
lazy val extraInputHash: Long =
|
||||
conf.##
|
||||
// Exclude log/logger fields — they contain Logger instances with
|
||||
// non-deterministic hashCodes that would break update caching.
|
||||
conf.withLog(None).withLogger(None).##
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
package lmcoursier.internal
|
||||
|
||||
import sbt.librarymanagement.ModuleSettings
|
||||
|
||||
private[lmcoursier] case class CoursierModuleSettings() extends ModuleSettings
|
||||
|
|
@ -7,7 +7,6 @@ import coursier.cache.CacheUrl
|
|||
import coursier.core.{ Authentication, Repository }
|
||||
import coursier.ivy.IvyRepository
|
||||
import coursier.maven.SbtMavenRepository
|
||||
import org.apache.ivy.plugins.resolver.IBiblioResolver
|
||||
import sbt.librarymanagement.*
|
||||
import sbt.util.Logger
|
||||
|
||||
|
|
@ -133,29 +132,42 @@ object Resolvers {
|
|||
|
||||
private object IBiblioRepository {
|
||||
|
||||
// Use reflection to avoid a compile-time dependency on lm-ivy / Apache Ivy.
|
||||
// At runtime the class will be present on the classpath via the main module.
|
||||
private val ibiblioClass: Option[Class[?]] =
|
||||
try Some(Class.forName("org.apache.ivy.plugins.resolver.IBiblioResolver"))
|
||||
catch { case _: ClassNotFoundException => None }
|
||||
|
||||
private def stringVector(v: java.util.List[?]): Vector[String] =
|
||||
Option(v).map(_.asScala.toVector).getOrElse(Vector.empty).collect { case s: String =>
|
||||
s
|
||||
}
|
||||
|
||||
private def patterns(resolver: IBiblioResolver): Patterns = Patterns(
|
||||
ivyPatterns = stringVector(resolver.getIvyPatterns),
|
||||
artifactPatterns = stringVector(resolver.getArtifactPatterns),
|
||||
isMavenCompatible = resolver.isM2compatible,
|
||||
descriptorOptional = !resolver.isUseMavenMetadata,
|
||||
skipConsistencyCheck = !resolver.isCheckconsistency
|
||||
)
|
||||
private def patternsViaReflection(resolver: AnyRef): Patterns =
|
||||
val cls = resolver.getClass
|
||||
Patterns(
|
||||
ivyPatterns = stringVector(
|
||||
cls.getMethod("getIvyPatterns").invoke(resolver).asInstanceOf[java.util.List[?]]
|
||||
),
|
||||
artifactPatterns = stringVector(
|
||||
cls.getMethod("getArtifactPatterns").invoke(resolver).asInstanceOf[java.util.List[?]]
|
||||
),
|
||||
isMavenCompatible = cls.getMethod("isM2compatible").invoke(resolver).asInstanceOf[Boolean],
|
||||
descriptorOptional =
|
||||
!cls.getMethod("isUseMavenMetadata").invoke(resolver).asInstanceOf[Boolean],
|
||||
skipConsistencyCheck =
|
||||
!cls.getMethod("isCheckconsistency").invoke(resolver).asInstanceOf[Boolean],
|
||||
)
|
||||
|
||||
def unapply(r: Resolver): Option[Patterns] =
|
||||
r match {
|
||||
case raw: RawRepository =>
|
||||
raw.resolver match {
|
||||
case b: IBiblioResolver =>
|
||||
Some(patterns(b))
|
||||
ibiblioClass match
|
||||
case Some(cls) if cls.isInstance(raw.resolver) =>
|
||||
Some(patternsViaReflection(raw.resolver))
|
||||
.filter(patternMatchGuard)
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3856,7 +3856,7 @@ object Classpaths {
|
|||
Option[FiniteDuration],
|
||||
Boolean,
|
||||
ProjectRef,
|
||||
IvySbt#Module,
|
||||
ModuleSettings,
|
||||
String,
|
||||
Boolean,
|
||||
Seq[UpdateReport],
|
||||
|
|
@ -3886,7 +3886,7 @@ object Classpaths {
|
|||
forceUpdatePeriod.toTaskable,
|
||||
sbtPlugin.toTaskable,
|
||||
thisProjectRef.toTaskable,
|
||||
ivyModule.toTaskable,
|
||||
moduleSettings.toTaskable,
|
||||
scalaOrganization.toTaskable,
|
||||
(update / skip).toTaskable,
|
||||
transitiveUpdate.toTaskable,
|
||||
|
|
@ -3916,7 +3916,7 @@ object Classpaths {
|
|||
fup,
|
||||
isPlugin,
|
||||
thisRef,
|
||||
im,
|
||||
ms,
|
||||
so,
|
||||
sk,
|
||||
tu,
|
||||
|
|
@ -3979,10 +3979,8 @@ object Classpaths {
|
|||
else Def.displayRelativeReference(extracted.currentRef, thisRef)
|
||||
|
||||
LibraryManagement.cachedUpdate(
|
||||
// LM API
|
||||
lm = lm,
|
||||
// Ivy-free ModuleDescriptor
|
||||
module = im,
|
||||
module = lm.moduleDescriptor(ms.asInstanceOf[ModuleDescriptorConfiguration]),
|
||||
cacheStoreFactory = cacheStoreFactory,
|
||||
label = label,
|
||||
updateConf,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import java.io.File
|
|||
|
||||
import sbt.io.*, syntax.*
|
||||
import sbt.util.*
|
||||
import sbt.internal.librarymanagement.ivy.{ IvyConfiguration, IvyDependencyResolution }
|
||||
import sbt.internal.util.{ ConsoleAppender, Terminal as ITerminal }
|
||||
import sbt.internal.util.complete.{ DefaultParsers, Parser }, DefaultParsers.*
|
||||
import xsbti.AppConfiguration
|
||||
|
|
@ -43,7 +42,7 @@ private[sbt] object TemplateCommandUtil {
|
|||
val infos = s0.get(templateResolverInfos).getOrElse(Nil).toList
|
||||
val log = s0.globalLogging.full
|
||||
val extracted = Project.extract(s0)
|
||||
val (s1, ivyConf) = extracted.runTask(Keys.ivyConfiguration, s0)
|
||||
val (s1, lm) = extracted.runTask(Keys.dependencyResolution, s0)
|
||||
val scalaModuleInfo = extracted.get(Keys.updateSbtClassifiers / Keys.scalaModuleInfo)
|
||||
val templateDescriptions = extracted.get(Keys.templateDescriptions)
|
||||
val args0 = inputArg.toList ++
|
||||
|
|
@ -54,7 +53,7 @@ private[sbt] object TemplateCommandUtil {
|
|||
def terminate = TerminateAction :: s1.copy(remainingCommands = Nil)
|
||||
def reload = "reboot" :: s1.copy(remainingCommands = Nil)
|
||||
if (args0.nonEmpty) {
|
||||
run(infos, args0, s0.configuration, ivyConf, globalBase, scalaModuleInfo, log)
|
||||
run(infos, args0, s0.configuration, lm, globalBase, scalaModuleInfo, log)
|
||||
terminate
|
||||
} else {
|
||||
fortifyArgs(templateDescriptions.toList) match {
|
||||
|
|
@ -63,7 +62,7 @@ private[sbt] object TemplateCommandUtil {
|
|||
extracted.runInputTask(Keys.templateRunLocal, " " + arg, s0)
|
||||
reload
|
||||
case args =>
|
||||
run(infos, args, s0.configuration, ivyConf, globalBase, scalaModuleInfo, log)
|
||||
run(infos, args, s0.configuration, lm, globalBase, scalaModuleInfo, log)
|
||||
terminate
|
||||
}
|
||||
}
|
||||
|
|
@ -73,13 +72,13 @@ private[sbt] object TemplateCommandUtil {
|
|||
infos: List[TemplateResolverInfo],
|
||||
arguments: List[String],
|
||||
config: AppConfiguration,
|
||||
ivyConf: IvyConfiguration,
|
||||
lm: DependencyResolution,
|
||||
globalBase: File,
|
||||
scalaModuleInfo: Option[ScalaModuleInfo],
|
||||
log: Logger
|
||||
): Unit =
|
||||
infos find { info =>
|
||||
val loader = infoLoader(info, config, ivyConf, globalBase, scalaModuleInfo, log)
|
||||
val loader = infoLoader(info, config, lm, globalBase, scalaModuleInfo, log)
|
||||
val hit = tryTemplate(info, arguments, loader)
|
||||
if (hit) {
|
||||
runTemplate(info, arguments, loader)
|
||||
|
|
@ -115,12 +114,12 @@ private[sbt] object TemplateCommandUtil {
|
|||
private def infoLoader(
|
||||
info: TemplateResolverInfo,
|
||||
config: AppConfiguration,
|
||||
ivyConf: IvyConfiguration,
|
||||
lm: DependencyResolution,
|
||||
globalBase: File,
|
||||
scalaModuleInfo: Option[ScalaModuleInfo],
|
||||
log: Logger
|
||||
): ClassLoader = {
|
||||
val cp = classpathForInfo(info, ivyConf, globalBase, scalaModuleInfo, log)
|
||||
val cp = classpathForInfo(info, lm, globalBase, scalaModuleInfo, log)
|
||||
ClasspathUtil.toLoader(cp, config.provider.loader)
|
||||
}
|
||||
|
||||
|
|
@ -146,12 +145,11 @@ private[sbt] object TemplateCommandUtil {
|
|||
// Cache files under ~/.sbt/sbt_version/templates/org_name_version
|
||||
private def classpathForInfo(
|
||||
info: TemplateResolverInfo,
|
||||
ivyConf: IvyConfiguration,
|
||||
lm: DependencyResolution,
|
||||
globalBase: File,
|
||||
scalaModuleInfo: Option[ScalaModuleInfo],
|
||||
log: Logger
|
||||
): List[Path] = {
|
||||
val lm = IvyDependencyResolution(ivyConf)
|
||||
val templatesBaseDirectory = new File(globalBase, "templates")
|
||||
val templateId = s"${info.module.organization}_${info.module.name}_${info.module.revision}"
|
||||
val templateDirectory = new File(templatesBaseDirectory, templateId)
|
||||
|
|
|
|||
|
|
@ -20,14 +20,15 @@ import sbt.SlashSyntax0.*
|
|||
import sbt.internal.BuildStreams.*
|
||||
import sbt.internal.inc.classpath.ClasspathUtil
|
||||
import sbt.internal.inc.{ MappedFileConverter, ScalaInstance, ZincLmUtil, ZincUtil }
|
||||
import sbt.internal.librarymanagement.ivy.{ InlineIvyConfiguration, IvyDependencyResolution }
|
||||
import lmcoursier.{ CoursierConfiguration, CoursierDependencyResolution }
|
||||
import lmcoursier.syntax.*
|
||||
import sbt.internal.util.Attributed.data
|
||||
import sbt.internal.util.Types.const
|
||||
import sbt.internal.util.Attributed
|
||||
import sbt.internal.util.appmacro.ContextUtil
|
||||
import sbt.internal.server.BuildServerEvalReporter
|
||||
import sbt.io.{ GlobFilter, IO }
|
||||
import sbt.librarymanagement.{ Configuration, Configurations, IvyPaths, Resolver, ScalaArtifacts }
|
||||
import sbt.librarymanagement.{ Configuration, Configurations, Resolver, ScalaArtifacts }
|
||||
import sbt.nio.Settings
|
||||
import sbt.util.{ Logger, Show }
|
||||
import xsbti.{ FileConverter, HashedVirtualFileRef, VirtualFile }
|
||||
|
|
@ -87,14 +88,10 @@ private[sbt] object Load {
|
|||
val classpath = Attributed.blankSeq(
|
||||
cp0.map(_.toPath).map(p => converter.toVirtualFile(p): HashedVirtualFileRef)
|
||||
)
|
||||
val ivyConfiguration =
|
||||
InlineIvyConfiguration()
|
||||
.withPaths(
|
||||
IvyPaths(baseDirectory.toString, bootIvyHome(state.configuration).map(_.toString))
|
||||
)
|
||||
.withResolvers(Resolver.combineDefaultResolvers(Vector.empty))
|
||||
.withLog(log)
|
||||
val dependencyResolution = IvyDependencyResolution(ivyConfiguration)
|
||||
val csrConfig = CoursierConfiguration()
|
||||
.withResolvers(Resolver.combineDefaultResolvers(Vector.empty).toVector)
|
||||
.withLog(log)
|
||||
val dependencyResolution = CoursierDependencyResolution(csrConfig)
|
||||
val si = ScalaInstance(scalaProvider.version, scalaProvider.launcher)
|
||||
val zincDir = BuildPaths.getZincDirectory(state, globalBase)
|
||||
val classpathOptions = ClasspathOptionsUtil.noboot(si.version)
|
||||
|
|
@ -140,13 +137,6 @@ private[sbt] object Load {
|
|||
)
|
||||
}
|
||||
|
||||
private def bootIvyHome(app: xsbti.AppConfiguration): Option[File] =
|
||||
try {
|
||||
Option(app.provider.scalaProvider.launcher.ivyHome)
|
||||
} catch {
|
||||
case _: NoSuchMethodError => None
|
||||
}
|
||||
|
||||
def injectGlobal(state: State): Seq[Setting[?]] =
|
||||
((GlobalScope / appConfiguration) :== state.configuration) +:
|
||||
LogManager.settingsLogger(state) +:
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ lazy val checkPom = taskKey[Unit]("check pom to ensure no <type> sections are ge
|
|||
|
||||
lazy val root = (project in file(".")).
|
||||
settings(
|
||||
scalaVersion := "2.10.6",
|
||||
libraryDependencies += { ("org.scala-tools.sbinary" %% "sbinary" % "0.4.1").withSources().withJavadoc() },
|
||||
libraryDependencies += { ("org.scala-sbt" % "io" % "0.13.8").intransitive() },
|
||||
scalaVersion := "2.13.16",
|
||||
libraryDependencies += { ("com.typesafe" % "config" % "1.4.3").withSources().withJavadoc() },
|
||||
libraryDependencies += { ("org.slf4j" % "slf4j-api" % "2.0.16").intransitive() },
|
||||
checkPom := {
|
||||
val converter = fileConverter.value
|
||||
val pomFile = makePom.value
|
||||
|
|
@ -16,8 +16,7 @@ lazy val root = (project in file(".")).
|
|||
val ur = update.value
|
||||
val dir = (update / streams).value.cacheDirectory / "out"
|
||||
val lines = IO.readLines(dir)
|
||||
val hasError = lines exists { line => line contains "Found intransitive dependency "}
|
||||
val hasError = lines.exists(line => line.contains("Found intransitive dependency "))
|
||||
assert(hasError, s"Failed to detect intransitive dependencies, got: ${lines.mkString("\n")}")
|
||||
},
|
||||
resolvers += Resolver.typesafeIvyRepo("releases")
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue