mirror of https://github.com/sbt/sbt.git
Merge pull request #7096 from adpi2/sbt-plugins-maven-path
[1.9.x] Smooth transition to valid Maven pattern of sbt plugins
This commit is contained in:
commit
894789cd36
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ object Dependencies {
|
|||
// sbt modules
|
||||
private val ioVersion = nightlyVersion.getOrElse("1.8.0")
|
||||
private val lmVersion =
|
||||
sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("1.8.0")
|
||||
sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("1.9.0-M1")
|
||||
val zincVersion = nightlyVersion.getOrElse("1.8.0")
|
||||
|
||||
private val sbtIO = "org.scala-sbt" %% "io" % ioVersion
|
||||
|
|
@ -77,7 +77,7 @@ object Dependencies {
|
|||
def addSbtZincCompile = addSbtModule(sbtZincPath, "zincCompile", zincCompile)
|
||||
def addSbtZincCompileCore = addSbtModule(sbtZincPath, "zincCompileCore", zincCompileCore)
|
||||
|
||||
val lmCoursierShaded = "io.get-coursier" %% "lm-coursier-shaded" % "2.0.15"
|
||||
val lmCoursierShaded = "io.get-coursier" %% "lm-coursier-shaded" % "2.0.16"
|
||||
|
||||
def sjsonNew(n: String) =
|
||||
Def.setting("com.eed3si9n" %% n % "0.9.1") // contrabandSjsonNewVersion.value
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
// sbt-plugin-example-diamond is a diamond graph of dependencies of sbt plugins.
|
||||
// sbt-plugin-example-diamond
|
||||
// / \
|
||||
// sbt-plugin-example-left sbt-plugin-example-right
|
||||
// \ /
|
||||
// sbt-plugin-example-bottom
|
||||
// Depending on the version of sbt-plugin-example-diamond, we test different patterns
|
||||
// of dependencies:
|
||||
// * Some dependencies were published using the deprecated Maven paths, some with the new
|
||||
// * Wheter the dependency on sbt-plugin-example-bottom needs conflict resolution or not
|
||||
|
||||
inThisBuild(
|
||||
Seq(
|
||||
csrCacheDirectory := baseDirectory.value / "coursier-cache"
|
||||
)
|
||||
)
|
||||
|
||||
// only deprecated Maven paths
|
||||
lazy val v1 = project
|
||||
.in(file("v1"))
|
||||
.settings(
|
||||
localCache,
|
||||
addSbtPlugin("ch.epfl.scala" % "sbt-plugin-example-diamond" % "0.1.0"),
|
||||
checkUpdate := checkUpdateDef(
|
||||
"sbt-plugin-example-diamond-0.1.0.jar",
|
||||
"sbt-plugin-example-left-0.1.0.jar",
|
||||
"sbt-plugin-example-right-0.1.0.jar",
|
||||
"sbt-plugin-example-bottom-0.1.0.jar",
|
||||
).value
|
||||
)
|
||||
|
||||
// diamond and left use the new Maven paths
|
||||
lazy val v2 = project
|
||||
.in(file("v2"))
|
||||
.settings(
|
||||
localCache,
|
||||
addSbtPlugin("ch.epfl.scala" % "sbt-plugin-example-diamond" % "0.2.0"),
|
||||
checkUpdate := checkUpdateDef(
|
||||
"sbt-plugin-example-diamond_2.12_1.0-0.2.0.jar",
|
||||
"sbt-plugin-example-left_2.12_1.0-0.2.0.jar",
|
||||
"sbt-plugin-example-right-0.1.0.jar",
|
||||
"sbt-plugin-example-bottom-0.1.0.jar",
|
||||
).value
|
||||
)
|
||||
|
||||
// conflict resolution on bottom between new and deprecated Maven paths
|
||||
lazy val v3 = project
|
||||
.in(file("v3"))
|
||||
.settings(
|
||||
localCache,
|
||||
addSbtPlugin("ch.epfl.scala" % "sbt-plugin-example-diamond" % "0.3.0"),
|
||||
checkUpdate := checkUpdateDef(
|
||||
"sbt-plugin-example-diamond_2.12_1.0-0.3.0.jar",
|
||||
"sbt-plugin-example-left_2.12_1.0-0.3.0.jar",
|
||||
"sbt-plugin-example-right-0.1.0.jar",
|
||||
"sbt-plugin-example-bottom_2.12_1.0-0.2.0.jar",
|
||||
).value
|
||||
)
|
||||
|
||||
// right still uses the deprecated Maven path and it depends on bottom
|
||||
// which uses the new Maven path
|
||||
lazy val v4 = project
|
||||
.in(file("v4"))
|
||||
.settings(
|
||||
localCache,
|
||||
addSbtPlugin("ch.epfl.scala" % "sbt-plugin-example-diamond" % "0.4.0"),
|
||||
checkUpdate := checkUpdateDef(
|
||||
"sbt-plugin-example-diamond_2.12_1.0-0.4.0.jar",
|
||||
"sbt-plugin-example-left_2.12_1.0-0.3.0.jar",
|
||||
"sbt-plugin-example-right-0.2.0.jar",
|
||||
"sbt-plugin-example-bottom_2.12_1.0-0.2.0.jar",
|
||||
).value
|
||||
)
|
||||
|
||||
// only new Maven paths with conflict resolution on bottom
|
||||
lazy val v5 = project
|
||||
.in(file("v5"))
|
||||
.settings(
|
||||
localCache,
|
||||
addSbtPlugin("ch.epfl.scala" % "sbt-plugin-example-diamond" % "0.5.0"),
|
||||
checkUpdate := checkUpdateDef(
|
||||
"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
|
||||
)
|
||||
|
||||
def localCache =
|
||||
ivyPaths := IvyPaths(baseDirectory.value, Some((ThisBuild / baseDirectory).value / "ivy-cache"))
|
||||
|
||||
lazy val checkUpdate = taskKey[Unit]("check the resolved artifacts")
|
||||
|
||||
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,12 @@
|
|||
> 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