Audit docs/developer/release: remove the retired Maven Central steps, fail on drift, and ship what the release builds - #2097
Merged
Merged
Conversation
…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>
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>
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>
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>
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
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.
This was referenced Sep 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.gradlesends-SNAPSHOTversions tohttps://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 onSONATYPE_NEXUS_USERNAMEexisting.The manual click needs one API call, not a plugin.
docs/developer/maven-central-publishing.mdrecords 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 ifsigning.gnupg.keyNameis unset, rather than lettinggpgfall 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 withchecker-framework-dev@googlegroups.com.Removed rather than reworded, since Maven Central publishing is in practice a separate
./gradlew publishrun, and the remaining GitHub/website steps renumbered 1–12. That retired the only caller passing a staging repository URL, somaven_sanity_check'srepo_urlparameter andadd_repo_informationwent with it.Version bumping was automated, and silently broken
update-checker-framework-versionsdoes 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.htmlstill advertisedchecker-framework-2.1.7.zipas a result.require-fileandrequire-markermacros now assert every file and marker before anything is rewritten. Verified both ways against a scratch copy with the real AFUbuild.properties: the happy path rewrites all five files and updates the AFU date and zip name; removing a file or renaming a marker givesBUILD 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 toMavenExample-framework-all/pom.xmlin #2017. The Ant target keyed off those same markers, including when preparing the copy the Maven sanity check compiles. Had Renovate reachedMavenExample/pom.xmlfirst, that check would have gone on compiling the previous release and still passed.Two
renovate.jsonchanges 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 explicitcustomManagersregex — matching this repository's existing practice for Bazel, the JDK EA build and ruff. And the repository-wideminimumReleaseAge: 7 daysmade no sense for our own artifact, soio.github.eisopis now 0 days in its own PR.The release zip shipped a broken example and no README
checker-includeshad drifted the same silent way — an Ant include pattern that matches nothing is not an error either.README.htmlwas renamed away in 2015 and is nowREADME.md, so no README shipped in the distribution at all.qual/and renamedDemo.java, so that example shipped itsMakefile,READMEandExpected.txtand not one Java source file — the shippedMakefilerefers 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/,READMEis 200 whileUnitsExtensionDemo.javais 404, andREADME.mdis 404.Two documents were built every release and thrown away
Both only ever reached the interm site directory that nothing publishes for eisop.
release_build.pyalready runsmakeindataflow/manual, and the website-docs target copies the result under the very name the old front page linked to. It now ships atdocs/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.htmlis deliberately untouched here. A follow-up PR ships it in the release zip as that release's own page and retires the website'scf-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 checkandpy_compilepass.release.xmland the edited pom parse;renovate.jsonis valid JSON and its newcustomManagersregex 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 — withbuild.gradlerestored afterwards.🤖 Generated with Claude Code
https://claude.ai/code/session_012n7eKNuv1S2mTden7x56ie