Skip to content

Commit 3c42192

Browse files
authored
ci: add Maven Central publish workflow + maintainer runbook (D4) (#98)
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 <server id='central'> 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.
1 parent b5eaf1e commit 3c42192

3 files changed

Lines changed: 156 additions & 2 deletions

File tree

.github/workflows/publish.yml

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
name: Publish to Maven Central
2+
3+
# Publishes the v*-tagged artefact set to Maven Central via the
4+
# central-publishing-maven-plugin in the `release` profile (Track D3).
5+
# Triggered by the same `v*` tag push that fires release.yml (Track D4).
6+
# The two workflows are independent — release.yml creates the GitHub
7+
# Release; this one publishes the Maven Central artefacts. They can
8+
# succeed or fail independently, and a maintainer can re-run this
9+
# workflow alone via workflow_dispatch if Central had a transient
10+
# validator hiccup without re-cutting the tag.
11+
#
12+
# Hyphenated tags (rc / alpha / beta / SNAPSHOT) are skipped: those go
13+
# only to JitPack and the GitHub Release pre-release, never to Central
14+
# (Central's validator rejects SNAPSHOT-style coordinates anyway).
15+
#
16+
# Human prerequisites (one-time per repo):
17+
# 1. Generate a GPG key locally; upload the public key to a
18+
# keyserver pool (keys.openpgp.org, keyserver.ubuntu.com).
19+
# 2. Register at https://central.sonatype.com — verify the
20+
# `io.github.demchaav` namespace via GitHub OAuth or DNS TXT.
21+
# 3. Generate a Central user token at Account -> Generate User Token.
22+
# 4. Add four GitHub repo secrets at Settings -> Secrets and
23+
# variables -> Actions:
24+
# MAVEN_GPG_PRIVATE_KEY — full ASCII-armored private key
25+
# MAVEN_GPG_PASSPHRASE — passphrase for the key above
26+
# CENTRAL_USERNAME — Central user-token username half
27+
# CENTRAL_TOKEN — Central user-token password half
28+
# See docs/contributing/release-process.md for the full runbook.
29+
30+
on:
31+
push:
32+
tags:
33+
- 'v*'
34+
workflow_dispatch:
35+
inputs:
36+
tag:
37+
description: 'Existing v*-prefixed tag to (re-)publish'
38+
required: true
39+
type: string
40+
41+
permissions:
42+
contents: read
43+
44+
jobs:
45+
publish:
46+
name: Publish ${{ github.ref_name }} to Maven Central
47+
runs-on: ubuntu-latest
48+
# Only ship plain semver tags (vX.Y.Z) to Central. Pre-release tags
49+
# like v1.7.0-rc.1 ship to JitPack + the GitHub Release pre-release
50+
# surface only.
51+
if: |
52+
github.event_name == 'workflow_dispatch' ||
53+
(!contains(github.ref, '-rc') && !contains(github.ref, '-alpha') && !contains(github.ref, '-beta') && !contains(github.ref, '-snapshot'))
54+
env:
55+
JAVA_TOOL_OPTIONS: -Djava.awt.headless=true
56+
57+
steps:
58+
- name: Check out repository at tag
59+
uses: actions/checkout@v6
60+
with:
61+
ref: ${{ github.event.inputs.tag || github.ref }}
62+
63+
- name: Set up Temurin JDK 17 with Central credentials and GPG key
64+
uses: actions/setup-java@v5
65+
with:
66+
distribution: temurin
67+
java-version: '17'
68+
cache: maven
69+
# `setup-java@v5` writes <server id="central"> into
70+
# ~/.m2/settings.xml mapping these two env-var names to
71+
# <username> and <password>. The plugin's
72+
# publishingServerId=central in pom.xml's release profile
73+
# (Track D3) wires up to this entry.
74+
server-id: central
75+
server-username: CENTRAL_USERNAME
76+
server-password: CENTRAL_TOKEN
77+
# Imports the GPG key into the runner's keyring so the
78+
# maven-gpg-plugin (Track D2) can sign without an
79+
# interactive prompt.
80+
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
81+
gpg-passphrase: MAVEN_GPG_PASSPHRASE
82+
83+
- name: Verify (full canonical suite green at tagged commit)
84+
# Re-verify the tagged commit before publishing — defence in
85+
# depth against a tag pushed from a broken branch by mistake.
86+
# The publish step below would also fail at signing/upload,
87+
# but failing here surfaces a clearer error.
88+
run: ./mvnw -B -ntp clean verify -pl .
89+
90+
- name: Publish to Maven Central
91+
# Activates the release profile (sources + javadoc + gpg sign +
92+
# central-publishing) and flips gpg.skip=false. The deploy
93+
# phase invokes central-publishing-maven-plugin's upload goal
94+
# which blocks until Sonatype's validator confirms validation.
95+
run: ./mvnw -B -ntp -P release -DskipTests -Dgpg.skip=false deploy
96+
env:
97+
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
98+
CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }}
99+
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,31 @@ JitPack continue to resolve through the existing coordinates.
2121
`./mvnw -DskipTests -P japicmp verify -pl .`; HTML/MD/XML reports
2222
land in `target/japicmp/`. JitPack repository is scoped to the
2323
`japicmp` profile, so downstream consumers do not inherit it.
24+
- **Maven Central publish workflow** (Track D4). New
25+
[`.github/workflows/publish.yml`](.github/workflows/publish.yml) fires
26+
on the same `v*` tag push that triggers the existing
27+
`release.yml`. It re-runs `mvnw verify` at the tagged commit, imports
28+
the GPG key (Track D2) into the runner keyring, writes the
29+
`<server id="central">` credentials block into `~/.m2/settings.xml`
30+
via `actions/setup-java@v5`, then invokes
31+
`./mvnw -P release -Dgpg.skip=false deploy` — the
32+
`central-publishing-maven-plugin` (Track D3) uploads to Central and
33+
blocks until Sonatype's validator responds. Hyphenated tags
34+
(`-rc`, `-alpha`, `-beta`, `-snapshot`) are explicitly skipped — those
35+
ship only to JitPack and the GitHub Release pre-release surface.
36+
A `workflow_dispatch` input lets the maintainer re-publish an
37+
existing tag without re-cutting it if Central had a transient
38+
validator hiccup. The workflow is dormant until four GitHub repo
39+
secrets are wired: `MAVEN_GPG_PRIVATE_KEY`, `MAVEN_GPG_PASSPHRASE`,
40+
`CENTRAL_USERNAME`, `CENTRAL_TOKEN`.
41+
- **`docs/contributing/release-process.md` updated** with the
42+
end-to-end Maven Central runbook (Track D4 docs). New § 2.C
43+
"One-time Maven Central setup (maintainer)" walks through GPG key
44+
generation, keyserver upload, Sonatype account / namespace
45+
verification, Central user-token generation, the four GitHub
46+
secrets, and the release-candidate dry-run strategy. § 2.B
47+
post-release checklist gains a new step 9 for the Central publish
48+
alongside the existing JitPack step.
2449
- **`central-publishing-maven-plugin` in the `release` profile**
2550
(Track D3). Adds Sonatype's `central-publishing-maven-plugin` 0.7.0
2651
to the existing `release` profile as a packaging extension. Replaces

