Skip to content

Audit docs/developer/release: remove the retired Maven Central steps, fail on drift, and ship what the release builds - #2097

Merged
wmdietl merged 12 commits into
masterfrom
maven-central-publishing
Sep 18, 2026
Merged

wmdietl merged 12 commits into
masterfrom
maven-central-publishing

Conversation

@wmdietl

@wmdietl wmdietl commented Sep 16, 2026

Copy link
Copy Markdown
Member

Started as an investigation of two asks — nightly snapshots to Maven Central, and removing the manual click that ends every release — and grew into an audit of docs/developer/release/, which had drifted badly.

Nothing here is enabled. The snapshot workflow is inert without secrets; merging publishes nothing.

The two original questions

Snapshots are already pointed at the right place. gradle-mvn-push.gradle sends -SNAPSHOT versions to https://central.sonatype.com/repository/maven-snapshots/, which is the Central Portal snapshot repository, and signs releases only — so a nightly job needs no publishing changes and no GPG key in CI. Only a schedule and two secrets are missing; the workflow is here, gated on SONATYPE_NEXUS_USERNAME existing.

The manual click needs one API call, not a plugin. docs/developer/maven-central-publishing.md records why, what was considered, and what was rejected.

Signing: any maintainer can sign a release

Maven Central does not pin a key to a namespace — it only checks that a signature verifies against a public key on a keyserver. Several maintainers can each sign with their own key, and nothing in the repository needs to change when a new one starts. Only the docs implied otherwise, by presenting one maintainer's key as the value to pass.

The key moves out of the command line into the releaser's own ~/.gradle/gradle.properties, so the release command names no key at all. The signing task now fails with an explicit message if signing.gnupg.keyName is unset, rather than letting gpg fall back to whichever secret key happens to be its default.

The release scripts drove a UI that no longer exists

Push steps 5a/5b/5c and 10 told the releaser to "close" artifacts in a top pane and paste a staging repository URL back into the script — the retired OSSRH Nexus staging UI. The staging step was also dead on arrival here: it read the signing passphrase from /projects/swlab1/checker-framework/hosting-info/, a University of Washington path that does not exist, and signed with checker-framework-dev@googlegroups.com.

Removed rather than reworded, since Maven Central publishing is in practice a separate ./gradlew publish run, and the remaining GitHub/website steps renumbered 1–12. That retired the only caller passing a staging repository URL, so maven_sanity_check's repo_url parameter and add_repo_information went with it.

Version bumping was automated, and silently broken

update-checker-framework-versions does the bump — but it rewrote three files that no longer exist, looked for the quick-start page in the wrong directory, and keyed two rewrites off marker comments absent from the page. Ant's <replaceregexp> only warns on a missing file and does not complain at all about a marker that matches nothing, so all five failed silently. docs/checker-framework-quick-start.html still advertised checker-framework-2.1.7.zip as a result.

require-file and require-marker macros now assert every file and marker before anything is rewritten. Verified both ways against a scratch copy with the real AFU build.properties: the happy path rewrites all five files and updates the AFU date and zip name; removing a file or renaming a marker gives BUILD FAILED, before any change is made.

The examples now belong to Renovate alone

The release scripts and Renovate were both bumping docs/examples, and fighting: Renovate rewrites a pom property in place and drops marker comments around it — it did that to MavenExample-framework-all/pom.xml in #2017. The Ant target keyed off those same markers, including when preparing the copy the Maven sanity check compiles. Had Renovate reached MavenExample/pom.xml first, that check would have gone on compiling the previous release and still passed.

Two renovate.json changes make the handoff reliable. Every Renovate edit ever made to the example Gradle builds was a plugin version; the eisop version in them was last bumped by Dependabot, removed in #1997. Those builds hold it in a variable, so rather than guess at Renovate's Gradle variable resolution there is now an explicit customManagers regex — matching this repository's existing practice for Bazel, the JDK EA build and ruff. And the repository-wide minimumReleaseAge: 7 days made no sense for our own artifact, so io.github.eisop is now 0 days in its own PR.

