From e8721eb9e5fc90a0cc1e0dbc7c4d0dec36e69c35 Mon Sep 17 00:00:00 2001 From: Tam Nguyen Duc <1218621+tamnd@users.noreply.github.com> Date: Thu, 20 Aug 2026 09:38:25 +0700 Subject: [PATCH] A release goes to Maven Central The version properties for the two plugins a release needs have been in the parent pom since the repository was laid out, and neither plugin was configured, so `mvn deploy` would have put the artifacts nowhere. It publishes now. One upload for the whole reactor rather than one per module, because Central validates a deployment as a unit and a half-published version is a version nobody can use and nobody can replace. autoPublish is off, so what a tag produces is a deployment waiting in the portal, and dropping it there is the only undo there is. The version lives in the tag rather than in the pom. A repository whose version has to be bumped in a commit before every release is a repository where a checkout between the bump and the tag says a version that was never built. The engine and this client move on one number, so the tag also names the engine release the libraries come from, and the suite runs against the library that is about to be published rather than against a build of main. A release that cannot answer a query does not go out. Central wants a sources jar and a javadoc jar beside every jar and has no way of being told that an artifact made of shared libraries has neither, so zudb-native attaches both by hand, each holding one file that says why it is empty. An empty jar with nothing at all in it reads like a build that went wrong. gpg runs with a loopback pinentry, because a release runs where there is no terminal and the default is to open one and wait until the job is cancelled. --- .github/workflows/release.yml | 110 ++++++++++++++++++++++++++++++++ README.md | 2 + pom.xml | 39 +++++++++++ zudb-native/pom.xml | 39 +++++++++++ zudb-native/src/empty/README.md | 7 ++ 5 files changed, 197 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 zudb-native/src/empty/README.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5a032e0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,110 @@ +# Publishing to Maven Central. +# +# A tag is the trigger, and the tag is the version: v0.11.0 publishes +# 0.11.0. The pom on main stays a snapshot, because a repository whose +# version has to be bumped in a commit before every release is a +# repository where the version in a checkout is a lie between the bump +# and the tag. +# +# The engine and this client move on one version number, so the same tag +# names the engine release the libraries are downloaded from. If that +# release does not exist yet, this job fails at the staging step rather +# than publishing a client with no engine in it. +# +# Nothing is published without a human: autoPublish is off, so this puts +# a deployment in the portal and stops. Dropping it there is how a +# mistake is undone, and there is no undoing a version that went out. +name: Release + +on: + push: + tags: ["v*"] + workflow_dispatch: + inputs: + tag: + description: The engine release to take libraries from, for example v0.11.0 + required: true + publish: + description: Upload to Central, rather than only building what would be uploaded + type: boolean + default: false + +env: + MAVEN_ARGS: -B -ntp + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: "25" + cache: maven + # Writes a settings.xml whose `central` server credentials come + # from these two, which is the id the publishing plugin is + # configured with. + server-id: central + server-username: CENTRAL_USERNAME + server-password: CENTRAL_PASSWORD + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: GPG_PASSPHRASE + + - name: Which version this is + run: | + set -eu + tag="${{ inputs.tag || github.ref_name }}" + case "$tag" in + v*) ;; + *) echo "$tag is not a version tag"; exit 1 ;; + esac + echo "TAG=$tag" >> "$GITHUB_ENV" + echo "VERSION=${tag#v}" >> "$GITHUB_ENV" + + # The version lives in the tag rather than in the pom, so it is set + # here and never committed. + - name: Set the version + run: mvn $MAVEN_ARGS versions:set -DnewVersion="$VERSION" -DgenerateBackupPoms=false + + - name: Stage the libraries + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./scripts/stage-natives.sh "$TAG" + + # The suite needs an engine, and one of the seven that was just + # staged is the platform this runner is, so it runs against the + # library that is about to be published rather than against a build + # of main. A release that cannot answer a query does not go out. + - name: The library that is about to be published answers + run: | + set -eu + echo "ZU_LIBRARY=$PWD/zudb-native/lib/linux-amd64/libzu.so" >> "$GITHUB_ENV" + + - run: mvn $MAVEN_ARGS -Pnatives test + + - name: Build, sign and upload + if: github.event_name == 'push' || inputs.publish + env: + CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + run: mvn $MAVEN_ARGS -Prelease,natives -DskipTests deploy + + # What a dispatch with publish off is for: everything up to the + # upload, so that a change to the packaging can be checked without + # a version being spent on it. + - name: Build what would have been uploaded + if: github.event_name != 'push' && !inputs.publish + run: mvn $MAVEN_ARGS -Prelease,natives -DskipTests -Dgpg.skip=true package + + - uses: actions/upload-artifact@v4 + with: + name: artifacts + path: | + */target/*.jar + */target/*.pom + if-no-files-found: error diff --git a/README.md b/README.md index 24d8478..4b39b1b 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,8 @@ ZU_LIBRARY=/path/to/libzu.dylib java -jar zudb-bench/target/benchmarks.jar `ZU_LIBRARY` rather than `-Dzu.library` there, because JMH forks a JVM of its own and a fork inherits the environment rather than the system properties. +A release is a tag. `v0.11.0` builds `0.11.0`, takes its libraries from the engine release of the same name, runs the suite against the very library it is about to publish, and puts one signed deployment of the whole reactor in the Central portal. The version is never committed: a pom that has to be bumped before a release is a pom that is wrong between the bump and the tag. Nothing is published without a human pressing the button, and dropping a deployment in the portal is the only way a mistake is undone, because a version that went out cannot be taken back. + ## Beyond Java - **Kotlin** (`dev.zudb:zudb-kotlin`, tier 2), a thin extension layer, not a second binding. `use`, `Sequence`, `Flow` with cancellation wired to `interrupt()`, and an inline `transaction { }`. diff --git a/pom.xml b/pom.xml index 0e4b882..fec4ff0 100644 --- a/pom.xml +++ b/pom.xml @@ -171,8 +171,47 @@ and a local build has no use for. --> release + + + --pinentry-mode=loopback + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven.gpg.plugin.version} + + + sign-artifacts + verify + sign + + + + + + org.sonatype.central + central-publishing-maven-plugin + ${central.publishing.plugin.version} + true + + central + false + validated + + zudb-bench + + + org.apache.maven.plugins maven-source-plugin diff --git a/zudb-native/pom.xml b/zudb-native/pom.xml index ede9232..28c272e 100644 --- a/zudb-native/pom.xml +++ b/zudb-native/pom.xml @@ -160,4 +160,43 @@ + + + + + release + + + + org.apache.maven.plugins + maven-jar-plugin + + + sources + jar + + sources + ${project.basedir}/src/empty + + + + javadoc + jar + + javadoc + ${project.basedir}/src/empty + + + + + + + + diff --git a/zudb-native/src/empty/README.md b/zudb-native/src/empty/README.md new file mode 100644 index 0000000..74a1eaf --- /dev/null +++ b/zudb-native/src/empty/README.md @@ -0,0 +1,7 @@ +# There is no source here, and no documentation either + +`zudb-native` is a jar of shared libraries. Nothing in it is compiled from Java and nothing in it has an API, so a sources jar and a javadoc jar have nothing to hold. + +Maven Central requires both of them beside every published jar, and there is no way to say that one of them is meaningless for an artifact. So both are published and both contain this file, which is at least an answer to whoever opens one looking for something. + +The source for the libraries is the engine, at https://github.com/tamnd/zu. The API this artifact serves is `dev.zudb`, whose javadoc is published beside the `zudb` artifact.