Release Checklist
Step-by-step process for cutting a doppler release. Run these steps in order — each one is a gate for the next.
1. Verify the tree is clean
git status # nothing uncommitted
make test-all # C (CTest) + Python (pytest) + Rust (cargo test)
make docs-build # docs build clean --strict
All suites must pass. Fix failures before continuing.
2. Update CHANGELOG.md
In CHANGELOG.md:
- Rename
## [Unreleased]→## [X.Y.Z] — YYYY-MM-DD - Add a fresh empty
## [Unreleased]section above it - Update the comparison links at the bottom of the file:
[Unreleased]: https://github.com/doppler-dsp/doppler/compare/vX.Y.Z...HEAD
[X.Y.Z]: https://github.com/doppler-dsp/doppler/compare/vPREV...vX.Y.Z
Commit the CHANGELOG separately so the diff is easy to review:
git add CHANGELOG.md
git commit -m "docs: update CHANGELOG for vX.Y.Z"
3. Bump the version
make bump-version updates five files atomically:
| File | Field |
|---|---|
pyproject.toml |
version |
python/specan/pyproject.toml |
version |
python/cli/pyproject.toml |
version |
ffi/rust/Cargo.toml |
version |
CMakeLists.txt |
project(doppler VERSION …) |
make bump-version VERSION=X.Y.Z
Review the diff, then commit:
git diff
git add pyproject.toml python/specan/pyproject.toml python/cli/pyproject.toml \
ffi/rust/Cargo.toml CMakeLists.txt
git commit -m "chore: release vX.Y.Z"
4. Tag and push
make tag-release creates an annotated tag and pushes it, which
triggers the release workflow automatically:
make tag-release VERSION=X.Y.Z
!!! warning "This push is irreversible" Once the tag is pushed, the release workflow starts and PyPI uploads begin. Double-check the version number before running.
5. Release workflow (automatic)
The release.yml
workflow runs these jobs in order:
verify-version
│
▼
build-python ── matrix ──┬── ubuntu-latest (manylinux_2_28 x86_64 wheels via cibuildwheel)
└── macos-14 (arm64 wheels via cibuildwheel)
│
▼
publish-python ── PyPI (OIDC trusted publishing, no token needed)
│
▼
github-release ── GitHub Release + auto-generated notes + wheel attachments
What cibuildwheel does per Python version (cp312, cp313):
before-all— install system deps (zeromq-devel,fftw-devel), build C librarybefore-build— clean stale.sofiles, build and copy extensions for this interpreteruv_build— package the wheelrepair-wheel-command(scripts/retag_wheel.sh) — retagpy3-none-any→cpXYZ-cpXYZ, thenauditwheel repair(Linux) /delocate-wheel(macOS) to bundle shared-lib deps
verify-version checks — the workflow fails immediately if any of these disagree with the tag:
pyproject.tomlpython/specan/pyproject.tomlpython/cli/pyproject.tomlffi/rust/Cargo.tomlCMakeLists.txt
If it fails, bump the missed file manually, push a fixup commit on main, then re-tag.
6. Verify the release
Once the workflow goes green:
# Fresh venv — confirm all three packages install and import
python -m venv /tmp/doppler-verify && source /tmp/doppler-verify/bin/activate
pip install doppler-dsp==X.Y.Z doppler-specan==X.Y.Z doppler-cli==X.Y.Z
python -c "import doppler; print(doppler.__version__)"
doppler --help
doppler-specan --help
Check the GitHub Release page to confirm wheels for all three platforms are attached.
7. Bump to the next development version
Immediately after tagging, advance to the next alpha so main is
never at a released version:
make bump-version VERSION=X.Y.(Z+1)a0
git add pyproject.toml python/specan/pyproject.toml python/cli/pyproject.toml \
ffi/rust/Cargo.toml CMakeLists.txt
git commit -m "chore: begin vX.Y.(Z+1) development"
git push origin main
Version conventions
Doppler uses Semantic Versioning with stable
X.Y.Z releases only — no alpha/beta/rc suffixes.
| Increment | When to use |
|---|---|
Patch (Z) |
Bug fixes, no API changes |
Minor (Y) |
New features, backwards-compatible |
Major (X) |
Breaking API changes |
!!! note "Pre-1.0"
Minor releases may contain breaking changes before 1.0.0.
The CHANGELOG is the authoritative record.