ci: release pipeline with abi3 wheels, smoke tests and trusted publishing (#60) - #232
Merged
Merged
Conversation
…hing (#60) Replaces the Release Readiness workflow with a tag-driven release: abi3 wheels for Linux x86_64/aarch64, macOS x86_64/arm64 and Windows x86_64 plus an sdist, each installed into clean venvs on Python 3.11-3.13 and run through the Quickstart; PyPI trusted publishing, cargo publish and a GitHub Release with notes extracted from CHANGELOG.md, on a v* tag only. workflow_dispatch is a dry run by default, and pull requests touching packaging files run the build half. The PyPI distribution name is read from pyproject.toml and appears nowhere else. Refs #60 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alls Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Refs #60. Not
Closes: the first publish still needs the distribution name (#58) and the one-time maintainer setup below.What changed
.github/workflows/release.yml("Release Readiness") is replaced by a release pipeline:v*workflow_dispatch(dry_run, default true)metadata: reads name/version/toolchain; the three manifest versions must agree; tag must bev<version>release-notes: section ofCHANGELOG.md(required on a publish; dry runs fall back to Unreleased)release-check(fmt, clippy, fast tests, benches compile) +release-slow-tests(SADF)crate-package:cargo package --list+cargo publish -p openquant --locked --dry-runwheels: maturin-action, linux x86_64 + aarch64 (manylinux_2_28, native arm runner), macOS x86_64 + arm64, windows x86_64smoke: each wheel on a fresh runner, clean venvs for Python 3.11, 3.12, 3.13, runs the Quickstart (scripts/release/smoke_wheel.py)sdist: built, then a wheel built from the sdist alone, installed and smoke-testedpublish-pypi(trusted publishing,pypienvironment,id-token: write)dry_run=falseon a tag refpublish-crate(cargo publish -p openquant,CARGO_REGISTRY_TOKEN)github-release(notes from CHANGELOG, wheels + sdist attached)Publish jobs depend on every build/test job. A dispatch with
dry_run=falseon a branch fails inmetadata.The distribution name lives only in
pyproject.toml.scripts/release/release_info.py metadatareads[project] name; the workflow uses that output (environment URL, smoke test'simportlib.metadatacheck) and never spells the name out. Choosing the name in #58 is a one-line change topyproject.tomlplus the trusted-publisher entry. Thepyopenquantplaceholder was left as is.abi3:
[tool.maturin] features = ["pyo3/abi3-py311"]inpyproject.toml, so there is onecp311-abi3wheel per platform. PyO3 0.29 and pyo3-polars 0.28 use only the limited API (pyo3-polars goes through Python polars'Series._export/_importandPyCapsule_Import), and the bindings use no non-limited API. The feature is set for maturin only, socargo test --workspaceand clippy still build against the full API. The smoke test checks the installed extension really is the abi3 build (_core.abi3.so/_core.pyd) and loads on 3.11, 3.12 and 3.13.Toolchain/maturin pins: the Rust toolchain is read from
rust-toolchain.toml(1.98.1) and passed to maturin-action anddtolnay/rust-toolchain; maturin is pinned to v1.15.0 (the version inuv.lock; pyproject requires >= 1.9.4).New files:
scripts/release/release_info.py(version checks + changelog notes extraction, stdlib only),scripts/release/smoke_wheel.py(the Quickstart against an installed wheel),python/tests/test_release_info.py(14 tests).Docs:
docs/publishing.mdrewritten (procedure, one-time setup, job matrix); a Releases section inCONTRIBUTING.md; governance page, Python bindings page (abi3) anddocs/stabilization_productionization.mdupdated; CHANGELOG entry.Blockers listed in #60
Verification
pytest python/tests/test_release_info.py: 14 passed locally;ruff check/ruff format --checkclean; actionlint clean.release_info.py metadata→name=pyopenquant version=0.1.0 rust_toolchain=1.98.1;notes 0.1.0 --allow-unreleasedreturns the Unreleased section.Release(run 36802886142): all five wheels built ascp311-abi3(manylinux_2_28_x86_64,manylinux_2_28_aarch64, macOS x86_64, macOS arm64,win_amd64), and each smoke job passed on Python 3.11, 3.12 and 3.13 (extension_core.abi3.so/_core.pyd, Quickstart numbers identical to the docs page: portfolio_sharpe 25.303744, net_total_return -0.001511). sdist built, a wheel built from it alone passed the same smoke test.cargo publish -p openquant --dry-runpackaged 120 files, verified, and aborted the upload. Publish jobs skipped.workflow_dispatchwithdry_run=trueon this branch (run 36802888497): every job passed, including release-check and the long SADF test; publish jobs skipped.Scripts/python, on Windows); fixed in the second commit.pythonjobs now build the extension as abi3 too (maturin reads the rootpyproject.toml); tests and stubtest pass on 3.11 and 3.13.One-time maintainer setup (checklist)
[project] nameinpyproject.toml(one line).Open-Quant, repoopenquant, workflowrelease.yml, environmentpypi.pypi; recommended: deployment tags limited tov*and a required reviewer.publish-new+publish-update, scoped to crateopenquant; save as repository secretCARGO_REGISTRY_TOKEN.v*tags.mainwithdry_runchecked; all jobs green.Cutting v0.1.0 once #58 is decided
pyproject.toml(versions already 0.1.0 inpyproject.toml,crates/openquant/Cargo.toml,crates/pyopenquant/Cargo.toml); runuv lock.CHANGELOG.mdrename## Unreleasedto## 0.1.0 - YYYY-MM-DD, add an empty## Unreleasedabove it, update the intro paragraph ("nothing is published"); runpython3 scripts/docs/generate_site_pages.py --write.python3 scripts/release/release_info.py metadata --tag v0.1.0andpython3 scripts/release/release_info.py notes 0.1.0.Releaserun pass. Optionally dispatch a dry run onmain.git checkout main && git pull && git tag -a v0.1.0 -m "OpenQuant 0.1.0" && git push origin v0.1.0pypideployment if a reviewer is required; the run publishes to PyPI and crates.io and creates the GitHub Release.pip install <name>.🤖 Generated with Claude Code