From 2c95119fa3dd79c74747b311cc57d7c3fc82b326 Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Sun, 31 May 2026 17:39:40 +0100 Subject: [PATCH] ci: add Maven Central publish workflow + maintainer runbook (D4) Wires up Track D4 - the fourth and final step of the Maven Central pipeline. Fires on the same v* tag push that triggers the existing release.yml workflow. What the workflow does: 1. Re-runs mvnw verify against the tagged commit (defence-in-depth against a tag pushed from a broken branch). 2. actions/setup-java@v5 imports MAVEN_GPG_PRIVATE_KEY into the runner keyring and writes credentials block from CENTRAL_USERNAME + CENTRAL_TOKEN secrets into ~/.m2/settings.xml. 3. Runs ./mvnw -P release -Dgpg.skip=false deploy. Release profile (D1) attaches sources + javadoc jars; maven-gpg-plugin (D2) signs them; central-publishing-maven-plugin (D3) uploads to Central and blocks until validation completes. Hyphenated tags (-rc, -alpha, -beta, -snapshot) are explicitly skipped via the job's if: guard. Those ship only to JitPack + the GitHub Release pre-release surface; Central rejects them anyway. workflow_dispatch input lets the maintainer re-publish an existing tag without re-cutting it if Central had a transient validator hiccup. Workflow is dormant until four GitHub repo secrets are wired by the maintainer: MAVEN_GPG_PRIVATE_KEY, MAVEN_GPG_PASSPHRASE, CENTRAL_USERNAME, CENTRAL_TOKEN. docs/contributing/release-process.md section 2.C walks through the one-time setup end-to-end. Stacked on D3 (#97). After D3 merges, this rebases fast-forward. --- .github/workflows/publish.yml | 99 ++++++++++++++++++++++++++++ CHANGELOG.md | 25 +++++++ docs/contributing/release-process.md | 34 +++++++++- 3 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..8027c1ca --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,99 @@ +name: Publish to Maven Central + +# Publishes the v*-tagged artefact set to Maven Central via the +# central-publishing-maven-plugin in the `release` profile (Track D3). +# Triggered by the same `v*` tag push that fires release.yml (Track D4). +# The two workflows are independent — release.yml creates the GitHub +# Release; this one publishes the Maven Central artefacts. They can +# succeed or fail independently, and a maintainer can re-run this +# workflow alone via workflow_dispatch if Central had a transient +# validator hiccup without re-cutting the tag. +# +# Hyphenated tags (rc / alpha / beta / SNAPSHOT) are skipped: those go +# only to JitPack and the GitHub Release pre-release, never to Central +# (Central's validator rejects SNAPSHOT-style coordinates anyway). +# +# Human prerequisites (one-time per repo): +# 1. Generate a GPG key locally; upload the public key to a +# keyserver pool (keys.openpgp.org, keyserver.ubuntu.com). +# 2. Register at https://central.sonatype.com — verify the +# `io.github.demchaav` namespace via GitHub OAuth or DNS TXT. +# 3. Generate a Central user token at Account -> Generate User Token. +# 4. Add four GitHub repo secrets at Settings -> Secrets and +# variables -> Actions: +# MAVEN_GPG_PRIVATE_KEY — full ASCII-armored private key +# MAVEN_GPG_PASSPHRASE — passphrase for the key above +# CENTRAL_USERNAME — Central user-token username half +# CENTRAL_TOKEN — Central user-token password half +# See docs/contributing/release-process.md for the full runbook. + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + tag: + description: 'Existing v*-prefixed tag to (re-)publish' + required: true + type: string + +permissions: + contents: read + +jobs: + publish: + name: Publish ${{ github.ref_name }} to Maven Central + runs-on: ubuntu-latest + # Only ship plain semver tags (vX.Y.Z) to Central. Pre-release tags + # like v1.7.0-rc.1 ship to JitPack + the GitHub Release pre-release + # surface only. + if: | + github.event_name == 'workflow_dispatch' || + (!contains(github.ref, '-rc') && !contains(github.ref, '-alpha') && !contains(github.ref, '-beta') && !contains(github.ref, '-snapshot')) + env: + JAVA_TOOL_OPTIONS: -Djava.awt.headless=true + + steps: + - name: Check out repository at tag + uses: actions/checkout@v6 + with: + ref: ${{ github.event.inputs.tag || github.ref }} + + - name: Set up Temurin JDK 17 with Central credentials and GPG key + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '17' + cache: maven + # `setup-java@v5` writes into + # ~/.m2/settings.xml mapping these two env-var names to + # and . The plugin's + # publishingServerId=central in pom.xml's release profile + # (Track D3) wires up to this entry. + server-id: central + server-username: CENTRAL_USERNAME + server-password: CENTRAL_TOKEN + # Imports the GPG key into the runner's keyring so the + # maven-gpg-plugin (Track D2) can sign without an + # interactive prompt. + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + + - name: Verify (full canonical suite green at tagged commit) + # Re-verify the tagged commit before publishing — defence in + # depth against a tag pushed from a broken branch by mistake. + # The publish step below would also fail at signing/upload, + # but failing here surfaces a clearer error. + run: ./mvnw -B -ntp clean verify -pl . + + - name: Publish to Maven Central + # Activates the release profile (sources + javadoc + gpg sign + + # central-publishing) and flips gpg.skip=false. The deploy + # phase invokes central-publishing-maven-plugin's upload goal + # which blocks until Sonatype's validator confirms validation. + run: ./mvnw -B -ntp -P release -DskipTests -Dgpg.skip=false deploy + env: + CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 31f23d81..64d6d31f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,31 @@ JitPack continue to resolve through the existing coordinates. `./mvnw -DskipTests -P japicmp verify -pl .`; HTML/MD/XML reports land in `target/japicmp/`. JitPack repository is scoped to the `japicmp` profile, so downstream consumers do not inherit it. +- **Maven Central publish workflow** (Track D4). New + [`.github/workflows/publish.yml`](.github/workflows/publish.yml) fires + on the same `v*` tag push that triggers the existing + `release.yml`. It re-runs `mvnw verify` at the tagged commit, imports + the GPG key (Track D2) into the runner keyring, writes the + `` credentials block into `~/.m2/settings.xml` + via `actions/setup-java@v5`, then invokes + `./mvnw -P release -Dgpg.skip=false deploy` — the + `central-publishing-maven-plugin` (Track D3) uploads to Central and + blocks until Sonatype's validator responds. Hyphenated tags + (`-rc`, `-alpha`, `-beta`, `-snapshot`) are explicitly skipped — those + ship only to JitPack and the GitHub Release pre-release surface. + A `workflow_dispatch` input lets the maintainer re-publish an + existing tag without re-cutting it if Central had a transient + validator hiccup. The workflow is dormant until four GitHub repo + secrets are wired: `MAVEN_GPG_PRIVATE_KEY`, `MAVEN_GPG_PASSPHRASE`, + `CENTRAL_USERNAME`, `CENTRAL_TOKEN`. +- **`docs/contributing/release-process.md` updated** with the + end-to-end Maven Central runbook (Track D4 docs). New § 2.C + "One-time Maven Central setup (maintainer)" walks through GPG key + generation, keyserver upload, Sonatype account / namespace + verification, Central user-token generation, the four GitHub + secrets, and the release-candidate dry-run strategy. § 2.B + post-release checklist gains a new step 9 for the Central publish + alongside the existing JitPack step. - **`central-publishing-maven-plugin` in the `release` profile** (Track D3). Adds Sonatype's `central-publishing-maven-plugin` 0.7.0 to the existing `release` profile as a packaging extension. Replaces diff --git a/docs/contributing/release-process.md b/docs/contributing/release-process.md index cbcf5c5f..90337143 100644 --- a/docs/contributing/release-process.md +++ b/docs/contributing/release-process.md @@ -94,9 +94,39 @@ Run within 1 hour of the tag push. Independent steps can run in parallel. 6. **Re-run all examples against the published artifact** — `./mvnw -f examples/pom.xml clean package` followed by `exec:java -Dexec.mainClass=com.demcha.examples.GenerateAllExamples`. Expect 26+ `Generated:` lines. 7. **Flip ShowcaseMetadata back to develop** — `pwsh ./scripts/cut-release.ps1 -PostReleaseOnly`. This restores linkable "View Code" buttons for ongoing v1.x.y dev work. 8. **GitHub Release — automated.** Pushing the `v` tag triggers [`.github/workflows/release.yml`](../../.github/workflows/release.yml): it re-runs `./mvnw clean verify -pl .` against the tagged commit, then creates the Release with that version's CHANGELOG section as the body (hyphenated tags like `v1.7.0-rc.1` ship as pre-releases; the step is idempotent — it edits the notes if the Release already exists). The workflow titles it `GraphCompose v`; for a **minor** release, edit the title to add the codename (`v1.4`=cinematic, `v1.5`=intuitive, `v1.6`=expressive; patches drop it). Create the Release by hand (`gh release create v --notes-file `) only if the workflow is unavailable. -9. **Optional**: GitHub Discussions announcement (mirror the prior release's style; close with *"author intent, not coordinates"*), LinkedIn post, r/java post. +9. **Maven Central publish — automated (from v1.6.6).** The same `v` tag push triggers [`.github/workflows/publish.yml`](../../.github/workflows/publish.yml): it re-runs `mvnw verify` at the tagged commit, signs the four artefacts (main / sources / javadoc / pom) with the repo's GPG key, and uploads to Maven Central via the `central-publishing-maven-plugin`. Hyphenated tags (`-rc`, `-alpha`, `-beta`, `-snapshot`) are skipped — those go only to JitPack + the GitHub Release pre-release surface. `autoPublish=false` in the plugin config means the artefact lands in the Central validation queue; the maintainer flips the switch on [central.sonatype.com](https://central.sonatype.com) for the first publish, then can opt into auto-release in a follow-up. Verify via `mvn dependency:get -DgroupId=io.github.demchaav -DartifactId=graphcompose -Dversion=` once the artifact appears (usually 5–15 minutes after the workflow turns green). +10. **Optional**: GitHub Discussions announcement (mirror the prior release's style; close with *"author intent, not coordinates"*), LinkedIn post, r/java post. -The release is **done** only when steps 1–7 are all green. +The release is **done** only when steps 1–7 are all green; step 9 adds Maven Central availability once the D-track of v1.6.6 has shipped. + +--- + +## 2.C One-time Maven Central setup (maintainer) + +These steps are done **once per repo** before the publish workflow can succeed; they are *not* part of every release. Listed here so a future maintainer (or a future me) can reproduce the setup without spelunking through commit history. + +1. **Generate a GPG key.** + ```bash + gpg --full-generate-key # RSA 4096, no expiry, real-name / email of maintainer + gpg --list-secret-keys --keyid-format=long + gpg --armor --export-secret-keys > private-key.asc # never commit this file + ``` +2. **Publish the public key to a keyserver pool.** Central validates the signature against one of these. Two redundant pools is the conventional minimum: + ```bash + gpg --keyserver keys.openpgp.org --send-keys + gpg --keyserver keyserver.ubuntu.com --send-keys + ``` + `keys.openpgp.org` requires a one-time email-verification round-trip on the address attached to the key. +3. **Register a Sonatype Central account.** At [central.sonatype.com](https://central.sonatype.com) — sign in with GitHub for the auto-verified `io.github.` namespace (the `io.github.demchaav` namespace this repo claims). Verify the namespace via GitHub-account check or DNS TXT record on the published domain. +4. **Generate a Central user token.** Account → Generate User Token → copy the `username` and `password` halves. These are credentials, not the GitHub login. +5. **Wire four GitHub repo secrets.** Repo Settings → Secrets and variables → Actions → New repository secret: + - `MAVEN_GPG_PRIVATE_KEY` — full ASCII-armored private key (the contents of `private-key.asc` from step 1). + - `MAVEN_GPG_PASSPHRASE` — the passphrase guarding the key. + - `CENTRAL_USERNAME` — token username from step 4. + - `CENTRAL_TOKEN` — token password from step 4. +6. **Test the wiring on a release-candidate tag** *before* the first real release. `v1.6.6-rc.1` (hyphenated) skips Central per `publish.yml`'s `if:` guard, so it's safe — alternatively, cut `v1.6.6` for real and observe the workflow; `autoPublish=false` means a failed validation does not pollute Central, the artefact just sits in the validation queue until manually released or deleted. + +If any of these stop working between releases (key expired, token rotated), the publish workflow surfaces the failure inside the workflow run — the GitHub Release and the JitPack artefact are still cut by the other workflows. ---