Skip to content

ci: build and size-gate every release target, and pin actions to commits - #349

Merged
Max17190 merged 6 commits into
mainfrom
gate-releases-on-every-target
Oct 6, 2026
Merged

Max17190 merged 6 commits into
mainfrom
gate-releases-on-every-target

Conversation

@Max17190

@Max17190 Max17190 commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

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

  • The release-build job is now a parallel matrix over all six targets in [workspace.metadata.dist], each built on the runner dist plan assigns it (macos-15, macos-15-intel, ubuntu-22.04, ubuntu-22.04-arm), with musl-tools installed for the musl targets. fail-fast is off so one failure reports every broken target, and only main saves these caches.
  • scripts/check-binary-size.sh replaces the inline warning with a hard per-target gate on the release-profile binary, which the dist profile 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.
  • Every action in ci.yml is 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.rs keeps all of this true as the repository changes (see Test Plan).
  • release.yml is untouched and no published artifact changes. A note in Cargo.toml says a cargo-dist bump must re-sync the matrix runners with dist 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 from dist plan), when a custom runner in the manifest sets a host or container (which CI does not mirror; one written as a table that sets only runner is read as that runner, as cargo-dist reads it), when cargo-dist-version changes without those defaults being re-read, when the release-build job has no gate step, when any step or job sets continue-on-error, when the release-build job or any of its steps but the musl-tools install carries an if: (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's toml crate, 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 any uses: 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:

    Target Size (bytes) Budget (bytes) Used
    aarch64-apple-darwin 7,602,176 8,264,819 91.9%
    x86_64-apple-darwin 7,707,736 8,366,908 92.1%
    aarch64-unknown-linux-gnu 6,826,560 7,423,407 91.9%
    x86_64-unknown-linux-gnu 7,675,496 8,327,563 92.1%
    aarch64-unknown-linux-musl 6,613,032 7,184,020 92.0%
    x86_64-unknown-linux-musl 7,785,072 8,464,244 91.9%

RetriggerConfidence Score: 5/5

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 ..."

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.
Comment thread crates/tui/tests/release.rs Outdated
Comment thread crates/tui/tests/release.rs Outdated
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.
Comment thread crates/tui/tests/release.rs Outdated
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.
Comment thread crates/tui/tests/release.rs Outdated
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.
@Max17190
Max17190 merged commit 2ba8947 into main Oct 6, 2026
16 checks passed
@Max17190
Max17190 deleted the gate-releases-on-every-target branch October 10, 2026 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant