Skip to content

Commit

Permalink
Merge pull request #322 from sbt/wip/back_publishing
Browse files Browse the repository at this point in the history
Implement back publishing support
  • Loading branch information
eed3si9n authored Oct 20, 2024
2 parents 0a5401e + 4cd48ae commit 7b7b4f1
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ jobs:
java-version: 8
cache: sbt
- uses: sbt/setup-sbt@v1
- run: sbt +compile
- run: sbt +test
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
69 changes: 63 additions & 6 deletions plugin/src/main/scala/com/geirsson/CiReleasePlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 ~= {
Expand All @@ -128,15 +130,29 @@ 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(
(Test / publishArtifact) := false,
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
Expand All @@ -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.")
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -210,10 +228,49 @@ 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 nonComment =
if (ver.contains("#")) ver.split("#").head
else ver
val commands0 = nonComment.split("@").toList.drop(1)
var nonDigit = false
val commands = (commands0.map { cmd =>
if (cmd.isEmpty) sys.error(s"Invalid back-publish version: $ver")
else {
if (!cmd.head.isDigit) {
nonDigit = true
cmd
}
else if (cmd.contains(".x")) s"++${cmd}"
else s"++${cmd}!"
}
}) ::: (if (nonDigit) Nil else List("publishSigned"))
commands match {
case x :: Nil => x
case xs => xs.mkString(";", ";", "")
}
} 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
}
}
47 changes: 47 additions & 0 deletions plugin/src/test/scala/com/geirsson/CiReleaseTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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("Commands can be chained") {
assertEquals(backPubVersionToCommand("1.1.0@2.12.20@foo/publishSigned"), ";++2.12.20!;foo/publishSigned")
assertEquals(dropBackPubCommand("1.1.0@2.12.20@foo/publishSigned"), expectedVer)

assertEquals(backPubVersionToCommand("1.1.0@foo/something@bar/publishSigned"), ";foo/something;bar/publishSigned")
assertEquals(dropBackPubCommand("1.1.0@foo/something@bar/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)
}
}
50 changes: 50 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,56 @@ page will look like this:

Enjoy 👌

### Back-publishing support

sbt-ci-release implements a mini-DSL for the Git tag for back publishing purpose, which is useful if you maintain a compiler plugin, library for Scala Native, or an sbt plugin during 2.x migration etc.

```
v1.2.3[@3.x|@2.n.x|@a.b.c][@command][#comment]
```
- `#` is used for comments, which is useful if you need to use the same command multiple times
- `@3.x` expands to `++3.x`, and if no other commands follow, `;++3.x;publishSigned`
- `@2.13.x` expands to `++2.13.x`, and if no other commands follow, `;++2.13.x;publishSigned`
- Other commands such as `@foo/publishSigned` expands to `foo/publishSigned`
#### Case 1: Publish all subprojects for Scala 2.13.15
`v1.2.3@2.13.15`
#### Case 2: Publish all subprojects for Scala 3.x
`v1.2.3@3.x`. Optionally we can add a comment: `v1.2.3@3.x#comment`.
We can use this to back publish sbt 2.x plugins.
1. Branch off of `v1.2.3` to create `release/1.2.3` branch, and send a PR to update sbt version
2. Tag the brach to `v1.2.3@3.x#sbt2.0.0-Mn`
#### Case 3: Publish some subprojects for Scala 2.13.15
`v1.2.3@2.13.15@foo/publishSigned`
You can create a subproject to aggregate 2 or more subprojects.
#### Case 4: Publish some subprojects for supported Scala versions
`v1.2.3@+foo_native/publishSigned#comment`
1. Branch off of `v1.2.3` to create `release/1.2.3` branch, and send a PR to update the Scala Native version.
2. Tag the branch to `v1.2.3@+foo_native/publishSigned#native0.5`
#### Case 5: Minimize the use of command
`v1.2.3#unique_comment`, for example `v1.2.3#native0.5_3`
If you prefer to keep most of the information in a git branch instead, you can just use the comment functionality.
1. Branch off of `v1.2.3` to create `release/1.2.3` branch, and send a PR to:
a. Update appropriate dependency (sbt, Scala Native etc)
b. Modify the `CI_RELEASE` environment variable to encode the actions you want to take, like `;++3.x;foo_native/publishSigned`. For GitHub Actions, it would be in `.github/workflows/release.yml`
2. Tag the branch to `v1.2.3#unique_comment`. For record keeping, encode the version you're trying to back publishing for e.g. `v1.2.3#native0.5_3`
## FAQ
### How do I publish to Sonatype Central?
Expand Down

0 comments on commit 7b7b4f1

Please sign in to comment.