Releasing
How resilience4cata is published to Maven Central. Everything uses Gradle's own maven-publish and signing plugins; there is no third-party publishing plugin.
What is published
| artifactId | Contents |
|---|---|
resilience4cata-annotations |
jar, sources, Javadoc, POM, Gradle module metadata |
resilience4cata-core |
same; depends on org.jspecify:jspecify (compile scope) |
resilience4cata-processor |
same; depends on resilience4cata-annotations (runtime scope, for the processor path) |
resilience4cata-config |
same; depends on resilience4cata-core (compile) |
resilience4cata-micrometer |
same; depends on resilience4cata-core and io.micrometer:micrometer-core (compile) |
resilience4cata-testkit |
same; depends on resilience4cata-core (compile) |
resilience4cata-opentelemetry |
same; depends on resilience4cata-core and io.opentelemetry:opentelemetry-api (compile) |
resilience4cata-junit |
same; depends on resilience4cata-testkit and org.junit.jupiter:junit-jupiter-api 5.10.5 (compile) |
resilience4cata-all |
jar (only module-info), sources, a Javadoc jar with a README (javadoc refuses a module without packages), POM, module metadata; depends on core, annotations and config (compile) |
resilience4cata-bom |
POM and module metadata only: constraints on every module above, at the same version |
groupId dev.resilience4cata. Every jar carries LICENSE and NOTICE (Apache-2.0) in META-INF, and archives are reproducible (no file timestamps, fixed order). examples, benchmark and stress are not published.
How the build is organized
r4c.publish(conventions/.../PublishConventionPlugin.java), applied by each published module: onemavenJavapublication with the POM fields Central requires (name, description from the module'sdescription, url, license, developer, scm, issue tracker), signing, and acentralStagingfile repository atbuild/central-staging.r4c.central(CentralConventionPlugin.java), applied by the rootbuild.gradle:centralBundledeletesbuild/central-staging, publishes every module into it, checks it and zips it intobuild/central-bundle.zip. The check fails on a-SNAPSHOTversion, a missing module, a missing sources or Javadoc jar, or any file without its.ascsignature and.md5/.sha1checksums. Only the release version's files go in (nomaven-metadata.xml), and without the.sha256/.sha512checksums Gradle also writes: Central does not require them, and every file of the bundle counts against the Central Portal's monthly file quota (1,000 files on the free plan; a release of every module is about 250 files).centralUploadposts the zip to the Central Portal Publisher API (POST /api/v1/publisher/upload,publishingType=USER_MANAGED). Nothing depends on it, sobuildnever uploads, and it fails without a token. AUSER_MANAGEDdeployment is validated by Central and then waits in the portal until someone presses Publish.
apiCompatibility(ApiCompatibilityTask.java, added byr4c.publishand run bycheck, so bybuildand CI): japicmp compares each published module's jar with the same artifact atr4c.apiBaseline(ingradle.properties: the last release) from Maven Central, public and protected members, binary and source compatibility. The API is the packages the module exports inmodule-info.java, orr4cApi.packagesin its build file for a module without one (micronaut,quarkus:runtime); a module that exports none (processor,quarkus:deployment) is skipped, and so is a module with no baseline (new since the last release, or offline). The report is<module>/build/reports/api-compatibility/api-changes.md. Before 1.0 an incompatible change is a warning; from 1.0 it fails the build unless the major version changes. japicmp runs through Gradle's Worker API, so it adds no Gradle plugin.opentelemetryreports its version as the OpenTelemetry instrumentation scope version:processResourceswrites the build'sversionintodev/resilience4cata/opentelemetry/version.properties, so a release (including-Pversion=…) needs no manual step.- Signing is skipped when no key is set, so
./gradlew buildand./gradlew publishToMavenLocalneed no secrets.
Gradle properties (or ORG_GRADLE_PROJECT_<name> environment variables):
| Property | Used by | Meaning |
|---|---|---|
signingInMemoryKey |
signing | ASCII-armored secret key (gpg --armor --export-secret-keys <id>) |
signingInMemoryKeyPassword |
signing | Its passphrase |
signingInMemoryKeyId |
signing | Optional: a signing subkey's id |
centralPortalUsername, centralPortalPassword |
centralUpload |
Central Portal user token |
r4c.scmRepository |
POM | GitHub owner/repository, in gradle.properties |
r4c.developerId, r4c.developerName |
POM | Developer entry, in gradle.properties |
Keep secrets in ~/.gradle/gradle.properties or the environment, never in the repository. With the configuration cache on, Gradle stores property values in its (encrypted) cache under .gradle/; pass --no-configuration-cache when signing on a shared machine.
One-time setup
-
Create an account at central.sonatype.com.
-
Verify the namespace
dev.resilience4cata: in the portal, Namespaces → Add namespace, then add the verification key it shows as a DNS TXT record onresilience4cata.devand press Verify. -
Generate a user token (View Account → Generate User Token). Its two parts are
centralPortalUsernameandcentralPortalPassword. -
Create a signing key and publish the public part to a keyserver Central checks (for example
keys.openpgp.orgorkeyserver.ubuntu.com):gpg --quick-gen-key "resilience4cata releases <you@example.com>" rsa4096 sign 2y gpg --keyserver keys.openpgp.org --send-keys <key id> gpg --armor --export-secret-keys <key id> # value of signingInMemoryKey -
For the GitHub workflow, create the environment
production(Settings → Environments; the release job declares it) and add to it the secretsSIGNING_KEY,SIGNING_KEY_PASSWORD, and, to upload from CI,CENTRAL_PORTAL_USERNAMEandCENTRAL_PORTAL_PASSWORD. Restrict the environment tov*tags so only a release tag can read them. -
Set
r4c.scmRepository(and the developer properties) ingradle.propertiesto the real GitHub repository.
Release steps
- Check the API changes since the last release:
./gradlew apiCompatibility, then read the warnings and*/build/reports/api-compatibility/api-changes.md; every incompatible change goes in the changelog. - Set the release version in
gradle.properties:version=0.1.0. Commit (release 0.1.0) and tag:git tag v0.1.0, then push the commit and the tag. - Build the bundle, either:
- in CI: run the release workflow (
.github/workflows/release.yml) on tagv0.1.0. It first runs the whole build on JDK 21 and 25 (the samebuildworkflow as CI, on the tagged commit), and only then builds, signs, checks, archivescentral-bundle.zipas a workflow artifact, and uploads it if the token secrets are set; or - locally:
./gradlew build centralBundle --no-configuration-cachewith the signing properties set.
- in CI: run the release workflow (
- Upload, if CI did not:
./gradlew centralUpload -x centralBundle, or in the portal Publish Component, choosebuild/central-bundle.zip. - In the portal, open Deployments, wait for Validated, review the files, then press Publish (or Drop to discard it). Artifacts reach Maven Central within about 30 minutes.
- Bump to the next development version:
version=0.2.0-SNAPSHOT, commit. - Once the release is visible on Maven Central, update the version written in the docs and the root
README.md(build snippets on the site, landing pages, changelog):grep -rn "0\.1\.0" README.md docs/en docs/es. Setr4c.apiBaseline=0.1.0ingradle.propertiestoo, soapiCompatibilitycompares with the new release.
To check a bundle without releasing anything, build it with a throwaway key and a release version:
gpg --quick-gen-key "throwaway <throwaway@example.invalid>" rsa3072 sign 1d
ORG_GRADLE_PROJECT_signingInMemoryKey="$(gpg --armor --export-secret-keys throwaway@example.invalid)" \
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword=<passphrase> \
./gradlew centralBundle -Pversion=0.1.0 --no-configuration-cache
unzip -l build/central-bundle.zip