From fb64a22e412db75b1682be3df0ccf4b8deff13a5 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 13 Oct 2024 15:34:25 -0400 Subject: [PATCH] Implement back publishing support **Problem** For plugin code, like sbt plugins and Scala compiler plugin, and even normal libraries, it's common for the maintainers to want to back publish the current code base against a new version of Scala (or sbt, Scala Nave etc) instead of bumping the version. The current "bump the version" approach effectively pushes the work of figuring out the correct patch version number to the end users. In addition, if the plugin or the library does not cross publish all variants, then the end user might need to figure out different patch version to use effectively the same code. **Solution** This implements a mini DSL for tag `version[@command|@3.3.4][#comment]`, which allows plugin authors to back publish a code base. --- build.sbt | 1 + .../scala/com/geirsson/CiReleasePlugin.scala | 59 +++++++++++++++++-- .../scala/com/geirsson/CiReleaseTest.scala | 39 ++++++++++++ 3 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 plugin/src/test/scala/com/geirsson/CiReleaseTest.scala diff --git a/build.sbt b/build.sbt index f3745f3..04e0678 100644 --- a/build.sbt +++ b/build.sbt @@ -40,6 +40,7 @@ lazy val plugin = project case _ => "2.0.0-M2" } }, + libraryDependencies += "org.scalameta" %% "munit" % "0.7.29" % Test, addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.1.0"), addSbtPlugin("com.github.sbt" % "sbt-git" % "2.1.0"), addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.0"), diff --git a/plugin/src/main/scala/com/geirsson/CiReleasePlugin.scala b/plugin/src/main/scala/com/geirsson/CiReleasePlugin.scala index ad99c50..8286ffa 100644 --- a/plugin/src/main/scala/com/geirsson/CiReleasePlugin.scala +++ b/plugin/src/main/scala/com/geirsson/CiReleasePlugin.scala @@ -106,6 +106,8 @@ object CiReleasePlugin extends AutoPlugin { Some(s"scm:git:git@github.com:$user/$repo.git") ) + lazy val cireleasePublishCommand = settingKey[String]("") + override lazy val buildSettings: Seq[Def.Setting[_]] = List( dynverSonatypeSnapshots := true, scmInfo ~= { @@ -128,7 +130,20 @@ object CiReleasePlugin extends AutoPlugin { } catch { case NonFatal(_) => None } - } + }, + cireleasePublishCommand := { + val gitDescribe = dynverGitDescribeOutput.value + val v = gitDescribe.getVersion( + dynverCurrentDate.value, + dynverSeparator.value, + dynverSonatypeSnapshots.value + ) + sys.env.get("CI_RELEASE") match { + case Some(cmd) => cmd + case None => backPubVersionToCommand(v) + } + }, + version ~= dropBackPubCommand, ) override lazy val globalSettings: Seq[Def.Setting[_]] = List( @@ -136,7 +151,8 @@ object CiReleasePlugin extends AutoPlugin { publishMavenStyle := true, commands += Command.command("ci-release") { currentState => val shouldDeployToSonatypeCentral = isDeploySetToSonatypeCentral(currentState) - val isSnapshot = isSnapshotVersion(currentState) + val version = getVersion(currentState) + val isSnapshot = isSnapshotVersion(version) if (!isSecure) { println("No access to secret variables, doing nothing") currentState @@ -150,6 +166,8 @@ object CiReleasePlugin extends AutoPlugin { val reloadKeyFiles = "; set pgpSecretRing := pgpSecretRing.value; set pgpPublicRing := pgpPublicRing.value" + val publishCommand = getPublishCommand(currentState) + if (shouldDeployToSonatypeCentral) { if (isSnapshot) { println(s"Sonatype Central does not accept snapshots, only official releases. Aborting release.") @@ -161,7 +179,7 @@ object CiReleasePlugin extends AutoPlugin { println("Tag push detected, publishing a stable release") reloadKeyFiles :: sys.env.getOrElse("CI_CLEAN", "; clean ; sonatypeBundleClean") :: - sys.env.getOrElse("CI_RELEASE", "+publishSigned") :: + publishCommand :: sys.env.getOrElse("CI_SONATYPE_RELEASE", "sonatypeCentralRelease") :: currentState } @@ -184,7 +202,7 @@ object CiReleasePlugin extends AutoPlugin { println("Tag push detected, publishing a stable release") reloadKeyFiles :: sys.env.getOrElse("CI_CLEAN", "; clean ; sonatypeBundleClean") :: - sys.env.getOrElse("CI_RELEASE", "+publishSigned") :: + publishCommand :: sys.env.getOrElse("CI_SONATYPE_RELEASE", "sonatypeBundleRelease") :: currentState } @@ -210,10 +228,39 @@ object CiReleasePlugin extends AutoPlugin { } } - def isSnapshotVersion(state: State): Boolean = { + def getVersion(state: State): String = (ThisBuild / version).get(Project.extract(state).structure.data) match { - case Some(v) => v.endsWith("-SNAPSHOT") + case Some(v) => v case None => throw new NoSuchFieldError("version") } + + def getPublishCommand(state: State): String = + (ThisBuild / cireleasePublishCommand).get(Project.extract(state).structure.data) match { + case Some(v) => v + case None => throw new NoSuchFieldError("cireleasePublishCommand") + } + + def isSnapshotVersion(v: String): Boolean = v.endsWith("-SNAPSHOT") + + def backPubVersionToCommand(ver: String): String = + if (ver.contains("@")) { + val afterAt = ver.split("@").drop(1).mkString("@") + val cmd = + if (afterAt.contains("#")) afterAt.split("#").head + else afterAt + if (cmd.isEmpty) sys.error(s"Invalid back-publish version: $ver") + else { + if (!cmd.head.isDigit) cmd + else if (cmd.contains(".x")) s";++${cmd};publishSigned" + else s";++${cmd}!;publishSigned" + } + } else "+publishSigned" + + def dropBackPubCommand(ver: String): String = { + val nonComment = + if (ver.contains("#")) ver.split("#").head + else ver + if (nonComment.contains("@")) nonComment.split("@").head + else nonComment } } diff --git a/plugin/src/test/scala/com/geirsson/CiReleaseTest.scala b/plugin/src/test/scala/com/geirsson/CiReleaseTest.scala new file mode 100644 index 0000000..4cdd39f --- /dev/null +++ b/plugin/src/test/scala/com/geirsson/CiReleaseTest.scala @@ -0,0 +1,39 @@ +package com.geirsson + +import CiReleasePlugin.{ backPubVersionToCommand, dropBackPubCommand } + +class CiReleaseTest extends munit.FunSuite { + val expectedVer = "1.1.0" + + test("Normal version default") { + assertEquals(backPubVersionToCommand("1.1.0"), "+publishSigned") + assertEquals(dropBackPubCommand("1.1.0"), expectedVer) + } + + test("Command starting with number is assumed to be a cross version") { + assertEquals(backPubVersionToCommand("1.1.0@2.12.20"), ";++2.12.20!;publishSigned") + assertEquals(dropBackPubCommand("1.1.0@2.12.20"), expectedVer) + + assertEquals(backPubVersionToCommand("1.1.0@3.x"), ";++3.x;publishSigned") + assertEquals(dropBackPubCommand("1.1.0@3.x"), expectedVer) + } + + test("Non-number is treated as an alternative publish command") { + assertEquals(backPubVersionToCommand("1.1.0@foo/publishSigned"), "foo/publishSigned") + assertEquals(dropBackPubCommand("1.1.0@foo/publishSigned"), expectedVer) + + assertEquals(backPubVersionToCommand("1.1.0@+foo/publishSigned"), "+foo/publishSigned") + assertEquals(dropBackPubCommand("1.1.0@+foo/publishSigned"), expectedVer) + } + + test("Treat # as comments") { + assertEquals(backPubVersionToCommand("1.1.0#comment"), "+publishSigned") + assertEquals(dropBackPubCommand("1.1.0#comment"), expectedVer) + + assertEquals(backPubVersionToCommand("1.1.0@2.12.20#comment"), ";++2.12.20!;publishSigned") + assertEquals(dropBackPubCommand("1.1.0@2.12.20#comment"), expectedVer) + + assertEquals(backPubVersionToCommand("1.1.0@3.x#comment"), ";++3.x;publishSigned") + assertEquals(dropBackPubCommand("1.1.0@3.x#comment"), expectedVer) + } +}