Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Releasing

A version is a year and a count, every push to master publishes a snapshot, and a release is a tag on the commit that declares it.

docs/releasing.md on GitHub is the full checklist, with the status of every piece and the one-time setup. This chapter is what the process looks like once it is set up.

Versions

Versions are YEAR.RELEASE[.PATCH]: 2026.1, 2026.2, 2026.2.1. The release count starts at 1 each year, and there is no .0 patch. What a user of a toolkit wants from the number is how old it is, and that is what the year answers. The record is ADR-0333.

gradle.properties holds the line being worked towards, and never -SNAPSHOT:

goldberryVersion=2026.1

The build resolves the actual version. Nothing else types the suffix:

InputsVersion
nothing2026.1-SNAPSHOT
-Pgoldberry.release=true -Pgoldberry.releaseTag=v2026.12026.1
-Pgoldberry.release=true -Pgoldberry.releaseTag=v2026.2The build fails, naming both
goldberryVersion=2026.1-SNAPSHOT in the fileThe build fails, and so does BuildVersionTest
./gradlew -q :core:printVersion

A release tag that does not match the property fails the build before anything is compiled, so a tag pushed on the wrong commit stops at configuration rather than at Central.

Where things go

The group is dev.goldberry, and the artifact ids are goldberry-<module>. The namespace is ADR-0510, which moved it from an account name to the project’s own domain while nothing had been released.

WhatWhereWhen
goldberry-{common,natives,core,widgets,html,emoji,gpu,media}, goldberry-bom, goldberry, as -SNAPSHOTThe Central Portal’s snapshot repositoryEvery push to master
The same, releasedMaven CentralA v* tag
goldberry-natives classifiers linux-x64, linux-aarch64, macos-aarch64, windows-x64Beside goldberry-nativesWith it
goldberry-media classifiers ffmpeg-<target>, one per target the Media workflow builtBeside goldberry-mediaWith it
goldberry-media classifier ffmpeg-sources: FFmpeg’s and dav1d’s complete source at the pinned tags, the superbuild, and how to rebuildBeside goldberry-mediaWith the ffmpeg-<target> classifiers
goldberry-showcase-native-{linux-x64,macos-aarch64}.tar.gz, goldberry-showcase-native-windows-x64.exeThe tag’s GitHub Release, created as a draftA v* tag. A manual run leaves them as the run’s artifacts

The BOM knows versions and not platforms, so an application adds the goldberry-natives classifier jars itself. All four is the default worth writing, because NativeLibrary picks the right one at run time. Installing has the dependency block.

FFmpeg’s binaries do not go without their source. The ffmpeg-<target> jars carry an LGPL library in object form, so the same publication carries ffmpeg-sources beside them. Every publication that carries a target refuses to go without it, snapshots too, and both the superbuild and the sources jar refuse a tag that does not name the pinned commit. The records are ADR-0495 and ADR-0508. A release needs all four targets’ FFmpeg and refuses without them.

Snapshots

Every push to master runs snapshot.yml. It calls publish.yml, which builds Linux, macOS and Windows through the per-OS workflows, then publishes every module and every classifier jar in one Gradle invocation. It has to be one: a snapshot’s classifier jars are listed in the metadata its upload writes, and a runner publishing its own platform would leave Central naming whichever finished last. That is ADR-0334.

So a snapshot appears only after all three platforms are green. One broken platform stops every snapshot, which is the point. Runs are queued rather than cancelled, because an upload stopped halfway leaves a snapshot whose modules disagree about which build they are.

Until the Central secrets exist, a snapshot run rehearses into mavenLocal and says so in the run’s summary instead of failing.

Cutting a release

1

Rehearse. Actions, Release, Run workflow on master. It builds every platform and runs the whole chain into mavenLocal, uploading nothing. Fix anything red before tagging.

2

Check the licences. The release run enforces it, and a bumped upstream pin means re-copying that component's file from the new checkout.

3

Tag the commit that declares the version. release.yml publishes to a Portal deployment, and showcase.yml builds the native images and attaches them to a draft GitHub Release.

4

Publish in the Portal, then publish the draft GitHub Release, so the binaries and the artifacts appear together. Central takes a few minutes to an hour to sync.

5

Merge the bump. release.yml opens it as a pull request moving goldberryVersion to the next line.

Step 2 is one command, and releaseCheck turns a warning about an unvendored licence into a failure:

./gradlew checkLicenses -Pgoldberry.releaseCheck=true

Step 3 is a tag:

git tag -a v2026.1 -m "Goldberry 2026.1"
git push origin v2026.1

A release stops in the Portal for a person to press Publish, unless the repository variable CENTRAL_AUTO_RELEASE is true. A release on Central is permanent, so the first few get looked at.

Step 5 matters more than it looks. Until the bump is merged, master publishes 2026.1-SNAPSHOT, which Maven orders below the release it follows, so a consumer on the snapshot silently goes backwards. That is why the release workflow opens the pull request itself, which is ADR-0421. If the job could not open it, the branch is pushed and this is what it ran:

./gradlew -q :core:bumpVersion

A patch

A patch is cut from a release branch that declares the patch version. bumpVersion on a patch line moves to the next patch rather than to the next release.

git switch -c release/2026.1 v2026.1
# fix, then set goldberryVersion=2026.1.1, or ./gradlew -q :core:bumpVersion
git tag -a v2026.1.1 -m "Goldberry 2026.1.1"
git push origin release/2026.1 v2026.1.1

Patch branches publish no snapshots. snapshot.yml runs on master only.

The showcase binaries

Every release tag builds the showcase as a GraalVM native image on all three platforms, one file with libgoldberry inside it, and attaches the three to the tag’s GitHub Release. It is a release artifact and not a package: there is no jlink image and no GitHub Packages upload. A push to master does not build it, because a native build on three runners is an artifact rather than a check. The record is ADR-0340.

The two Unix binaries are tarballs rather than zips, because the zip format the artifact upload writes does not carry the executable bit.

Rehearsing locally

The whole chain without any upload, with stand-in libraries:

for t in linux-x64:libgoldberry.so linux-aarch64:libgoldberry.so \
         windows-x64:goldberry.dll macos-aarch64:libgoldberry.dylib; do
  mkdir -p /tmp/art/${t%%:*} && echo stand-in > /tmp/art/${t%%:*}/${t#*:}
done
./gradlew publishToMavenLocal -Dmaven.repo.local=/tmp/m2 \
    -Pgoldberry.skipNative=true -Pgoldberry.artifactsDir=/tmp/art -x test

-Pgoldberry.artifactsDir is what attaches the four classifier jars, and each jar refuses to build without its library.

The one-time setup

Central’s side is set up by a person, once, and nothing in the repository can check it: the dev.goldberry namespace verified by a DNS TXT record on goldberry.dev, snapshots enabled for it, a user token, a signing key on a keyserver, and the four repository secrets. docs/releasing.md lists every step and the current status of each.

Note

Nothing has been released yet. Snapshots have gone to Central, and the release leg has never run, because there is no tag.