Parent
Part of #103 — Python bindings via PyO3 / maturin.
Depends on the crate-skeleton sub-issue and ideally the tooling
sub-issue (so wheels are built from lint-clean source).
Status
Landed in 942f231 on 2026-05-23. See the in-thread comment
("Implementation note: manylinux wheel CI landed") for the
deviation rationale (native ARM, abi3 default, attestations) and
the action-SHA bibliography. The workflow file is
.github/workflows/python-wheels.yml.
One-time PyPI setup (Trusted Publisher + pypi GitHub
Environment) is documented in RELEASING.md →
"Python wheels (PyPI) → One-time PyPI setup". It must be
completed before the first v* tag triggers a publish.
Goal
Build and publish manylinux wheels for big-code-analysis from CI so
target environments (no gcc, no cargo) can pip install.
Scope
Wheel matrix (v1)
- Linux x86_64 — manylinux_2_28 (or whatever
maturin-action's
current default is; pin it explicitly).
- Linux aarch64 — same manylinux profile, cross-compiled.
- Python: 3.12, 3.13. Add 3.14 once it ships if the release lines up.
- ABI: build as abi3 (limited-API) where PyO3 supports it for our
feature set; falls back to per-version wheels otherwise. Document
the decision in the workflow file.
macOS and Windows are explicitly out of scope (#103 lists them under
"Out of scope (track separately)"). Open follow-up issues only when
there is concrete demand.
Build workflow
.github/workflows/python-wheels.yml:
- Triggers:
push to main for release branches / tags (v*) only —
no wheel build on every commit.
- Manual
workflow_dispatch for ad-hoc test builds.
pull_request with a path filter on big-code-analysis-py/**
AND a python-wheels label — keeps PR-time cost opt-in.
- Jobs:
linux-x86_64 — PyO3/maturin-action@v1 with target = x86_64-unknown-linux-gnu, manylinux = 2_28,
args = "--release --strip --out dist".
linux-aarch64 — same with target = aarch64-unknown-linux-gnu. Implemented with the native
ubuntu-24.04-arm runner instead of QEMU — see the in-
thread comment for the rationale (GA for private repos
Jan 2026, ~10× faster than QEMU).
sdist — maturin sdist so PyPI has a source distribution as
well (even though we mark wheels as required, the sdist is
useful for niche architectures and reproducibility).
smoke-test — pulls the built wheel for the runner arch and
runs a minimal python -c "import big_code_analysis; big_code_analysis.analyze(...)" to catch dynamic-linker /
missing-symbol failures before publish. Runs on both Python
3.12 and 3.13 so the abi3 forward-compat claim is verified.
publish — gated on a tag matching v* and the pypi
environment with PyPI trusted-publisher OIDC. No PyPI API
tokens — uses pypa/gh-action-pypi-publish@v1.14.0.
Reproducibility / security
- Pin every action by SHA, not by tag (the repo already does this
for the Rust workflows — match the style).
- Cache cargo and maturin builds keyed off
Cargo.lock +
pyproject.toml.
- Sign artefacts with PEP 740 Sigstore attestations, generated
automatically by gh-action-pypi-publish@v1.14.0 (replaces the
separate sigstore/gh-action-sigstore-python step in the
original spec — see in-thread comment).
cargo deny runs in ci.yml already; pip-audit runs in the
smoke-test job as the wheel-side equivalent.
Release coordination
- ✅ Updated
RELEASING.md with the Python release procedure:
- When to bump
big-code-analysis-py/pyproject.toml version
(it inherits from [workspace.package]).
- The fact that wheels are built only on tags.
- How to test a release candidate via
workflow_dispatch.
- PyPI Trusted Publisher one-time setup.
- ✅
packaging/README.md is binary-only; the Python wheel
pipeline is cross-referenced from RELEASING.md directly,
not duplicated under packaging/.
Documentation
Acceptance criteria
- ✅ Manually triggering
python-wheels.yml on a feature branch
produces uploadable wheels for both Linux x86_64 and aarch64,
Python 3.12 + 3.13. (Single abi3 wheel per arch covers both
Python versions; smoke-test verifies both 3.12 and 3.13 load
the same wheel.)
- ✅ The smoke-test job successfully imports and analyses a
fixture on the built wheel.
- ⏭️ Tagging
vX.Y.Z on main publishes those wheels to PyPI
via trusted-publisher OIDC. (Cannot verify until the first
tag — the one-time PyPI setup in RELEASING.md must be
completed first. The publish job is gated on
github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
so PR / workflow_dispatch runs validate the matrix without
uploading.)
- ✅ No PyPI tokens stored as repo secrets.
Parent
Part of #103 — Python bindings via PyO3 / maturin.
Depends on the crate-skeleton sub-issue and ideally the tooling
sub-issue (so wheels are built from lint-clean source).
Status
Landed in
942f231on 2026-05-23. See the in-thread comment("Implementation note: manylinux wheel CI landed") for the
deviation rationale (native ARM, abi3 default, attestations) and
the action-SHA bibliography. The workflow file is
.github/workflows/python-wheels.yml.One-time PyPI setup (Trusted Publisher +
pypiGitHubEnvironment) is documented in
RELEASING.md→"Python wheels (PyPI) → One-time PyPI setup". It must be
completed before the first
v*tag triggers a publish.Goal
Build and publish manylinux wheels for
big-code-analysisfrom CI sotarget environments (no
gcc, nocargo) canpip install.Scope
Wheel matrix (v1)
maturin-action'scurrent default is; pin it explicitly).
feature set; falls back to per-version wheels otherwise. Document
the decision in the workflow file.
macOS and Windows are explicitly out of scope (#103 lists them under
"Out of scope (track separately)"). Open follow-up issues only when
there is concrete demand.
Build workflow
.github/workflows/python-wheels.yml:pushtomainfor release branches / tags (v*) only —no wheel build on every commit.
workflow_dispatchfor ad-hoc test builds.pull_requestwith a path filter onbig-code-analysis-py/**AND a
python-wheelslabel — keeps PR-time cost opt-in.linux-x86_64—PyO3/maturin-action@v1withtarget = x86_64-unknown-linux-gnu,manylinux = 2_28,args = "--release --strip --out dist".linux-aarch64— same withtarget = aarch64-unknown-linux-gnu. Implemented with the nativeubuntu-24.04-armrunner instead of QEMU — see the in-thread comment for the rationale (GA for private repos
Jan 2026, ~10× faster than QEMU).
sdist—maturin sdistso PyPI has a source distribution aswell (even though we mark wheels as required, the sdist is
useful for niche architectures and reproducibility).
smoke-test— pulls the built wheel for the runner arch andruns a minimal
python -c "import big_code_analysis; big_code_analysis.analyze(...)"to catch dynamic-linker /missing-symbol failures before publish. Runs on both Python
3.12 and 3.13 so the abi3 forward-compat claim is verified.
publish— gated on a tag matchingv*and thepypienvironment with PyPI trusted-publisher OIDC. No PyPI API
tokens — uses
pypa/gh-action-pypi-publish@v1.14.0.Reproducibility / security
for the Rust workflows — match the style).
Cargo.lock+pyproject.toml.automatically by
gh-action-pypi-publish@v1.14.0(replaces theseparate
sigstore/gh-action-sigstore-pythonstep in theoriginal spec — see in-thread comment).
cargo denyruns inci.ymlalready;pip-auditruns in thesmoke-test job as the wheel-side equivalent.
Release coordination
RELEASING.mdwith the Python release procedure:big-code-analysis-py/pyproject.tomlversion(it inherits from
[workspace.package]).workflow_dispatch.packaging/README.mdis binary-only; the Python wheelpipeline is cross-referenced from
RELEASING.mddirectly,not duplicated under
packaging/.Documentation
CHANGELOG.md:### Addedentry for the workflow and### Changedentry for thepyo3/abi3-py312feature flip.tracked separately under docs(book): Python Bindings chapter (8 pages) with CI-tested examples (phase 8/9) #272 (phase 8/9).
Acceptance criteria
python-wheels.ymlon a feature branchproduces uploadable wheels for both Linux x86_64 and aarch64,
Python 3.12 + 3.13. (Single abi3 wheel per arch covers both
Python versions; smoke-test verifies both 3.12 and 3.13 load
the same wheel.)
fixture on the built wheel.
vX.Y.Zonmainpublishes those wheels to PyPIvia trusted-publisher OIDC. (Cannot verify until the first
tag — the one-time PyPI setup in
RELEASING.mdmust becompleted first. The publish job is gated on
github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')so PR / workflow_dispatch runs validate the matrix without
uploading.)