Repository navigation
v0.32.1
A CI/CD-only release. No library, binding or C API change: every entry below is about how hal is built and published, and this release exists to exercise that chain end to end for the first time.
Changed
-
The release is now three workflows with one action each, and a tag deploys without building.
release.ymltriggers on a push torelease/X.Y.Zand builds every wheel, C-API archive and the SBOM;tag-release.ymlcreates the annotatedvX.Y.Ztag on merge, and refuses to create one unless that build is green for the commit being merged; a newpublish.ymltriggers on the tag, resolves the run that built the artifacts, verifies its tree matches the tag's, and publishes to PyPI, crates.io and GitHub Releases without compiling anything.release.ymlpreviously built and published on the tag and had nopull_requesttrigger, so its build ran at the one point in the process nothing could test it. Five of the nine release tags to date failed, and every one of those was a build failure at deploy time rather than a publishing failure: v0.32.0 failed three times in a row on a runner image withoutgit-lfs, then the same image without a C compiler, then arust-toolchain.tomlcomponent conflict, andBuild wheels (aarch64-linux)had already failed the same way in v0.29.1 and v0.29.2. Each retry required deleting and re-pushing a tag the release ruleset exists to make immutable. Those failures now appear on the release PR.Artifacts are bound to the tag by tree SHA rather than commit SHA, since a squash merge changes the commit and not the tree, and are held for 30 days because the release-PR-to-tag gap is human-paced. A missing, expired or mismatched artifact fails the publish; there is no fallback to a build.
-
Build provenance is attested where the artifacts are built rather than where they are uploaded. Attesting a downloaded file describes the download; attesting it at build time describes the build that produced what consumers receive.
-
cargo publishremains on the tag path and is the single exception to "the tag builds nothing", because it has no pre-built input.--no-verifykeeps the verification compile off that path, and the release branch has already packaged the same tree. -
Quick lints the aarch64 target again.
skip-cross-clippywas switched on because the shared per-target pass took a fixed argument list and hal's six PyO3 crates cannot cross-compile without an interpreter for the target — so the escape hatch cost hal aarch64 clippy coverage entirely, on a repository whose products are aarch64. The pass now takes the same exclusions the host pass uses, and runs check-only without theopencvfeature, since opencv-sys probes for a target-native OpenCV that is not installed. -
Quick also runs
actionlintand the organisation's SHA-pin policy over.github/, which arrives with the shared workflows rather than being configured here. The 21 findings it reported are fixed rather than suppressed: unquoted$GITHUB_STEP_SUMMARY/$GITHUB_ENV/$GITHUB_OUTPUTredirects,export X=$(...)masking the command's exit status,for x in $(find ...)splitting on whitespace in a path, andsha256sum *reading a leading-dash filename as an option. The maturin argument lists became arrays, so the expansion is quoted and the--sdistfiltering is an explicit comparison rather than a substring removal, and the matrix value reaches the script throughenvrather than being interpolated into it.read -rastill splits on whitespace without interpreting quotes, which is what the unquoted expansion did too — every value in that matrix is a bare flag, and the step records what to do if one ever is not. Onexargs -0 sh -c '...'keeps its single quotes with a scopedshellcheck disableand the reason, because$1there belongs to the inner shell. -
release.ymlkeeps the packaged.cratefiles as artifacts.publish.ymlrunscargo publish --no-verify, and skipping the verification compile on the tag path is only defensible because packaging was exercised on a branch a human reviewed — these are the record of exactly what that packaging produced.cargo publishpackages from source and cannot consume them, so they are provenance rather than an input. -
The wheel build uses the shared
maturin-buildaction for installing maturin, running the builds and checking each wheel's version, instead of hal's own copy of each. The matrix, the ANGLE fetch, thelldinstall, the per-package staging and the two post-build assertions stay here, because they are hal's: an action composes into the job hal already has, where a reusable workflow would have needed a hook for each of them.--sdistmoved from the lane arguments totracker's entry in the build list, which is where it belongs — it is per package and per lane, and expressing only half of that in the lane arguments is what made the old loop strip it by substring.Provenance is still generated by hal rather than by the action (
attest: false), because the action attests as soon as it has built and these wheels are not shippable until the layout and Mach-O checks have passed. Provenance for a wheel an assertion is about to reject is provenance for something nobody can install.maturin is no longer named per lane. The action derives it from the runner OS, which is what it is a property of —
maturin[patchelf,zig]on the two Linux lanes and plainmaturinon macOS and Windows, exactly what the matrix used to spell out. -
Release notes were never extracted. The awk range
/## \[VERSION\]/,/^## \[/ends on the first line matching its second pattern — and the heading it starts on matches that too, so the range was always a single record, which the body then skipped withnext. Every release since that step was written shipped empty notes: v0.32.0's GitHub Release body is zero characters. The|| echo "Release $VERSION"fallback never fired, because awk exits 0 when it prints nothing. Replaced with.github/scripts/extract_release_notes.py, which requires a complete version token — a prefix match would take the## [1.2.30]section for release 1.2.3 — and exits non-zero when the section is absent, so the failure is loud rather than an empty release body.Found by the
publish.ymlrehearsal on this release branch, before any tag existed. It is the first defect the rehearsal has caught, and it had been shipping unnoticed for at least one release.
Operational
- PyPI and crates.io Trusted Publishers must be re-pointed from
release.ymltopublish.ymlbefore the next tag. Both match a publisher on the workflow filename, so all five PyPI distributions and all ten crates will fail to upload until their publisher configuration names the new file. Apublish.ymlrehearsal does not catch this, because a rehearsal skips the upload.