Continuous integration and releases¶
This page describes how changes reach a released version of
pysnmp-pysmi. The same model is used by
pyasn1 and
pysnmp, so what follows applies to
all three projects except where a difference is called out.
Branches¶
There are exactly two long-lived branches.
nextWhere work integrates. Every pull request targets
next. It is normal and expected fornextto carry commits thatmaindoes not.mainWhat has been released. It moves only when
nextis promoted into it.
Maintenance branches matching N.x or N.M.x are configured but none
exist yet.
Do not open a pull request against main. A fix merged there would be
released without ever having existed as a release candidate, and next
would not contain it.
Neither branch releases on push. Both the release candidate and the GA are cut by a human dispatching the workflow; see Cutting a release.
Commit messages¶
Releases are cut by semantic-release from the commit history, so the subject line of every commit decides whether a release happens and how the version changes.
The Conventional Commits
conventionalcommits preset is in use:
Subject |
Version change |
Appears in release notes |
|---|---|---|
|
patch |
Bug Fixes |
|
minor |
Features |
|
patch |
Performance Improvements |
|
major |
Features, and BREAKING CHANGES |
|
major |
BREAKING CHANGES |
|
none |
hidden |
The ! marker and the BREAKING CHANGE: footer are equivalent. Note that
the angular preset, which is semantic-release’s default, does not parse !
at all — a feat!: commit under that preset silently produces a minor
release rather than a major one. PySMI sets conventionalcommits
explicitly for this reason.
Reference an issue in the body (Closes #123) rather than in the subject, so
the generated notes link it.
The format is checked rather than assumed. commitlint reads commitlint.config.mjs at the
repository root from two places: the commit-msg hook installed by
pre-commit install checks a message as it is written, and the Commit
conventions workflow checks every commit in a pull request. Run pre-commit
install once per checkout — a checkout made before the hook was added has to
run it again, because installing the commit-msg hook type is what makes the
check run at all.
Merge commits, fixup! commits and the subjects git writes for a revert are
ignored. The pull request title is not checked: pull requests here are merged
rather than squashed, so the title never enters the history. What the config
changes relative to @commitlint/config-conventional, and how to lint a
range by hand, is in .github/semantic-release.md.
What runs on a pull request¶
The CI workflow (.github/workflows/build-test-release.yml) runs on
every push to main and next and on every pull request against them.
pre-commitThe repository’s
pre-commithooks, on every file.docssphinx-build -n -W --keep-going. Warnings are errors,-nreports cross-references that resolve to nothing, and--keep-goingreports all of them rather than stopping at the first.builduv build, uploading the wheel and sdist as an artifact.matrixComputes the unit-test matrix. See below.
test-unitThe unit test suite, once per matrix entry, publishing JUnit results and coverage.
test-consumerA non-gating smoke test that loads generated modules under
pysnmp. Regressions there are worth seeing, butpysnmpships on its own cycle and an upstream breakage is not a PySMI defect.Build ReleaseRuns semantic-release only on pushes to
mainornext, and on manual dispatches from those branches. On a push it rehearses the release without cutting one.
One check runs outside that workflow. Commit conventions
(.github/workflows/commit-conventions.yml) lints the commit messages of a
pull request and runs on pull requests only, so it is not part of the release
path. Like the CI jobs, it gates a merge only where branch protection names
it as a required check.
The test matrix¶
Every supported Python runs on Linux, and the edge Python versions run on
Windows, on every pull request. macOS runs on the edge versions too, but only
when the run can justify the cost: a push to main or next, a manual
dispatch, or a pull request carrying the ci:full-matrix label.
Trigger |
Linux |
Windows |
macOS |
|---|---|---|---|
Pull request |
3.10 – 3.14 |
3.10 and 3.14 |
— |
Pull request labelled |
3.10 – 3.14 |
3.10 and 3.14 |
3.10 and 3.14 |
Push to |
3.10 – 3.14 |
3.10 and 3.14 |
3.10 and 3.14 |
That is seven jobs on an ordinary pull request and nine on a broad run.
These are pure-Python tests, so the interpreter version is where most of the
risk lives – but not all of it. Windows is the platform whose line endings,
path separators and missing pwd module this package has to account for,
and a Windows-only regression that a pull request does not run is one found on
next instead. macOS has yet to catch anything Linux did not, so it stays
behind the label.
To get the broad matrix on a pull request, add the ci:full-matrix label.
The workflow listens for the labeled event, so the run starts when the
label is applied — no need to push again.
Cutting a release¶
Nothing releases on push. Both kinds of release are cut by dispatching the
CI workflow from the Actions tab and choosing the branch:
nextCuts a release candidate,
X.Y.Z-rc.N.mainCuts the GA release,
X.Y.Z.
semantic-release works out the version from the commits, writes it into
pysmi/__init__.py and CHANGELOG.md, commits that as
chore(release): X.Y.Z, tags it, and publishes a GitHub release.
To ship a GA: promote next into main with a pull request, wait for it
to be green, then dispatch the workflow against main.
Every push — to next as much as to main — is a dry run instead.
semantic-release computes the version it would cut and tags nothing. Because
its prepare step never runs, nothing is built either, so the workflow builds
that version as X.Y.Z.devN and keeps it as an artifact. Install that
artifact and exercise it before dispatching the real release.
Releasing by hand rather than on every merge is deliberate. Automatic release
candidates are what let next drift below a GA already on PyPI; see
Keeping next and main in step.
Keeping next and main in step¶
Promoting next into main and then releasing leaves the
chore(release): commit, and the tag on it, on main only. next does
not contain them, so as far as semantic-release can see from next, the
newest release is still the last one that branch cut.
Left alone this drifts. In PySMI it produced 2.0.0rc15 and 2.0.0rc16 on
PyPI after 2.0.1 had shipped: release candidates numbered below a
released version, uploaded after it.
So after cutting a GA on main, merge main back into next. This is
the only direction in which main should ever be merged into next, and
it carries nothing but the release commit.
Commits on next that are not on main are the normal state of things and
need no action: that is unreleased work waiting for the next promotion.
Reaching PyPI¶
Publishing runs as the final step of the Build Release job after
semantic-release has cut a real release. A tag no longer re-runs the whole CI
workflow, so the release is still cut once, from its branch, and the upload
uses the distributions that semantic-release prepared.
The upload uses PyPI Trusted Publishing. No API token exists anywhere in the repository. PyPI matches four claims from the run’s OIDC token — owner, repository, workflow filename, and environment name — and mints a credential valid for that run alone. Under Trusted Publishing the upload also carries PEP 740 attestations by default.
The trusted publisher is registered on the PyPI project’s Publishing settings page and must name the workflow file exactly:
Claim |
Value |
|---|---|
Owner |
|
Repository |
|
Workflow name |
|
Environment name |
|
Renaming the workflow file or the environment breaks publishing until the publisher is re-registered.
Troubleshooting¶
A run was cancelled and no step failed. Check whether another push to the same branch superseded it. The workflow keeps one run per ref and cancels older push-triggered runs; manual dispatches are never cancelled.
A release did not happen after merging to next. Merging does not
release; dispatch the workflow. If a dispatch also released nothing, check the
commit subjects — only fix, feat, perf and breaking changes produce
a release, so a branch of nothing but docs and chore commits correctly
releases nothing. The Build Release job log states which commits it
analysed and what it concluded.
PyPI rejected the upload with 403. The trusted publisher does not match. Confirm all four fields on the PyPI publishing settings page against the table above; the workflow filename is the one most often wrong.