[2.x] fix: Fixes the local artifact handling in updateSbtClassifiers task (#8734)

- this should also work for global plugins once they are fixed in sbt 2.x
- add `missingOk` support in classifier resolution to enable failure-tolerant artifact retrieval
This commit is contained in:
azdrojowa123 2026-02-13 18:15:36 +01:00 committed by GitHub
parent 0cae58403f
commit 9ca4f186f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 142 additions and 3 deletions

View File

@ -3633,6 +3633,7 @@ object Classpaths {
.withArtifactFilter(
updateConfig0.artifactFilter.map(af => af.withInverted(!af.inverted))
)
.withMissingOk(true)
val app = appConfiguration.value
val srcTypes = sourceArtifactTypes.value
val docTypes = docArtifactTypes.value
@ -3718,11 +3719,14 @@ object Classpaths {
val ref = thisProjectRef.value
val unit = loadedBuild.value.units(ref.build).unit
val converter = unit.converter
val pluginClasspath = unit.plugins.pluginData.dependencyClasspath.toVector
val pluginData = unit.plugins.pluginData
val pluginInternalCp = pluginData.internalDependencyClasspath.toVector
val pluginClasspath = pluginData.dependencyClasspath.toVector
val cp = pluginClasspath.diff(pluginInternalCp)
// Exclude directories: an approximation to whether they've been published
// Note: it might be a redundant legacy from sbt 0.13/1.x times where the classpath contained directories
// but it's left just in case
val pluginJars = pluginClasspath.filter: x =>
val pluginJars = cp.filter: x =>
!Files.isDirectory(converter.toPath(x.data))
val pluginIDs: Vector[ModuleID] = pluginJars.flatMap(_.get(moduleIDStr).map: str =>
moduleIdJsonKeyFormat.read(str))

View File

@ -142,6 +142,10 @@ object EvaluateTaskConfig {
) extends EvaluateTaskConfig
}
/**
* @param internalDependencyClasspath internal classpath entries from the metabuild that are used to exclude
* them when resolving/retrieving classifiers for sbt.
*/
final case class PluginData(
dependencyClasspath: Def.Classpath,
definitionClasspath: Def.Classpath,
@ -155,13 +159,28 @@ final case class PluginData(
managedSources: Seq[File],
buildTarget: Option[BuildTargetIdentifier],
converter: FileConverter,
internalDependencyClasspath: Def.Classpath,
) {
val classpath: Def.Classpath = definitionClasspath ++ dependencyClasspath
}
object PluginData {
private[sbt] def apply(dependencyClasspath: Def.Classpath, converter: FileConverter): PluginData =
PluginData(dependencyClasspath, Nil, None, None, Nil, Nil, Nil, Nil, Nil, Nil, None, converter)
PluginData(
dependencyClasspath,
Nil,
None,
None,
Nil,
Nil,
Nil,
Nil,
Nil,
Nil,
None,
converter,
Nil
)
}
object EvaluateTask {

View File

@ -1381,6 +1381,7 @@ private[sbt] object Load {
isMetaBuild :== true,
pluginData := Def.uncached {
val prod = (Configurations.Runtime / exportedProducts).value
val internalCp = (Configurations.Runtime / internalDependencyClasspath).value
val cp = (Configurations.Runtime / fullClasspath).value
val opts = (Configurations.Compile / scalacOptions).value
val javaOpts = (Configurations.Compile / javacOptions).value
@ -1403,6 +1404,7 @@ private[sbt] object Load {
managedSrcs,
Some(buildTarget),
converter,
internalCp,
)
},
onLoadMessage := ("loading project definition from " + baseDirectory.value)
@ -1670,6 +1672,7 @@ final case class LoadBuildConfiguration(
Nil,
None,
converter,
Nil
)
case None => PluginData(globalPluginClasspath, converter)
}

View File

@ -0,0 +1,39 @@
lazy val root = (project in file("."))
.settings(
// Verify that local plugins work
TaskKey[Unit]("checkLocalPlugins") := Def.uncached {
val localResult = localPluginCheck.value
val customResult = customPluginCheck.value
assert(localResult == "local-plugin-active", s"Expected local plugin to be active, got: $localResult")
assert(customResult == "custom plugin", s"Expected custom plugin to be active, got: $customResult")
},
// Verify that the dependencies in updateSbtClassifiers / classifiersModule do not include the local plugins but do include
// other declared dependencies
TaskKey[Unit]("checkClassifiersModule") := Def.uncached {
val mod = (updateSbtClassifiers / classifiersModule).value
val deps = mod.dependencies
val actual = deps.map(m => s"${m.organization}:${m.name}").sorted.toSet
val expected = Set(
"org.scala-sbt:sbt",
"junit:junit",
"com.eed3si9n:sbt-buildinfo_sbt2_3",
"org.hamcrest:hamcrest-core",
"com.eed3si9n.manifesto:manifesto_3",
"org.scala-lang:scala3-library_3",
"org.scala-lang:scala-library",
"org.typelevel:cats-core_3",
"org.typelevel:cats-kernel_3"
)
assert(
actual == expected,
s"""
|ClassifiersModule dependencies mismatch.
|Expected: ${expected.mkString(", ")}
|Actual: ${actual.mkString(", ")}
""".stripMargin
)
}
)

View File

@ -0,0 +1,17 @@
import sbt.*
import sbt.Keys.*
object LocalPlugin extends AutoPlugin {
override def requires: Plugins = plugins.JvmPlugin
override def trigger: PluginTrigger = allRequirements
object autoImport {
val localPluginCheck = taskKey[String]("A task provided by the local plugin")
}
import autoImport.*
override lazy val projectSettings: Seq[Setting[?]] = Seq(
localPluginCheck := "local-plugin-active"
)
}

View File

@ -0,0 +1,16 @@
lazy val meta = (project in file("."))
.settings(
// Just to make the test more comprehensive and check whether the additional libraries/plugins
// are present in updateSbtClassifiers/classifiersModule.
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1"),
libraryDependencies += "junit" % "junit" % "4.13.2"
)
.dependsOn(customPlugin)
lazy val customPlugin = (project in file("custom"))
.enablePlugins(SbtPlugin)
.settings(
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.13.0",
)
)

View File

@ -0,0 +1,20 @@
import sbt.*
import sbt.Keys.*
object CustomPlugin extends AutoPlugin {
override def requires: Plugins = plugins.JvmPlugin
override def trigger: PluginTrigger = allRequirements
object autoImport {
val customPluginCheck = taskKey[String]("A task provided by the custom plugin")
}
import autoImport.*
override lazy val projectSettings: Seq[Setting[?]] = Seq(
customPluginCheck := {
import cats.implicits.*
List("custom", " ", "plugin").combineAll
}
)
}

View File

@ -0,0 +1,8 @@
# Verify the local plugins are loaded and active
> checkLocalPlugins
# Verify the local plugins are not included in the classifiersModule dependencies
> checkClassifiersModule
# Also verify updateSbtClassifiers itself succeeds
> updateSbtClassifiers

View File

@ -0,0 +1,11 @@
lazy val root = (project in file("."))
.settings(
// Inject a non-existent module into the classifiers module dependencies
// to simulate a scenario where classifier artifacts can't be downloaded.
// With missingOk=true, updateSbtClassifiers should still succeed.
updateSbtClassifiers / classifiersModule := {
val mod = (updateSbtClassifiers / classifiersModule).value
val fakeModule = "com.example.nonexistent" % "fake-library" % "0.0.1"
mod.withDependencies(mod.dependencies :+ fakeModule)
},
)

View File

@ -0,0 +1 @@
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1")

View File

@ -0,0 +1 @@
> updateSbtClassifiers