The release zip shipped a broken example and no README

checker-includes had drifted the same silent way — an Ant include pattern that matches nothing is not an error either.

  • README.html was renamed away in 2015 and is now README.md, so no README shipped in the distribution at all.
  • "Make class name and file name the same" (2020) moved the units-extension qualifiers into qual/ and renamed Demo.java, so that example shipped its Makefile, README and Expected.txt and not one Java source file — the shipped Makefile refers to files that are not in the zip.

Confirmed against the published release rather than inferred: under /cf/checker-framework-3.49.3-eisop1/examples/units-extension/, README is 200 while UnitsExtensionDemo.java is 404, and README.md is 404.

Two documents were built every release and thrown away

Both only ever reached the interm site directory that nothing publishes for eisop.

  • The quick-start guide was kept version-correct by the Ant target but shipped nowhere and appeared on no website. It now travels in the zip like the manual and the tutorial, and its links — which resolved only through the 404 page's JavaScript redirect — are site-relative.
  • The dataflow manual. release_build.py already runs make in dataflow/manual, and the website-docs target copies the result under the very name the old front page linked to. It now ships at docs/manual/checker-framework-dataflow-manual.pdf, which the website generator surfaces with no change on its side.

Follow-ups, not in this PR

checker-framework-webpage.html is deliberately untouched here. A follow-up PR ships it in the release zip as that release's own page and retires the website's cf-template.md: one template shared across every release is a standing mismatch, because a link added for content that arrives in release N is broken on the archived page of every release before N. That PR must merge before the next release, since only a release built after it can carry the page.

Companion website PRs: eisop/eisop.github.io#101, #102, #103, #104.

Testing

spotlessCheck, requireJavadoc/javadocDoclintAll, ruff format --check, ruff check and py_compile pass. release.xml and the edited pom parse; renovate.json is valid JSON and its new customManagers regex was checked to select all five example builds and extract the current version. The Ant target was exercised end-to-end on a scratch copy, both passing and failing. The signing guard was verified in all three states — no key set, key set, and snapshot — with build.gradle restored afterwards.

🤖 Generated with Claude Code

https://claude.ai/code/session_012n7eKNuv1S2mTden7x56ie

…l release step

Two asks: publish nightly snapshots, and stop finishing every release by hand
on the Central Portal website. Both turn out to need less than expected.

Snapshots already go to the Central Portal snapshot repository -- the URL in
gradle-mvn-push.gradle is the right one -- and they are deliberately unsigned,
so a nightly job needs no publishing changes and no GPG key. What is missing is
a scheduled workflow, a per-namespace "enable SNAPSHOT" setting in the Portal,
and two secrets.

The manual click on a release exists because releases upload through
ossrh-staging-api.central.sonatype.com, Sonatype's compatibility layer for
builds that predate the Portal API, which leaves the deployment for a human to
publish. That service has an API for the last step:

  POST /manual/upload/defaultRepository/io.github.eisop?publishing_type=automatic

so the click can go away without changing how the build uploads anything.

The document recommends against adopting a Portal-native publishing plugin.
com.vanniktech.maven.publish and the alternatives configure publications for
you, and this build has eighteen, several hand-tuned: checker publishes
components.shadow rather than components.java, with a comment explaining that
the latter would ship the skinny jar under the fat jar's name, and a Gradle
attribute copied onto the shadow configuration by hand. Re-expressing that
inside another plugin's model is risk for no gain when the upload already
works. The document says which plugin to revisit if the publications are ever
simplified.

Adds .github/workflows/publish-snapshot.yml as a starting point, inert until
the secrets exist: a first job checks for SONATYPE_NEXUS_USERNAME and the
publish job is skipped without it, so merging publishes nothing by itself. It
also refuses to run if the version does not end in SNAPSHOT, since a release
upload cannot be undone. The version-extraction command and the workflow's
shell were both checked locally.

Nothing here is enabled. Enabling it is adding the secrets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 16, 2026 13:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

wmdietl and others added 2 commits September 16, 2026 09:18
The six links to search.maven.org in the manual no longer resolve: that host
answers 403 for both its /search and /artifact URLs, with a browser user agent
as well as without, while the same queries on central.sonatype.com answer 200.

Rewrite them, and drop the trailing "/jar" segment that the artifact URL form
no longer takes. Each rewritten URL was fetched to confirm it resolves. Only
the URLs change; the surrounding LaTeX is untouched.

The publishing URLs in the build were already current: snapshots go to
central.sonatype.com/repository/maven-snapshots/ and releases to the
ossrh-staging-api host, and no oss.sonatype.org or s01.oss.sonatype.org
reference remains anywhere in the tree. The occurrence in docs/CHANGELOG.md is
in a released section and is left as the historical record.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found while tracing how a release reaches Maven Central.

release_push.py steps 5b and 5c walk the releaser through the OSSRH Nexus
staging UI -- "click on iogithubeisop-XXXX", "Click close at the top", "Copy
the URL of the closed artifacts (in the bottom pane)" -- and then ask them to
paste that URL back into the script. The Central Portal has no close step and
no such panes, so anyone following the script looks for a button that no
longer exists. Adopting the one-call automation removes these steps rather
than requiring them to be reworded.

A release has to bump the version in 34 places across 12 files besides
build.gradle: the examples under docs/examples, and several places in the
manual and the webpage. They are consistent today, and keeping them so is
manual; a missed one leaves the manual telling readers to depend on a version
that is no longer newest.

docs/examples/publish-smoketest builds against publishToMavenLocal, so it
checks the artifacts the build would publish but not that a deployment
arrived. Once nightly snapshots exist, pointing it at the published snapshot
repository would catch a broken publication on the day it breaks.

And the documented release command hardcodes one maintainer's GPG key, which
reads as the value to use rather than as the releaser's own.

README-eisop.md now says that the key is the releaser's own, that the upload
is staged and not live until published, where that is done, and that other
files carry the version too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@wmdietl wmdietl changed the title Investigate Maven Central publishing: nightly snapshots and the manual release step Maven Central: nightly snapshots, the manual release step, and stale URLs Sep 16, 2026
wmdietl and others added 4 commits September 16, 2026 10:43
Push steps 5a/5b/5c and 10 drove the retired OSSRH Nexus staging UI: they
told the releaser to "close" artifacts in a top pane and paste a staging
repository URL back into the script. The Central Portal has no such step.
The staging call itself could never have worked here either: it read the
signing passphrase from /projects/swlab1/checker-framework/hosting-info/,
a University of Washington path that does not exist, and signed with
checker-framework-dev@googlegroups.com rather than the releaser's key.

Maven Central publishing is in practice a separate ./gradlew publish run,
so remove those steps rather than reword them, and renumber the remaining
GitHub/website steps 1-12. Removing step 5c retires the only caller that
passed a staging repository URL, so maven_sanity_check's repo_url
parameter and add_repo_information -- which edited poms to point at
org/checkerframework artifacts -- go with it. Document the separate
publish step in README-release-process.html.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The target rewrote files that no longer exist and keyed off marker
comments that pages no longer carry. Ant's <replaceregexp> only prints
"The following file is missing" and still reports BUILD SUCCESSFUL, and a
marker regex that matches nothing is not an error at all, so both kinds of
drift were silent.

Gone from the tree: docs/manual/checkerframework.gradle,
build-common.properties, checker/build.properties. Wrong path:
checkerQuickStartPage pointed into docs/manual/ rather than docs/.
Absent markers: checker-framework-version and compiler-version on
docs/checker-framework-webpage.html.

