Merge pull request #409 from adpi2/sbt-plugins-maven-path

Try resolve sbt plugin from valid Maven pattern
This commit is contained in:
adpi2 2023-02-23 09:13:28 +01:00 committed by GitHub
commit 67ee08e964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 162 additions and 25 deletions

View File

@ -27,10 +27,16 @@ import org.apache.ivy.plugins.resolver.{
import org.apache.ivy.plugins.repository.url.{ URLRepository => URLRepo }
import org.apache.ivy.plugins.repository.file.{ FileResource, FileRepository => FileRepo }
import java.io.{ File, IOException }
import java.util.Date
import org.apache.ivy.util.{ ChecksumHelper, FileUtil, Message }
import org.apache.ivy.core.module.descriptor.{ Artifact => IArtifact }
import org.apache.ivy.core.module.id.ModuleRevisionId
import org.apache.ivy.core.module.descriptor.DefaultArtifact
import org.apache.ivy.core.report.DownloadReport
import org.apache.ivy.plugins.resolver.util.{ ResolvedResource, ResourceMDParser }
import org.apache.ivy.util.{ ChecksumHelper, FileUtil, Message }
import scala.collection.JavaConverters._
import sbt.internal.librarymanagement.mavenint.PomExtraDependencyAttributes
import sbt.io.IO
import sbt.util.Logger
import sbt.librarymanagement._
@ -173,6 +179,32 @@ private[sbt] object ConvertResolver {
setArtifactPatterns(pattern)
setIvyPatterns(pattern)
}
override protected def findResourceUsingPattern(
mrid: ModuleRevisionId,
pattern: String,
artifact: IArtifact,
rmdparser: ResourceMDParser,
date: Date
): ResolvedResource = {
val extraAttributes =
mrid.getExtraAttributes.asScala.toMap.asInstanceOf[Map[String, String]]
getSbtPluginCrossVersion(extraAttributes) match {
case Some(sbtCrossVersion) =>
// if the module is an sbt plugin
// we first try to resolve the artifact with the sbt cross version suffix
// and we fallback to the one without the suffix
val newArtifact = DefaultArtifact.cloneWithAnotherName(
artifact,
artifact.getName + sbtCrossVersion
)
val resolved =
super.findResourceUsingPattern(mrid, pattern, newArtifact, rmdparser, date)
if (resolved != null) resolved
else super.findResourceUsingPattern(mrid, pattern, artifact, rmdparser, date)
case None =>
super.findResourceUsingPattern(mrid, pattern, artifact, rmdparser, date)
}
}
}
val resolver = new PluginCapableResolver
if (repo.localIfFile) resolver.setRepository(new LocalIfFileRepo)
@ -230,6 +262,13 @@ private[sbt] object ConvertResolver {
}
}
private def getSbtPluginCrossVersion(extraAttributes: Map[String, String]): Option[String] = {
for {
sbtVersion <- extraAttributes.get(PomExtraDependencyAttributes.SbtVersionKey)
scalaVersion <- extraAttributes.get(PomExtraDependencyAttributes.ScalaVersionKey)
} yield s"_${scalaVersion}_$sbtVersion"
}
private sealed trait DescriptorRequired extends BasicResolver {
// Works around implementation restriction to access protected method `get`
def getResource(resource: Resource, dest: File): Long

View File

@ -75,6 +75,31 @@ object CustomPomParser {
private[this] val unqualifiedKeys =
Set(SbtVersionKey, ScalaVersionKey, ExtraAttributesKey, ApiURLKey, VersionSchemeKey)
/** In the new POM format of sbt plugins, the dependency to an sbt plugin
* contains the sbt cross-version _2.12_1.0. The reason is we want Maven to be able
* to resolve the dependency using the pattern:
* <org>/<artifact-name>_2.12_1.0/<version>/<artifact-name>_2.12_1.0-<version>.pom
* In sbt 1.x we use extra-attributes to resolve sbt plugins, so here we must remove
* the sbt cross-version and keep the extra-attributes.
* Parsing a dependency found in the new POM format produces the same module as
* if it is found in the old POM format. It used not to contain the sbt cross-version
* suffix, but that was invalid.
* Hence we can resolve conflicts between new and old POM formats.
*
* To compare the two formats you can look at the POMs in:
* https://repo1.maven.org/maven2/ch/epfl/scala/sbt-plugin-example-diamond_2.12_1.0/0.5.0/
*/
private def removeSbtCrossVersion(
properties: Map[String, String],
moduleName: String
): String = {
val sbtCrossVersion = for {
sbtVersion <- properties.get(s"e:$SbtVersionKey")
scalaVersion <- properties.get(s"e:$ScalaVersionKey")
} yield s"_${scalaVersion}_$sbtVersion"
sbtCrossVersion.map(moduleName.stripSuffix).getOrElse(moduleName)
}
// packagings that should be jars, but that Ivy doesn't handle as jars
// TODO - move this elsewhere.
val JarPackagings = Set("eclipse-plugin", "hk2-jar", "orbit", "scala-jar")
@ -163,9 +188,12 @@ object CustomPomParser {
import collection.JavaConverters._
val oldExtra = qualifiedExtra(id)
val newExtra = (oldExtra ++ properties).asJava
// remove the sbt plugin cross version from the resolved ModuleRevisionId
// sbt-plugin-example_2.12_1.0 => sbt-plugin-example
val nameWithoutCrossVersion = removeSbtCrossVersion(properties, id.getName)
ModuleRevisionId.newInstance(
id.getOrganisation,
id.getName,
nameWithoutCrossVersion,
id.getBranch,
id.getRevision,
newExtra

View File

@ -219,9 +219,28 @@ final class IvySbt(
else IvySbt.cachedResolutionResolveCache.clean()
}
final class Module(rawModuleSettings: ModuleSettings)
/**
* In the new POM format of sbt plugins, we append the sbt-cross version _2.12_1.0 to
* the module artifactId, and the artifactIds of its dependencies that are sbt plugins.
*
* The goal is to produce a valid Maven POM, a POM that Maven can resolve:
* Maven will try and succeed to resolve the POM of pattern:
* <org>/<artifact-name>_2.12_1.0/<version>/<artifact-name>_2.12_1.0-<version>.pom
*/
final class Module(rawModuleSettings: ModuleSettings, appendSbtCrossVersion: Boolean)
extends sbt.librarymanagement.ModuleDescriptor { self =>
val moduleSettings: ModuleSettings = IvySbt.substituteCross(rawModuleSettings)
def this(rawModuleSettings: ModuleSettings) =
this(rawModuleSettings, appendSbtCrossVersion = false)
val moduleSettings: ModuleSettings =
rawModuleSettings match {
case ic: InlineConfiguration =>
val icWithCross = IvySbt.substituteCross(ic)
if (appendSbtCrossVersion) IvySbt.appendSbtCrossVersion(icWithCross)
else icWithCross
case m => m
}
def directDependencies: Vector[ModuleID] =
moduleSettings match {
@ -696,32 +715,44 @@ private[sbt] object IvySbt {
)
}
private def substituteCross(m: ModuleSettings): ModuleSettings = {
m.scalaModuleInfo match {
case None => m
case Some(is) => substituteCross(m, is.scalaFullVersion, is.scalaBinaryVersion)
private def substituteCross(ic: InlineConfiguration): InlineConfiguration = {
ic.scalaModuleInfo match {
case None => ic
case Some(is) => substituteCross(ic, is.scalaFullVersion, is.scalaBinaryVersion)
}
}
private def substituteCross(
m: ModuleSettings,
ic: InlineConfiguration,
scalaFullVersion: String,
scalaBinaryVersion: String
): ModuleSettings = {
m match {
case ic: InlineConfiguration =>
val applyCross = CrossVersion(scalaFullVersion, scalaBinaryVersion)
def propagateCrossVersion(moduleID: ModuleID): ModuleID = {
val crossExclusions: Vector[ExclusionRule] =
moduleID.exclusions.map(CrossVersion.substituteCross(_, ic.scalaModuleInfo))
applyCross(moduleID)
.withExclusions(crossExclusions)
}
ic.withModule(applyCross(ic.module))
.withDependencies(ic.dependencies.map(propagateCrossVersion))
.withOverrides(ic.overrides map applyCross)
case _ => m
): InlineConfiguration = {
val applyCross = CrossVersion(scalaFullVersion, scalaBinaryVersion)
def propagateCrossVersion(moduleID: ModuleID): ModuleID = {
val crossExclusions: Vector[ExclusionRule] =
moduleID.exclusions.map(CrossVersion.substituteCross(_, ic.scalaModuleInfo))
applyCross(moduleID)
.withExclusions(crossExclusions)
}
ic.withModule(applyCross(ic.module))
.withDependencies(ic.dependencies.map(propagateCrossVersion))
.withOverrides(ic.overrides map applyCross)
}
private def appendSbtCrossVersion(ic: InlineConfiguration): InlineConfiguration =
ic.withModule(appendSbtCrossVersion(ic.module))
.withDependencies(ic.dependencies.map(appendSbtCrossVersion))
.withOverrides(ic.overrides.map(appendSbtCrossVersion))
private def appendSbtCrossVersion(mid: ModuleID): ModuleID = {
val crossVersion = for {
scalaVersion <- mid.extraAttributes.get("e:scalaVersion")
sbtVersion <- mid.extraAttributes.get("e:sbtVersion")
} yield s"_${scalaVersion}_$sbtVersion"
crossVersion
.filter(!mid.name.endsWith(_))
.map(cv => mid.withName(mid.name + cv))
.getOrElse(mid)
}
private def toIvyArtifact(

View File

@ -37,7 +37,8 @@ trait BaseIvySpecification extends AbstractEngineSpec {
deps: Vector[ModuleID],
scalaFullVersion: Option[String],
uo: UpdateOptions = UpdateOptions(),
overrideScalaVersion: Boolean = true
overrideScalaVersion: Boolean = true,
appendSbtCrossVersion: Boolean = false
): IvySbt#Module = {
val scalaModuleInfo = scalaFullVersion map { fv =>
ScalaModuleInfo(
@ -55,7 +56,7 @@ trait BaseIvySpecification extends AbstractEngineSpec {
.withConfigurations(configurations)
.withScalaModuleInfo(scalaModuleInfo)
val ivySbt = new IvySbt(mkIvyConfiguration(uo))
new ivySbt.Module(moduleSetting)
new ivySbt.Module(moduleSetting, appendSbtCrossVersion)
}
def resolvers: Vector[Resolver] = Vector(Resolver.mavenCentral)

View File

@ -0,0 +1,38 @@
package sbt.internal.librarymanagement
import sbt.internal.librarymanagement.mavenint.PomExtraDependencyAttributes.{
SbtVersionKey,
ScalaVersionKey
}
import sbt.librarymanagement.{ CrossVersion, ModuleDescriptorConfiguration }
object IvyModuleSpec extends BaseIvySpecification {
test("The Scala binary version of a Scala module should be appended to its name") {
val m = module(
defaultModuleId.withCrossVersion(CrossVersion.Binary()),
Vector.empty,
Some("2.13.10")
)
m.moduleSettings match {
case configuration: ModuleDescriptorConfiguration =>
assert(configuration.module.name == "foo_2.13")
case _ => fail()
}
}
test("The sbt cross-version should be appended to the name of an sbt plugin") {
val m = module(
defaultModuleId.extra(SbtVersionKey -> "1.0", ScalaVersionKey -> "2.12"),
Vector.empty,
Some("2.12.17"),
appendSbtCrossVersion = true
)
m.moduleSettings match {
case configuration: ModuleDescriptorConfiguration =>
assert(configuration.module.name == "foo_2.12_1.0")
case _ => fail()
}
}
}