This is a Mill plugin modeled after the fantastic sbt-ci-release plugin which helps automate publishing to Sonatype from GitHub Actions with as little friction as possible. These are the key features of using the plugin.
- A new git tag is published as a regular release
- A merge is published as a SNAPSHOT release
- Auto versioning based on git by mill-vcs-version
- A simple one-liner in CI to publish
If you've never published to Sonatype before you'll need to do a one-time setup
per domain name that you're publishing under. You can find the instructions for
this here. If you don't have a domain name you can use
io.github.<@your_username>
.
NOTE: Keep in mind that as of February 2021 newly created accounts and group ids (even if your account was created before February 2021) are tied
to https://s01.oss.sonatype.org/ whereas older accounts will be tied to
https://oss.sonatype.org/. This matters when logging in. You'll also want to
make sure you set sonatypeHost
to Some(SonatypeHost.s01)
in this scenario. See this section
To start using this plugin you'll want to include the following import in your build file:
import $ivy.`io.chris-kipp::mill-ci-release::<latest-version>`
This plugin under the hood uses mill-vcs-version to manage
your version, so if you have a publishVersion
set, remove it. The reason for
this is that mill-ci-release is making sure that when you're on a snapshot
version, it's appending -SNAPSHOT
which is necessary to publish to Sonatype
Snapshots. You still can override publishVersion
locally, but then you're 100%
on your own to ensure that -SNAPSHOT
is appended when necessary. This might
just be easily included in the plugin in the future.
The only other thing you'll need to do to your build is replace PublishModule
with CiReleaseModule
.
- import de.tobiasroeser.mill.vcs.version.VcsVersion
+ import io.kipp.mill.ci.release.CiReleaseModule
object example
extends ScalaModule
- with PublishModule {
+ with CiReleaseModule {
- def publishVersion = VcsVersion.vcsState().format()
You'll still need to ensure your pomSettings
are correctly filled in, just as
if you were extending PublishModule
.
NOTE: Again, if you have a newly created account (as of February 2021) you'll also want to ensure you add the following:
+ import io.kipp.mill.ci.release.SonatypeHost
...
+ override def sonatypeHost = Some(SonatypeHost.s01)
This will then set the correct sonatypeUri
and sonatypeSnapshotUri
for you.
If you have an older account, then there is no need to change the default or use
sonatypeHost
at all.
If you're using your own instance of Sonatype Nexus, your configuration needs to be adapted slightly:
- override def sonatypeHost = Some(SonatypeHost.s01)
+ override def sonatypeUri = "https://your-sonatype-nexus.url/path/to/releases"
+ override def sonatypeSnapshotUri = "https://your-sonatype-nexus.url/path/to/snapshots"
+
+ // The Open Source version of Nexus does not support staging
+ override def stagingRelease = false
If you've never created a keypair before that can be used to sign your artifacts you'll need to do this. You can find a guide for doing this here.
Before using mill-ci-release in your GitHub actions workflow you'll need to have
the following secrets defined. You can add these by going to repo Settings -> Secrets -> New repository secret
.
Here are the necessary secrets:
PGP_PASSPHRASE
: The passphrase that was used when creating your keypair. This will be the same passphrase that you're prompted to use when copying the value for yourPGP_SECRET
.PGP_SECRET
: A base64 encoded secret of your private key that you can export from the command line like below:
# macOS
gpg --export-secret-key -a $LONG_ID | base64 | pbcopy
# Ubuntu (assuming GNU base64)
gpg --export-secret-key -a $LONG_ID | base64 -w0 | xclip
# Arch
gpg --export-secret-key -a $LONG_ID | base64 | sed -z 's;\n;;g' | xclip -selection clipboard -i
# FreeBSD (assuming BSD base64)
gpg --export-secret-key -a $LONG_ID | base64 | xclip
# Windows
gpg --export-secret-key -a %LONG_ID% | openssl base64
SONATYPE_USERNAME
: The username you use to log into your Sonatype account.SONATYPE_PASSWORD
: The password you use to log into your Sonatype account.
You can use an exact copy of what is being used in this repo to publish by doing the following command:
curl -sLo .github/workflows/release.yml --create-dirs https://raw.githubusercontent.com/ckipp01/mill-ci-release/main/.github/workflows/release.yml
This will create a .github/workflows/release.yml
file which will be triggered
by a new git tag or a merge to main
. The contents should look like this:
name: Release
on:
push:
branches:
- main
tags: ["*"]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- run: ./mill -i io.kipp.mill.ci.release.ReleaseModule/publishAll
env:
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
PGP_SECRET: ${{ secrets.PGP_SECRET }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
By default, this will publish all of your modules that are extending
CiReleaseModule
.
The underlying publish should actually be identical as it's using the same Sonatype publisher. The difference is only in the set up. Below is a comparison of what you'll commonly see with Mill to release vs using mill-ci-release.
- name: Publish
+ run: ./mill -i io.kipp.mill.ci.release.ReleaseModule/publishAll
- run: |
- if [[ $(git tag --points-at HEAD) != '' ]]; then
- echo $PGP_PRIVATE_KEY | base64 --decode > gpg_key
- gpg --import --no-tty --batch --yes gpg_key
- rm gpg_key
- ./mill -i mill.scalalib.PublishModule/publishAll \
- --publishArtifacts __.publishArtifacts \
- --sonatypeUri "https://s01.oss.sonatype.org/service/local" \
- --sonatypeSnapshotUri "https://s01.oss.sonatype.org/content/repositories/snapshots" \
- --sonatypeCreds $SONATYPE_USER:$SONATYPE_PASSWORD \
- --gpgArgs --passphrase=$PGP_PASSWORD,--no-tty,--pinentry-mode,loopback,--batch,--yes,-a,-b \
- --readTimeout 600000 \
- --awaitTimeout 600000 \
- --release true \
- --signed true
- fi
Most often this is due to not correctly setting the following if you have a new account or group id:
override def sonatypeHost = Some(SonatypeHost.s01)
Or manually doing
override def sonatypeUri = "https://s01.oss.sonatype.org/service/local"
override def sonatypeSnapshotUri =
"https://s01.oss.sonatype.org/content/repositories/snapshots"
If you see this it's probably because you forgot to add the fetch-depth
in
checkout, meaning that no git tags are getting pulled in CI:
- uses: actions/checkout@v3
with:
fetch-depth: 0
This plugin has only really been tested on more minimal projects. There is purposefully not many configuration options mainly because I firmly believe in sane defaults that should easily allow what most users want to do with minimal setup. If you're missing certain configuration options, please do open a discussion or an issue and we can explore adding more customization options to this.