mirror of https://github.com/sbt/sbt.git
Smooth transition to valid Maven pattern of sbt plugin
For an sbt plugin, we publish two POM files, the legacy one, and the new Maven compatible one. The name of the new POM file contains the sbt cross-version _2.12_1.0. The format of the new POM file is also slightly different, because we append the sbt cross-version to all artifactIds of sbt plugins. Hence Maven can resolve the new sbt plugin POM and its dependencies. When resolving an sbt plugin, we first try to resolve the new Maven POM and if it fails we fallback on the legacy one. When parsing the new POM format, we remove the sbt cross-version from all artifact IDs so that there is no mismatch between old and new format of dependencies.
This commit is contained in:
parent
8e64caae8f
commit
8020ec4d7c
|
|
@ -2827,9 +2827,52 @@ object Classpaths {
|
|||
|
||||
val jvmPublishSettings: Seq[Setting[_]] = Seq(
|
||||
artifacts := artifactDefs(defaultArtifactTasks).value,
|
||||
packagedArtifacts := packaged(defaultArtifactTasks).value
|
||||
packagedArtifacts := packaged(defaultArtifactTasks).value ++
|
||||
Def
|
||||
.ifS(sbtPlugin.toTask)(mavenArtifactsOfSbtPlugin)(Def.task(Map.empty[Artifact, File]))
|
||||
.value
|
||||
) ++ RemoteCache.projectSettings
|
||||
|
||||
/**
|
||||
* Produces the Maven-compatible artifacts of an sbt plugin.
|
||||
* It adds the sbt-cross version suffix into the artifact names, and it generates a
|
||||
* valid POM file, that is a POM file that Maven can resolve.
|
||||
*/
|
||||
private def mavenArtifactsOfSbtPlugin: Def.Initialize[Task[Map[Artifact, File]]] =
|
||||
Def.ifS(publishMavenStyle.toTask)(Def.task {
|
||||
val crossVersion = sbtCrossVersion.value
|
||||
val legacyArtifact = (makePom / artifact).value
|
||||
val pom = makeMavenPomOfSbtPlugin.value
|
||||
val legacyPackages = packaged(defaultPackages).value
|
||||
|
||||
def addSuffix(a: Artifact): Artifact = a.withName(crossVersion(a.name))
|
||||
val packages = legacyPackages.map { case (artifact, file) => addSuffix(artifact) -> file }
|
||||
packages + (addSuffix(legacyArtifact) -> pom)
|
||||
})(Def.task(Map.empty))
|
||||
|
||||
private def sbtCrossVersion: Def.Initialize[String => String] = Def.setting {
|
||||
val sbtV = sbtBinaryVersion.value
|
||||
val scalaV = scalaBinaryVersion.value
|
||||
name => name + s"_${scalaV}_$sbtV"
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a POM file that Maven can resolve.
|
||||
* It appends the sbt cross version into all artifactIds of sbt plugins
|
||||
* (the main one and the dependencies).
|
||||
*/
|
||||
private def makeMavenPomOfSbtPlugin: Def.Initialize[Task[File]] = Def.task {
|
||||
val config = makePomConfiguration.value
|
||||
val nameWithCross = sbtCrossVersion.value(artifact.value.name)
|
||||
val version = Keys.version.value
|
||||
val pomFile = config.file.get.getParentFile / s"$nameWithCross-$version.pom"
|
||||
val publisher = Keys.publisher.value
|
||||
val ivySbt = Keys.ivySbt.value
|
||||
val module = new ivySbt.Module(moduleSettings.value, appendSbtCrossVersion = true)
|
||||
publisher.makePomFile(module, config.withFile(pomFile), streams.value.log)
|
||||
pomFile
|
||||
}
|
||||
|
||||
val ivyPublishSettings: Seq[Setting[_]] = publishGlobalDefaults ++ Seq(
|
||||
artifacts :== Nil,
|
||||
packagedArtifacts :== Map.empty,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
> v1/checkUpdate
|
||||
> v2/checkUpdate
|
||||
> v3/checkUpdate
|
||||
> v4/checkUpdate
|
||||
> v5/checkUpdate
|
||||
> v1 / checkUpdate
|
||||
> v2 / checkUpdate
|
||||
> v3 / checkUpdate
|
||||
> v4 / checkUpdate
|
||||
> v5 / checkUpdate
|
||||
|
||||
> set ThisBuild/useCoursier:=true
|
||||
> v1/checkUpdate
|
||||
> v2/checkUpdate
|
||||
> v3/checkUpdate
|
||||
> v4/checkUpdate
|
||||
> v5/checkUpdate
|
||||
> set ThisBuild / useCoursier:=false
|
||||
> v1 / checkUpdate
|
||||
> v2 / checkUpdate
|
||||
> v3 / checkUpdate
|
||||
> v4 / checkUpdate
|
||||
> v5 / checkUpdate
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
import scala.util.matching.Regex
|
||||
|
||||
lazy val repo = file("test-repo")
|
||||
lazy val resolver = Resolver.file("test-repo", repo)
|
||||
|
||||
lazy val example = project.in(file("example"))
|
||||
.enablePlugins(SbtPlugin)
|
||||
.settings(
|
||||
organization := "org.example",
|
||||
addSbtPlugin("ch.epfl.scala" % "sbt-plugin-example-diamond" % "0.5.0"),
|
||||
publishTo := Some(resolver),
|
||||
checkPackagedArtifacts := checkPackagedArtifactsDef.value,
|
||||
checkPublish := checkPublishDef.value
|
||||
)
|
||||
|
||||
lazy val testMaven = project.in(file("test-maven"))
|
||||
.settings(
|
||||
addSbtPlugin("org.example" % "example" % "0.1.0-SNAPSHOT"),
|
||||
externalResolvers -= Resolver.defaultLocal,
|
||||
resolvers += {
|
||||
val base = (ThisBuild / baseDirectory).value
|
||||
MavenRepository("test-repo", s"file://$base/test-repo")
|
||||
},
|
||||
checkUpdate := checkUpdateDef(
|
||||
"example_2.12_1.0-0.1.0-SNAPSHOT.jar",
|
||||
"sbt-plugin-example-diamond_2.12_1.0-0.5.0.jar",
|
||||
"sbt-plugin-example-left_2.12_1.0-0.3.0.jar",
|
||||
"sbt-plugin-example-right_2.12_1.0-0.3.0.jar",
|
||||
"sbt-plugin-example-bottom_2.12_1.0-0.3.0.jar",
|
||||
).value
|
||||
)
|
||||
|
||||
lazy val testLocal = project.in(file("test-local"))
|
||||
.settings(
|
||||
addSbtPlugin("org.example" % "example" % "0.1.0-SNAPSHOT"),
|
||||
checkUpdate := checkUpdateDef(
|
||||
"example.jar", // resolved from local repository
|
||||
"sbt-plugin-example-diamond_2.12_1.0-0.5.0.jar",
|
||||
"sbt-plugin-example-left_2.12_1.0-0.3.0.jar",
|
||||
"sbt-plugin-example-right_2.12_1.0-0.3.0.jar",
|
||||
"sbt-plugin-example-bottom_2.12_1.0-0.3.0.jar",
|
||||
).value
|
||||
)
|
||||
|
||||
lazy val checkPackagedArtifacts = taskKey[Unit]("check the packaged artifacts")
|
||||
lazy val checkPublish = taskKey[Unit]("check publish")
|
||||
lazy val checkUpdate = taskKey[Unit]("check update")
|
||||
|
||||
def checkPackagedArtifactsDef: Def.Initialize[Task[Unit]] = Def.task {
|
||||
val packagedArtifacts = Keys.packagedArtifacts.value
|
||||
val deprecatedArtifacts = packagedArtifacts.keys.filter(a => a.name == "example")
|
||||
assert(deprecatedArtifacts.size == 4)
|
||||
|
||||
val artifactsWithCrossVersion = packagedArtifacts.keys.filter(a => a.name == "example_2.12_1.0")
|
||||
assert(artifactsWithCrossVersion.size == 4)
|
||||
|
||||
val deprecatedPom = deprecatedArtifacts.find(_.`type` == "pom")
|
||||
assert(deprecatedPom.isDefined)
|
||||
val deprecatedPomContent = IO.read(packagedArtifacts(deprecatedPom.get))
|
||||
assert(deprecatedPomContent.contains(s"<artifactId>example</artifactId>"))
|
||||
assert(deprecatedPomContent.contains(s"<artifactId>sbt-plugin-example-diamond</artifactId>"))
|
||||
|
||||
val pomWithCrossVersion = artifactsWithCrossVersion.find(_.`type` == "pom")
|
||||
assert(pomWithCrossVersion.isDefined)
|
||||
val pomContent = IO.read(packagedArtifacts(pomWithCrossVersion.get))
|
||||
assert(pomContent.contains(s"<artifactId>example_2.12_1.0</artifactId>"))
|
||||
assert(pomContent.contains(s"<artifactId>sbt-plugin-example-diamond_2.12_1.0</artifactId>"))
|
||||
}
|
||||
|
||||
def checkPublishDef: Def.Initialize[Task[Unit]] = Def.task {
|
||||
val _ = publish.value
|
||||
val org = organization.value
|
||||
val files = IO.listFiles(repo / org.replace('.', '/') / "example_2.12_1.0" / "0.1.0-SNAPSHOT")
|
||||
|
||||
assert(files.nonEmpty)
|
||||
|
||||
val Deprecated = s"example-${Regex.quote("0.1.0-SNAPSHOT")}(-javadoc|-sources)?(\\.jar|\\.pom)".r
|
||||
val WithCrossVersion = s"example${Regex.quote("_2.12_1.0")}-${Regex.quote("0.1.0-SNAPSHOT")}(-javadoc|-sources)?(\\.jar|\\.pom)".r
|
||||
|
||||
val deprecatedJars = files.map(_.name).collect { case jar @ Deprecated(_, ".jar") => jar }
|
||||
assert(deprecatedJars.size == 3, deprecatedJars.mkString(", ")) // bin, sources and javadoc
|
||||
|
||||
val jarsWithCrossVersion = files.map(_.name).collect { case jar @ WithCrossVersion(_, ".jar") => jar }
|
||||
assert(jarsWithCrossVersion.size == 3, jarsWithCrossVersion.mkString(", ")) // bin, sources and javadoc
|
||||
|
||||
val deprecatedPom = files
|
||||
.find { file =>
|
||||
file.name match {
|
||||
case pom @ Deprecated(_, ".pom") => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
assert(deprecatedPom.isDefined, "missing deprecated pom")
|
||||
val deprecatedPomContent = IO.read(deprecatedPom.get)
|
||||
assert(deprecatedPomContent.contains(s"<artifactId>example</artifactId>"))
|
||||
assert(deprecatedPomContent.contains(s"<artifactId>sbt-plugin-example-diamond</artifactId>"))
|
||||
|
||||
val pomWithCrossVersion = files
|
||||
.find { file =>
|
||||
file.name match {
|
||||
case pom @ WithCrossVersion(_, ".pom") => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
assert(pomWithCrossVersion.isDefined, "missing pom with sbt cross-version _2.12_1.0")
|
||||
val pomContent = IO.read(pomWithCrossVersion.get)
|
||||
assert(pomContent.contains(s"<artifactId>example_2.12_1.0</artifactId>"))
|
||||
assert(pomContent.contains(s"<artifactId>sbt-plugin-example-diamond_2.12_1.0</artifactId>"))
|
||||
}
|
||||
|
||||
def checkUpdateDef(expected: String*): Def.Initialize[Task[Unit]] = Def.task {
|
||||
val report = update.value
|
||||
val obtainedFiles = report.configurations
|
||||
.find(_.configuration.name == Compile.name)
|
||||
.toSeq
|
||||
.flatMap(_.modules)
|
||||
.flatMap(_.artifacts)
|
||||
.map(_._2)
|
||||
val obtainedSet = obtainedFiles.map(_.getName).toSet
|
||||
val expectedSet = expected.toSet + "scala-library.jar"
|
||||
assert(obtainedSet == expectedSet, obtainedFiles)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
> example / checkPackagedArtifacts
|
||||
|
||||
> example / checkPublish
|
||||
> testMaven / checkUpdate
|
||||
> set testMaven / useCoursier := false
|
||||
> testMaven / checkUpdate
|
||||
|
||||
> example / publishLocal
|
||||
> testLocal / checkUpdate
|
||||
> set testLocal / useCoursier := false
|
||||
> testLocal / checkUpdate
|
||||
Loading…
Reference in New Issue