Reusable GitHub Actions workflows that encapsulate the canonical Linux Foundation Python build / test / release pipeline. Adopt them from a thin caller workflow instead of copying hundreds of lines of pipeline YAML into every Python repository.
| Workflow | Purpose | Trigger style |
|---|---|---|
.github/workflows/build-test.yaml |
Single-arch build, test, audit, SBOM and Grype scan | Pull request |
.github/workflows/build-test-release.yaml |
Single-arch pipeline plus tag validation, release artefact attachment and draft-release promotion (the caller performs PyPI publishing) | Tag push |
.github/workflows/build-test-multiarch.yaml |
Multi-architecture (x64 + arm64) variant with native build hooks for C-extension projects | Pull request |
.github/workflows/build-test-release-multiarch.yaml |
Multi-architecture release variant | Tag push |
Each pipeline runs a repository-metadata job in parallel (an
informational step that does not gate the build). After python-build,
the test, audit and SBOM/Grype branches run in parallel - none gates
another, so a pull request surfaces every failure at once (jobs in
{ } run concurrently; -> denotes sequence):
python-build -> { python-tests | python-audit | sbom -> grype }
The release variants wrap this with tag-validate up front and a
release-promotion chain at the end. They also defer python-tests
until python-audit and grype have both passed, so a failing audit
skips the expensive test matrix and never reaches release promotion:
python-build -> { python-audit | sbom -> grype } -> python-tests
-> attach-artefacts -> promote-release
The release variants do not publish to PyPI. PyPI
trusted publishing matches the OIDC job_workflow_ref claim against the
calling repository, so a reusable workflow can never act as a
trusted publisher (see the PyPI docs). To keep
trusted publishing (and the PEP 740 attestations it enables), the caller
must run the publish jobs itself, stacked after the reusable call:
caller: release (reusable) -> test-pypi -> pypi
The reusable workflow exposes a tag output and leaves the run-scoped
build artefact in place, so the caller's publish jobs have everything
they need. See
examples/build-test-release/ for a
complete caller.
The multi-arch variants add a python-metadata job and fan the build,
test and audit jobs across an architecture matrix.
Copy a template from examples/ into your project's
.github/workflows/ directory and replace the placeholder uses: SHA with
a pinned release of this repository. Each workflow ships in two forms:
github.yaml— a plain GitHub-native caller (pull-request or tag-push triggered).gerrit.yaml— a Gerrit-wrapped caller for projects where Gerrit is the source of truth (SCM), integrating withgerrit_to_platformvoting.
examples/
build-test/ { github.yaml, gerrit.yaml }
build-test-release/ { github.yaml, gerrit.yaml }
build-test-multiarch/ { github.yaml, gerrit.yaml }
build-test-release-multiarch/ { github.yaml, gerrit.yaml }
A minimal caller looks like this:
jobs:
build-test:
permissions:
contents: read
pull-requests: read
uses: lfreleng-actions/python-workflows/.github/workflows/build-test.yaml@<release-sha>All inputs are optional and default to the canonical behaviour; see the
inputs: block at the top of each workflow file for the full, documented
list (project layout, matrix override, tox, pytest, audit allow-lists, SBOM
options, Grype severity gate, runner hardening, and Gerrit-aware checkout).
The reusable workflows are Gerrit-aware: when a caller sets the
gerrit_refspec input they check out the unmerged change with
checkout-gerrit-change-action instead of actions/checkout. Vote casting
lives in the gerrit.yaml caller examples (clear vote → run → report vote),
gated by a GERRIT_DISABLED toggle on the build-test workflows so they can
also run manually from the GitHub UI. See the gerrit.yaml examples for the
full pattern.
.github/workflows/testing.yaml exercises
the reusable workflows against three large downstream consumers
(dependamerge, lftools-uv and python-nss-ng) in parallel, validating
the workflows end-to-end on every pull request.
See docs/BRIEF.md for the full design rationale and the
decisions behind the input surface, Gerrit integration, harden-runner
strategy and multi-architecture structure.