docs/contributing/release-process.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,39 @@ Run within 1 hour of the tag push. Independent steps can run in parallel.
9494
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.
9595
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.
9696
8. **GitHub Release — automated.** Pushing the `v<target>` 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<target>`; 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<target> --notes-file <CHANGELOG section>`) only if the workflow is unavailable.
97-
9. **Optional**: GitHub Discussions announcement (mirror the prior release's style; close with *"author intent, not coordinates"*), LinkedIn post, r/java post.
97+
9. **Maven Central publish — automated (from v1.6.6).** The same `v<target>` 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=<target>` once the artifact appears (usually 5–15 minutes after the workflow turns green).
98+
10. **Optional**: GitHub Discussions announcement (mirror the prior release's style; close with *"author intent, not coordinates"*), LinkedIn post, r/java post.
9899

99-
The release is **done** only when steps 1–7 are all green.
100+
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.
101+
102+
---
103+
104+
## 2.C One-time Maven Central setup (maintainer)
105+
106+
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.
107+
108+
1. **Generate a GPG key.**
109+
```bash
110+
gpg --full-generate-key # RSA 4096, no expiry, real-name / email of maintainer
111+
gpg --list-secret-keys --keyid-format=long
112+
gpg --armor --export-secret-keys <KEYID> > private-key.asc # never commit this file
113+
```
114+
2. **Publish the public key to a keyserver pool.** Central validates the signature against one of these. Two redundant pools is the conventional minimum:
115+
```bash
116+
gpg --keyserver keys.openpgp.org --send-keys <KEYID>
117+
gpg --keyserver keyserver.ubuntu.com --send-keys <KEYID>
118+
```
119+
`keys.openpgp.org` requires a one-time email-verification round-trip on the address attached to the key.
120+
3. **Register a Sonatype Central account.** At [central.sonatype.com](https://central.sonatype.com) — sign in with GitHub for the auto-verified `io.github.<gh-handle>` namespace (the `io.github.demchaav` namespace this repo claims). Verify the namespace via GitHub-account check or DNS TXT record on the published domain.
121+
4. **Generate a Central user token.** Account → Generate User Token → copy the `username` and `password` halves. These are credentials, not the GitHub login.
122+
5. **Wire four GitHub repo secrets.** Repo Settings → Secrets and variables → Actions → New repository secret:
123+
- `MAVEN_GPG_PRIVATE_KEY` — full ASCII-armored private key (the contents of `private-key.asc` from step 1).
124+
- `MAVEN_GPG_PASSPHRASE` — the passphrase guarding the key.
125+
- `CENTRAL_USERNAME` — token username from step 4.
126+
- `CENTRAL_TOKEN` — token password from step 4.
127+
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.
128+
129+
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.
100130

101131
---
102132

0 commit comments

Comments
 (0)