Release process#
How a new version of femorph-solver ships: bumping the
version, the changelog, the docs-multiversion story for old-tag
galleries.
Versioning#
The package version lives at
src/femorph_solver/_version.py (driven by setuptools-scm
via the version_file knob in pyproject.toml). At build
time, setuptools-scm writes the resolved version into
_version.py based on the latest annotated git tag.
The semver mapping is the standard one:
Major — public API breaks; stale-tag galleries may stop working. Reserved for genuine compatibility breaks (currently 0.x; pre-1.0 doesn’t promise stability).
Minor — new features, new VM rows, new element kernels, new readers. Backward-compatible. This is the common cadence.
Patch — bug fixes, doc tweaks, perf improvements that don’t move public APIs.
setuptools-scm derives intermediate-release strings (e.g.
0.21.dev3+g<hash>) automatically when commits are ahead of
the latest tag. Don’t hand-edit _version.py.
Cutting a release#
Steps, in order:
Pre-flight. Confirm CI is green on
main, therelease-readinessworkflow’s diff against the previous tagged release shows no perf regressions ≥ 10 %, the docs build is clean.Update
CHANGELOG.rst(underdoc/source/changelog.rst— populated since #619). Move the in-progress section to the new version’s heading; group entries by category (Features, Fixes, Docs, Verification, Internal). Cite the PR number for each entry.Tag.
git tag -a v<MAJOR>.<MINOR>.<PATCH> -m "Release <MAJOR>.<MINOR>.<PATCH>" git push origin v<MAJOR>.<MINOR>.<PATCH>
Wheel build + upload. The release CI workflow handles PyPI upload via Trusted Publisher; no API tokens to manage.
Multiversion docs build. The
docsworkflow on the tag fires sphinx-multiversion with the new tag in scope; the per-version sidebar updates automatically (see below).Announcement. GitHub release notes are auto-populated from the changelog. Edit if needed.
Changelog conventions#
Phase 5 of #583 populated the changelog from v0.20.0 onwards. The conventions:
One section per version, headed
v<X.Y.Z> — YYYY-MM-DD.Within a section, entries are grouped under
Features,Fixes,Docs,Verification,Internal.Each entry is a one-liner that names what shipped, with the PR number in parentheses. Example:
Features -------- * Add ``StressSpec`` to the verification registry — unblocks NAFEMS LE1 and Kirsch K_t = 3 stress assertions (#626).
Cross-cutting changes (workflow / kernel / interop) reference any tracker / project they relate to (#345, #511, #601).
Don’t paraphrase the PR title — the changelog is for searchable history, not marketing.
The “in-progress” section header is Unreleased; entries
accumulate there until the cut.
sphinx-multiversion#
The docs deploy ships multiple versions in one site so users
on older releases can find the docs that match their installed
version. The implementation is sphinx-multiversion (config
in doc/source/conf.py).
How it works:
When the docs CI runs
sphinx-multiversion(see thedocsworkflow), it walks the git history and picks out every annotated tag matching the version pattern, plusmain.For each, it checks out the tag, runs Sphinx, and writes the built HTML under
_build/html/<version>/.A version switcher in the sidebar (powered by
doc/source/_static/switcher.json) lets readers move between versions.
Caveat that bit us in the past#
sphinx-multiversion checks out each tag’s source tree but
runs against the currently installed library. Old-tag
galleries that import from the installed
femorph_solver see the new library’s API, not the API the
tag used to ship. This caused issue #487 — a 1-D
displacement shape compatibility shim had to land in
static_result_to_grid so old-tag examples kept rendering.
The implication for authors: when you change a public API, the old-tag galleries are part of the test surface. Either:
Keep a back-compat shim in the new code (the standard approach — code stays simple, old galleries work).
Or accept that the docs deploy fails until the
switcher.jsoncuts the old tag from the version list.
Don’t surprise this on a release.
The release-readiness gate#
Before any tag fires, the release-readiness workflow has to
be green on the commit that’s about to be tagged. See
Performance — profiling, snapshots, the perf tracker for what that workflow tracks; the short
version is “perf regressions ≥ 10 % block the docs deploy.”
If a release is urgent and a regression is acknowledged-but-
unfixed, the workflow can be workflow_dispatch-bypassed by a
maintainer with a written explanation in the release notes.
Don’t bypass casually — the perf history breaks if the bypass
isn’t documented.
Yanking a release#
Mistakes happen. When a release ships with a broken bit:
yankon PyPI marks the version unbroken-installable but not eligible forpip install femorph-solverwithout a pin.Push a patch release ASAP that fixes the bug; the
CHANGELOGentry for the patch cites the yank.Don’t delete the git tag. The yanked version is part of the history; deleting the tag breaks
sphinx-multiversionfor any reader on that version.
Common pitfalls#
Hand-editing
_version.py.setuptools-scmrewrites it on every build; the edit is silently overwritten.Tagging without a clean
main. The release CI ties the wheel to the commit at the tag; an unclean tree means an un-reproducible release.Forgetting to update the
switcher.json. Adding a new version requires a switcher entry so the version dropdown shows it. The CI doesn’t auto-populate it (yet — that’s a Phase 5 follow-up).Backward-incompatible API change with no shim. Old-tag galleries break. Either land a shim or coordinate the cut in
switcher.jsonin the same release.Empty changelog section. A release with no changelog entries means either nothing of note shipped (in which case why release?) or the entries were missed during the cut. The reviewer flags this in the release PR.
Where things live#
Concern |
Path |
|---|---|
Version source-of-truth |
|
|
|
Changelog |
|
Multiversion config |
|
Version switcher |
|
Docs build script |
|
Release-readiness gate |
|
GitHub release notes |
auto-populated from the tagged |