Drop those five rules, fix the quick-start path, and add require-file and
require-marker macros that assert every file and marker up front, so the
target now fails before rewriting anything. Also drop checker.tutorial and
checker.release, unused in this target, and the release.properties entries
left with no reader.

docs/checker-framework-quick-start.html still advertised
checker-framework-2.1.7.zip, having missed every release since the path
broke; bring it to the current release and fix its javac path, which had
bin/checker inverted relative to every other document.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ents

The release scripts and Renovate were both bumping docs/examples, and
fighting. Renovate rewrites a pom property in place and drops marker
comments around it -- it did that to MavenExample-framework-all/pom.xml in
PR #2017. The Ant target keyed off those same markers, including in
update-and-copy-maven-example, which sets the version on the copy the
Maven sanity check compiles. Had Renovate reached MavenExample/pom.xml
first, that check would have gone on compiling the previous release and
still passed.

Give the examples to Renovate alone: drop both example rules from
update-checker-framework-versions, match the <checkerFrameworkVersion>
element rather than markers when preparing the sanity-check copy, and
remove the now-vestigial markers from MavenExample/pom.xml.

Two renovate.json changes make that reliable. Every Renovate edit ever
made to the example Gradle builds was a plugin version; the eisop version
in them was last bumped by Dependabot, removed in PR #1997. These builds
hold it in a variable (an ext.versions map entry, or a plain def), so add
an explicit customManagers regex rather than rely on Renovate's Gradle
variable resolution. Second, the repository-wide minimumReleaseAge of 7
days delayed our own artifact a week behind every release, so set it to 0
for io.github.eisop and give it its own PR.

Separately, checker-includes had drifted the same silent way, and an Ant
include pattern that matches nothing is not an error either. README.html
was renamed away in 2015, so no README shipped in the distribution at
all. "Make class name and file name the same" moved the units-extension
qualifiers into qual/ and renamed Demo.java in 2020, so that example
shipped its Makefile, README and Expected.txt and not one Java source --
the shipped Makefile refers to files that are not in the zip. Verified on
the published site, whose newest release is 3.49.3-eisop1: under
/cf/checker-framework-3.49.3-eisop1/examples/units-extension/, README is
200 while UnitsExtensionDemo.java is 404, and README.md is 404.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Maven Central does not pin a signing key to a namespace; it only checks
that each signature verifies against a public key it can fetch from a
keyserver. Several maintainers can therefore each sign with their own key,
and nothing in this repository has to change when a new one starts
releasing. Only the documentation implied otherwise, by presenting one
maintainer's key as the value to pass.

Move the key out of the command line and into the releaser's own
~/.gradle/gradle.properties, next to the Portal tokens already documented
there, so the release command names no key at all. Fail the signing task
with an explicit message when signing.gnupg.keyName is unset: gpg would
otherwise fall back to whichever secret key happens to be its default,
which may not be one Central can verify. Snapshots are unsigned, so the
check does not affect them.

Record the release-process findings from this audit in
maven-central-publishing.md: two web pages that the release still
rewrites but that nothing publishes (the main one has already been ported
to the website repository's cf-template.md, minus its Dataflow Framework
bullet), and the fact that the website is still on 3.49.3-eisop1 because
nothing runs the site generator as part of a release.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two documents were produced at release time and then discarded, because
they only ever reached the interm site directory that nothing publishes
for eisop.

docs/checker-framework-quick-start.html was kept version-correct by
update-checker-framework-versions but shipped nowhere and appeared on no
website. Add it to checker-includes so it travels in the release zip
alongside the manual, the tutorial and the CHANGELOG. Its links pointed at
https://eisop.github.io/cf/manual/#anchor, which resolves only through the
404 page's JavaScript redirect; make them site-relative so they work both
at cf/quick-start.html and in an archived cf/<release>/quick-start.html.

