Status: decided. This document records the release-automation approach for the dexpace Java SDK and the steps to operate it. It supersedes the open question of whether to adopt release automation before the first non-alpha tag.
Adopt tag-driven publishing through GitHub Actions, built directly on the existing
maven-publish + signing convention plugin. A workflow triggered by a v* tag builds the
SDK, signs the artifacts, and publishes them to Maven Central. Versioning and the changelog
stay manual for now.
No additional release framework (release-please, semantic-release, JReleaser, or a third-party release host) is introduced. The publishing machinery is already in place; what is missing is a trigger and a credential path, both of which this approach supplies with the tools the repository already uses (Gradle + GitHub Actions + Sonatype's upstream endpoints).
- No automated version bumping or changelog generation. The version lives in one place,
gradle.properties, and a release bumps it with a one-line edit and a commit. At the current cadence (pre-1.0, infrequent tags) a release-please manifest plus version anchors across the README and build scripts is more moving parts than the project benefits from. This can be revisited once the tag cadence justifies the automation; the trigger and publish steps below do not depend on that choice. - No third-party release host. Publishing goes to Sonatype directly using upstream Gradle and GitHub Actions only, matching the constraint in the originating issue.
The publishing setup is already complete and centralized:
build-logic/src/main/kotlin/dexpace.published-module.gradle.ktsis the single convention plugin every publishable module applies viaplugins { id("dexpace.published-module") }. It configures thelibraryMavenPublicationfrom thejavacomponent, the shared POM (name, description, MIT license, developer, SCM), the publish repositories, and signing.- Nine modules opt in today:
sdk-core,sdk-io-okio3,sdk-serde-jackson,sdk-transport-okhttp,sdk-transport-jdkhttp,sdk-async-coroutines,sdk-async-reactor,sdk-async-netty, andsdk-async-virtualthreads. The two unpublished modules (sdk-shrink-test,sdk-example) simply do not apply the plugin and stay out of releases. - Coordinates (
group=org.dexpace,version) come fromgradle.propertiesand apply to the root and every subproject, so a version bump is a single edit. - Signing is already CI-gated:
isRequired = (System.getenv("CI") == "true"), and the key material is read fromsigning.key/signing.passwordGradle properties or theSIGNING_KEY/SIGNING_PASSWORDenvironment variables viauseInMemoryPgpKeys(...). An in-memory ASCII-armored key is exactly what a CI secret supplies — no keyring file on the runner.
Because of that, release automation reduces to two additions: a remote publish repository the CI job points at, and a tag-triggered workflow that supplies the four secrets. Nothing in the module build scripts changes.
A release is cut by pushing an annotated tag matching v* (for example v0.1.0):
# 1. Bump the version on main (drop the -alpha suffix for the first stable release).
# Edit gradle.properties: version=0.1.0
git commit -am "chore: release 0.1.0"
git push origin main
# 2. Tag the release commit and push the tag.
git tag -a v0.1.0 -m "Release 0.1.0"
git push origin v0.1.0The tag push is the only thing that triggers a publish. Pushes to main and pull requests
continue to run CI only (the existing .github/workflows/ci.yml); they never publish.
The tag name and the version in gradle.properties must agree (v0.1.0 <-> 0.1.0). The
release workflow verifies this and fails fast on a mismatch, so a stray tag cannot publish a
stale version.
The release workflow needs four repository secrets in Settings -> Secrets and variables -> Actions:
| Secret | Purpose |
|---|---|
SIGNING_KEY |
ASCII-armored PGP private key (the full -----BEGIN PGP PRIVATE KEY BLOCK----- block). Consumed by useInMemoryPgpKeys. |
SIGNING_PASSWORD |
Passphrase for that PGP key. |
SONATYPE_USERNAME |
Sonatype Central user-token username (not the portal login). |
SONATYPE_PASSWORD |
Sonatype Central user-token password. |
Generating the PGP key (one-time):
gpg --quick-generate-key "Dexpace SDK Team <releases@dexpace.org>" rsa4096 sign 2y
gpg --armor --export-secret-keys <KEY_ID> # paste into SIGNING_KEY
gpg --keyserver keys.openpgp.org --send-keys <KEY_ID> # publish so Central can verifyThe Sonatype token is created on the Central Portal account page; use the generated token credentials rather than the account password.
The convention plugin currently declares only a local staging repository
(build/staging-repo) and notes that "CI must override this to publish to a real remote." The
release job supplies that remote. Two equivalent options:
- Preferred: point a Maven repository at the Sonatype Central upload endpoint and run
publishLibraryPublicationToCentralRepositoryper module, withSONATYPE_USERNAME/SONATYPE_PASSWORDas the repository credentials. This keeps everything inside the existingmaven-publishmachinery. - Alternatively, publish each module to the
localstaging directory (publishLibraryPublicationToLocalRepository) and hand the resulting bundle to the Central Portal upload API in a follow-up step.
Either way the artifacts published are exactly the signed library publications the convention
plugin already produces — sources jar, javadoc jar, POM, and .asc signatures — for the nine
opted-in modules only.
A release.yml workflow gated on the tag performs, in order:
- Check out the tagged commit (
submodules: recursive, matching CI — thestyleguidesubmodule must be present). - Set up JDK 21 (Temurin), the same toolchain CI uses; the foojay resolver auto-provisions the JDK 8 / 11 toolchains the cross-compiled modules need.
- Verify that the
v*tag matchesversioningradle.properties; fail on mismatch. - Build and verify with
./gradlew buildso a release never ships artifacts that would not pass the full quality gate (tests, ktlint, detekt, apiCheck, coverage floor). - Publish the signed publications to Sonatype Central with
CI=trueset (so signing is required) and the four secrets exported as environment variables / Gradle properties. - Release the staging repository to Maven Central (automatic via the Central Portal once the upload validates).
permissions: contents: read is sufficient; the workflow does not push commits or create GitHub
Releases. Cutting a GitHub Release with notes, if wanted, stays a manual step alongside the tag.
- Confirm the four secrets are present in repository settings.
- Confirm the PGP public key is published to a keyserver Central checks.
- Bump
versioningradle.propertiesto the release version (drop-alpha). - Update any version references in
README.mdin the same commit. - Merge to
main; confirm CI is green. - Tag the release commit
vX.Y.Zand push the tag. - Watch the release workflow; on success, confirm the artifacts appear on Maven Central.