The version lives in one place, project(mlrisk VERSION x.y.z) in
CMakeLists.txt. The C reads it through version.h, the Python package reads
it through pyproject.toml, and the release workflow refuses to publish if a
tag disagrees with it.
Publishing uses trusted publishing: PyPI accepts a short-lived token that GitHub mints for the workflow run, so there is no API token in this repository and nothing to rotate or leak.
Under the project on PyPI, Settings, Publishing, add a GitHub publisher:
| Field | Value |
|---|---|
| Owner | haeganm |
| Repository name | walkforward |
| Workflow name | release.yml |
| Environment name | pypi |
The workflow name is the file name in .github/workflows/, letter for letter.
release.yaml does not match release.yml, and the failure PyPI reports for
that (invalid-publisher) does not say which field is wrong. The GitHub
environment pypi already exists and gates the publish job; add a required
reviewer to it if you want a manual approval before anything ships.
-
Set the version in
CMakeLists.txtand add the section toCHANGELOG.md. -
Commit, push to
main, and wait for CI to go green. -
Tag and push it:
git tag v3.4.2 git push origin v3.4.2
-
The
Releaseworkflow builds a source distribution and five wheels (Linux x86-64 and aarch64, macOS Intel and Apple silicon, Windows x64), runs the test suite against each wheel on the platform it was built for, and installs the sdist from scratch in a clean directory. A check job then runstwine check --strict, requires exactly five wheels named for the source version and each carryingwalkforward_native, and requires a## <version>section inCHANGELOG.md. Only then does it publish. A tag pushed with the section still headedUnreleasedfails at that check, after the wheels are built; delete the tag, add the section, tag again. Publishing skips files PyPI already has, so a re-run after a partial upload finishes the job. -
Write the GitHub release notes from the changelog section:
gh release create v3.4.2 --title "walkforward 3.4.2" --notes-file notes.md --latest
Run the Release workflow by hand from the Actions tab. The publish job only
runs for a tag, so a manual run builds and tests every wheel and leaves them as
downloadable artifacts. Do this after any change to the build, the packaging or
the supported platforms. The changelog check applies to manual runs too: after
a version bump, the section has to exist before the run goes green.
The binding is ctypes and never touches the Python C API, so each wheel is
tagged py3-none-<platform> and works on every Python 3 that meets
requires-python. That is why cibuildwheel builds a single CPython per
platform instead of one wheel per interpreter version: the others would be
byte-identical. The ordinary CI workflow still tests the package from source
on the oldest and newest supported Python.