Repository navigation
ci: build and size-gate every release target, and pin actions to commits - #349
Merged
Merged
Conversation
A release tag builds six targets and publishes nothing unless every build passes, but CI built only two of them (x86_64 Linux glibc and arm64 macOS), so a target that stopped building surfaced on the release and blocked every install path. The size check only warned, at 8 MiB on one target the binary already filled to about 90%, and every action was referenced by a tag that can be moved to other code. The release-build job now builds each target in [workspace.metadata.dist] with the release profile, on the runner `dist plan` assigns it and with musl-tools for the musl targets, and scripts/check-binary-size.sh fails the job when a binary is over its target's budget: the v2026.10.0 binary's size plus 10%. Every action in ci.yml is pinned to a full commit SHA with its release in a trailing comment; checkout moves to v6.1.0, the major the release workflow already uses. crates/tui/tests/release.rs keeps the matrix equal to the published targets and the manifest's custom runner, the dist profile equal to release, and every action pinned, and checks that the gate passes each target's budget exactly and fails one byte over it.
The test that keeps the release-build size gate hard pinned the gate's run line, which catches a `|| true`, but `continue-on-error: true` on the gate's step or job let an over-budget binary pass the job while every assertion still held, turning the gate back into a warning. The test now fails when any non-comment line of ci.yml sets continue-on-error. The header note on the rust-toolchain pin is also corrected: that action is versioned by branch, and its one release tag, v1, follows master.
The release guard caught a gate that only warns, but not one that never runs: an `if:` on the release-build job, its build step or its gate step skipped the build or the size check on pull requests, and every assertion still passed. A gate moved out of release-build also passed, because the gate line was searched for anywhere in ci.yml. The guard now reads the release-build job on its own, requires the gate step inside it, and fails on any `if:` there except the musl-tools install. The size-gate script's header no longer says every size is a v2026.10.0 release binary, which stops being true after the first raise.
The release test compared CI's runner with the release's only for targets in github-custom-runners, one of the six. The other five build on runners cargo-dist assigns by default, which no file in the repository records, so a matrix edit that moved one of them, or a cargo-dist bump that moved the release, left CI building that target on another OS image or C library than the release while the test still passed. The test now records the runner cargo-dist 0.32.0 assigns each target by default, as `dist plan --output-format=json` reports it, and checks every matrix entry against its custom runner or that default. It fails when cargo-dist-version changes until the defaults are re-read.
The release test matched Cargo.toml as text: a custom runner had to be written `target = "runner"` with single spaces, and a target had to be a double-quoted string. cargo-dist reads any valid TOML, so writing `aarch64-apple-darwin="macos-15"` moved the release to macos-15 while the test skipped the line and compared CI with the recorded default, passing a CI build on macos-14. A single-quoted target was dropped from the published set and the size-gate loop alike, so CI could stop building it unnoticed. The test now parses Cargo.toml with the toml crate the workspace already uses, for the targets, cargo-dist-version, the custom runners and [profile.dist]. A custom runner given as a table, which can also set a host or container that ci.yml does not mirror, fails the test instead of passing on its runner name.
cargo-dist reads `target = { runner = "macos-15" }` exactly as it reads
`target = "macos-15"`: same runner, same inferred host, no container. The
release test failed any custom runner given as a table, so a maintainer who
wrote that valid form got a release-test failure even though CI builds the
target on the release's runner.
The test now reads the runner from either form and still fails an entry that
sets `host` or `container`, which can change how the release builds and which
ci.yml does not mirror, or one that names no runner.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A release tag builds six targets and publishes nothing unless every one of them builds, but CI built a release binary for only one of them (Linux x86_64) and tested on two hosts. A change that breaks Intel macOS, ARM Linux, or either musl target was found only when the next tag ran, and that one failed build blocked the shell installer, Homebrew, and npm for every platform at once. The size check only warned, against an 8 MiB limit the Linux binary already filled to about 90%, so nothing stopped a dependency or profile change from bloating the shipped binary. Every action was referenced by a tag, which can be moved to different code after review while these jobs run with the repository's token.
Summary
[workspace.metadata.dist], each built on the runnerdist planassigns it (macos-15, macos-15-intel, ubuntu-22.04, ubuntu-22.04-arm), withmusl-toolsinstalled for the musl targets.fail-fastis off so one failure reports every broken target, and only main saves these caches.scripts/check-binary-size.shreplaces the inline warning with a hard per-target gate on the release-profile binary, which thedistprofile inherits unchanged. Each budget is that target's v2026.10.0 release binary plus 10% headroom. An over-budget binary fails the job with an error annotation naming the target, its size, and the overage; a target with no budget fails rather than passing unmeasured. The script documents how to raise a budget deliberately.ci.ymlis pinned to a full commit SHA with its version in a trailing comment. rust-toolchain, which is versioned by branch rather than by release, is pinned to a dated master commit and given its toolchain as an input.crates/tui/tests/release.rskeeps all of this true as the repository changes (see Test Plan).release.ymlis untouched and no published artifact changes. A note inCargo.tomlsays a cargo-dist bump must re-sync the matrix runners withdist plan, and the release test fails on a bump until its recorded default runners are re-read.Test Plan
the_size_gate_fails_a_binary_over_its_budget: for each of the six targets, runs the gate on sparse files at 0 bytes and exactly at the budget (both pass) and one byte over it (exits 1 and names the target and budget). A target with no budget fails. This is the dry run showing the gate fails hard on an oversized binary.ci_builds_and_size_gates_every_release_target: fails when the CI matrix differs from the published targets, or builds any of them on a runner other than the release's (the manifest's custom runner, else the default cargo-dist 0.32.0 assigns, recorded fromdist plan), when a custom runner in the manifest sets ahostorcontainer(which CI does not mirror; one written as a table that sets onlyrunneris read as that runner, as cargo-dist reads it), whencargo-dist-versionchanges without those defaults being re-read, when the release-build job has no gate step, when any step or job setscontinue-on-error, when the release-build job or any of its steps but the musl-tools install carries anif:(which would skip the build or the gate on a pull request), or when[profile.dist]stops inheriting release unchanged. It parses Cargo.toml as TOML (with the workspace'stomlcrate, now also a dev-dependency of the TUI crate), so an entry cargo-dist reads is never skipped for its spacing or quoting.ci_pins_every_action_to_a_commit: fails on anyuses:without a 40-character commit SHA and a trailing version comment.cargo test --workspace --locked: pass.cargo +1.97.0 clippy --workspace --all-targets --locked -- -D warnings: pass.All six Release build jobs pass on this pull request. Sizes reported by the gate in each job's log:
No outstanding findings block merging.
Summary
The PR builds and size-gates all six release targets in CI, pins CI actions to commits, and checks that the CI matrix matches the release configuration. The latest change accepts custom runner tables containing only a runner name. No new issues remain.
Reviews (5) · Last reviewed commit: "ci: accept a custom runner written as a ..."