Releasing¶
One tag publishes everything. .github/workflows/release.yml builds the
packages below, tests the wheels, then uploads them.
| Registry | Package | Built from | Installed with |
|---|---|---|---|
| PyPI | opssignal |
python/ + crates/python |
pip install opssignal |
| PyPI | opssignal-aws |
python/addons/aws + crates/python-aws |
pip install 'opssignal[aws]' |
| PyPI | opssignal-gcp |
python/addons/gcp + crates/python-gcp |
pip install 'opssignal[gcp]' |
| PyPI | opssignal-azure |
python/addons/azure + crates/python-azure |
pip install 'opssignal[azure]' |
| crates.io | opssignal-core |
crates/core |
cargo add opssignal-core |
| crates.io | opssignal-cli |
crates/cli |
cargo install opssignal-cli |
opssignal[all] installs all three add-ons. opssignal-ffi and the
opssignal-py* crates are publish = false: the Python crates only exist
inside wheels, and C users build the library from source.
Wheels are abi3, so one wheel per platform covers CPython 3.9 and later:
Linux x86_64 and aarch64 (manylinux and musllinux), Windows x86_64 and
macOS arm64 (Apple Silicon). Each package also gets an sdist. Intel Macs
have no wheel, because macOS runner minutes bill at 10x; there pip builds
from the sdist, which needs a Rust toolchain. To ship Intel Mac wheels,
uncomment the macos-x86_64 line in the wheels matrix in release.yml.
One-time setup¶
Nothing is published until this is done. PyPI needs no stored secret at all; crates.io needs a token for the first release only.
GitHub environments¶
In Settings > Environments, create five environments: pypi,
pypi-aws, pypi-gcp, pypi-azure and crates-io. Add yourself as a
required reviewer on each, so a pushed tag waits for your click before
anything is uploaded (the Actions run lets you approve them all at once).
Docs site¶
The docs are published by .github/workflows/docs.yml to the gh-pages
branch, one copy per version (built with
mike). The first run creates the
branch. Then, in Settings > Pages, set Source to Deploy from a
branch, branch gh-pages, folder / (root).
To publish docs for a tag that was cut before versioned docs existed, run
Actions > Docs > Run workflow on main with that tag, for example
v1.0.0-beta.1.
PyPI: trusted publishing¶
The projects don't exist yet, so register a pending publisher for each one at https://pypi.org/manage/account/publishing/ (do the same on https://test.pypi.org if you want a rehearsal). Add four entries, one per project, each with its own environment name:
| PyPI project name | Environment name |
|---|---|
opssignal |
pypi |
opssignal-aws |
pypi-aws |
opssignal-gcp |
pypi-gcp |
opssignal-azure |
pypi-azure |
All four use owner adithyapathipaka, repository OpsSignal and workflow
release.yml. The environments must differ: a trusted-publishing token can
create only the one project whose pending publisher it matched, so four
publishers with the same environment fail with "Non-user identities cannot
create new projects".
A pending publisher does not reserve the name: whoever uploads first gets it. The first successful release turns each one into a normal publisher.
crates.io: token first, then trusted publishing¶
crates.io only allows trusted publishing for crates that already exist.
- Sign in to https://crates.io with GitHub and verify your email address (publishing is refused without it).
- Create an API token at https://crates.io/settings/tokens with the
publish-newandpublish-updatescopes, limited to the cratesopssignal-coreandopssignal-cli, with a short expiry. - Save it as the secret
CARGO_REGISTRY_TOKENon thecrates-ioenvironment. - After the first release, open each crate's Settings > Trusted
Publishing on crates.io and add owner
adithyapathipaka, repositoryOpsSignal, workflowrelease.yml, environmentcrates-io. Then delete theCARGO_REGISTRY_TOKENsecret and revoke the token; the workflow switches to trusted publishing on its own when the secret is gone.
Cutting a release¶
- Open a PR that turns
## [Unreleased]inCHANGELOG.mdinto## [X.Y.Z] - YYYY-MM-DD, adds a fresh empty## [Unreleased]above it, updates the compare links at the bottom, and checks that every "Added in" note indocs/names this version. Merge it, so the tagged commit's docs and changelog match the release.
That section becomes the GitHub release notes: the workflow runs
scripts/release_notes.py, which adds install commands, the docs link
and a compare link to the previous tag. Write it for users (what they
get, what changed, what to watch for), not as a list of PRs. A tag
with no matching ## [X.Y.Z] section stops the run before it builds.
-
Tag the commit on
mainyou want to release and push the tag:git tag v0.2.0 git push origin v0.2.0The tag sets the version. Before building, the workflow runs
scripts/set_version.pyto write0.2.0intoCargo.toml([workspace.package]and theopssignal-corepin incrates/cli), the fourpyproject.tomlfiles, the extras pins inpython/pyproject.toml, andopssignal.__version__. It also points the docs links PyPI shows (each package's Documentation URL and README) at that version's docs, for examplehttps://adithyapathipaka.github.io/OpsSignal/0.2.0/. Thenscripts/check_versions.pyconfirms they all agree. You don't need a version-bump commit.Tags are
vX.Y.Zfor a release, orvX.Y.Z-alpha.N,-beta.Nor-rc.Nfor a pre-release; anything else stops the run before it builds. A pre-release keeps that form on crates.io (1.0.0-beta.1) and uses Python's spelling on PyPI (1.0.0b1). Neither registry gives it to users by default: they ask for it withpip install --pre opssignaloropssignal-core = "1.0.0-beta.1". The GitHub release is marked as a pre-release.The change is made only inside the release run. The files on
mainkeep their old version, somaindoesn't show which version was last released; the tags do. -
Approve the
pypi-aws,pypi-gcp,pypi-azure,pypiandcrates-iodeployments in the Actions run.
The run uploads the three add-ons first, then opssignal (its extras pin
the add-ons, so they must exist first), then opssignal-core and
opssignal-cli, and finally creates a GitHub release with every wheel and
sdist attached.
The same tag also publishes that version's docs: the site gets a
X.Y.Z (or X.Y.Z-beta.N) entry in its version picker, and if it is the
highest tag, latest (what the site's home page opens) moves to it. Merges
to main keep updating the dev version. This needs no approval.
To build and test everything without publishing, run the workflow by hand
from Actions > Release > Run workflow. Pull requests don't run it, to
save Actions minutes, so do this before tagging if a change touched the
packaging (a pyproject.toml, Cargo.toml or release.yml); otherwise a
packaging mistake only shows up when the tag runs, and stops it before
anything is uploaded.
If a release fails halfway¶
Registry uploads can't be undone, and a version number can never be reused on either registry, even after it is yanked or deleted.
- Before any upload (a build or smoke test failed): fix it, delete the tag, and tag again.
- After some uploads: use Re-run failed jobs on the same run. Both
publish jobs skip anything already uploaded, so a re-run picks up where
it stopped. If the fix needs a code change, tag the next patch
version and release that instead. Yank a broken version with
cargo yankor PyPI's Yank button rather than deleting it.