release_build.py already runs `make` in dataflow/manual, and
checker-framework-website-docs copies the result to the interm directory
under the very name the old front page linked to. Add it to the zip at
docs/manual/checker-framework-dataflow-manual.pdf with a fullpath
zipfileset, the mechanism already used for CFLogo.png. Because the website
generator lifts docs/manual wholesale, it surfaces at
cf/manual/checker-framework-dataflow-manual.pdf with no generator change.

eisop/eisop.github.io#103 lifts the quick-start guide to cf/quick-start.html,
restores the Dataflow Framework bullet that was dropped when the front page
was ported to cf-template.md, and links both from the front page.

checker-framework-webpage.html is deliberately left untouched here: a
follow-up PR ships it in the release zip as that release's own page, which
is the opposite of the "not published, do not edit" note that would
otherwise have landed with this commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@wmdietl wmdietl changed the title Maven Central: nightly snapshots, the manual release step, and stale URLs Audit docs/developer/release: remove the retired Maven Central steps, fail on drift, and ship what the release builds Sep 16, 2026
wmdietl and others added 2 commits September 17, 2026 19:16
The snapshot-publishing workflow was written against generic examples
rather than this repository's own workflows, so it selected the temurin
JDK and pinned actions/checkout, actions/setup-java, and
gradle/actions/setup-gradle at v4.  ci.yml and dependency-submission.yml
use zulu and much newer pins, and Renovate has been bumping only those
two, because this file exists on a branch.

Switch to zulu and pin checkout@v7, setup-java@v6.0.1, and
setup-gradle@v6.3.0, matching the other workflows as of this merge.  The
v4 pins of checkout and setup-java run on an older Node runtime that
GitHub warns about.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wmdietl and others added 2 commits September 17, 2026 20:14
The document had grown a 250-line section recording each release-process
problem this branch found and fixed: the retired OSSRH Nexus steps in
release_push.py, the silently drifted version-bump target, the examples'
version ownership, the zip's missing README and broken units example, the two
unpublished web pages, the discarded dataflow manual.  That is commit and pull
request material, and it is in the commits; a reader arriving from
gradle-mvn-push.gradle or the release checklist wants to publish.

Keep what is still open, as a "Known gaps" section: the smoke test verifies
publishToMavenLocal output rather than what reached Central, and the website
generator is a manual step missing from the release checklist.  Drop the
version numbers, dates and 404 URLs that recorded the site being a release
behind, which is true only until the next generator run.

Also fold the recommendation into the section that explains the manual click,
and the rejected plugins into one "Why no publishing plugin" section.  422
lines to 181.  The #signing-any-maintainer-can-sign-a-release anchor that
README-eisop.md links is unchanged, and the claims the other three referring
files make about this document still hold.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#1060 asked to move publishing to the Central Portal and JReleaser.  The
first half is already true, and this branch's evaluation rejected the second:
the plugins that speak the Portal API natively configure publications, and
this build has 18 hand-tuned ones.  The issue is closed as superseded, so it
belongs in this release's list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@wmdietl
wmdietl merged commit 5391ed0 into master Sep 18, 2026
18 of 45 checks passed
@wmdietl
wmdietl deleted the maven-central-publishing branch September 18, 2026 00:31
wmdietl added a commit that referenced this pull request Sep 18, 2026
PR #2097 was squash-merged, so master carries its twelve commits as one commit
that is not an ancestor of this branch, and the eight of them that this branch
also contains came through a different route.  Four files conflicted:

- `publish-snapshot.yml` and `maven-central-publishing.md`: master's versions
  are newer -- the workflow's pins were aligned with the other workflows, and
  the document was trimmed -- and this branch never touched either after
  forking, so master's win.
- `checker-includes` and `release.xml`: both sides add entries.  Kept the union
  -- master's `README.md` and units-extension corrections, and this branch's
  front page and favicon.

One thing was lost with the trimmed document: the front page's relative versus
absolute link scheme, which is a trap for a future editor.  It is restored as a
comment in the page itself in the next commit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants