Build the Linux release binary statically against musl - #465
Merged
tinder-maxwellelliott merged 2 commits intoAug 17, 2026
Conversation
The published bazel-diff-rust-linux-amd64 was linked against the release
runner's glibc, which made the runner's glibc version a floor on the
systems that binary could run on -- and left it unable to run on musl
distributions such as Alpine at all. Build it for a musl platform instead,
so it is statically linked and runs anywhere.
Bazel has no vocabulary for which libc a platform uses, and neither does
rules_rust: abi_to_constraints maps x86_64-unknown-linux-gnu and
x86_64-unknown-linux-musl to the same {os:linux, cpu:x86_64}. //platforms
supplies that missing constraint, gates the stock Rust toolchains to
system-libc platforms and the new musl ones to //platforms:musl, so
exactly one toolchain matches any given build rather than resolution
silently taking whichever was registered first. The C toolchain
(@toolchains_musl, a dev dependency) is scoped the same way, so ordinary
builds keep using the host cc toolchain.
rustc itself stays the ordinary glibc build -- only the target changes --
which is what keeps proc-macro and build-script actions, compiled for the
exec platform, loadable. The build is a cross-compile from either a glibc
Linux host or an Apple Silicon Mac, so a maintainer can reproduce the
published binary locally with `make release_rust_binary_linux`.
Dropping --config=release-musl would not break the build; it would quietly
produce a glibc binary again. assert_static_binary.sh is therefore run on
Linux in both the per-PR release-artifacts job and the release job: it
checks that the asset is static, requests no dynamic loader, has no shared
library dependencies, references no versioned glibc symbols, and runs.
The asset name is derived from the target platform, so it is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tinder-maxwellelliott
marked this pull request as ready for review
August 17, 2026 16:44
//tools:perf_gate_test is flaky on CI: it failed the Bazel 8.x leg of this PR and the Bazel 9.x leg on master a day earlier, both in test_passes_when_rust_is_consistently_faster. The gate passes only when the faster implementation wins *every* round, and the tests asserting that path decided the winner with a 0.2s `sleep` in the stub binary. On a runner already busy with the rest of the build, process start-up noise dwarfs 0.2s -- a failing run measured 2.041s for a stub that only execs bash and exits -- so a single round flips and the gate reports FAIL. Reproduced locally at --runs_per_test=40, where ~20 runs failed. These tests are about the gate's decision, not about measurement, so supply the elapsed time instead of sleeping for it: fixed_timings() keys a fixed number on the binary that ran, while still executing the stub, so parity checking, output files and exit codes stay real. TimeCommandTest continues to cover the measurement itself. The two failure-direction tests move over too, so all four gate-decision tests read the same way. 60/60 runs pass under the load that previously failed ~20 of 40. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
What
The published
bazel-diff-rust-linux-amd64is now built for a musl platform, so it is statically linked. macOS and Windows assets are untouched, and the asset name is unchanged (it is derived from the target platform).Why
The Linux asset was linked against the release runner's glibc, which made that glibc version a floor on the systems it could run on, and left it unable to run on musl distributions such as Alpine at all.
How
Bazel has no vocabulary for which libc a platform uses, and neither does rules_rust:
abi_to_constraintsmapsx86_64-unknown-linux-gnuandx86_64-unknown-linux-muslto the same{os:linux, cpu:x86_64}. So the two toolchains would both match a musl build and resolution would silently take whichever was registered first.platforms/BUILD(new) — alibcconstraint setting with valuessystem_libc(the default, so@platforms//hostand every stock platform satisfy it without declaring anything) andmusl, theconfig_settings that gate toolchains, and thelinux_x86_64_muslplatform.MODULE.bazel— musl Rust std viarust.repository_set, hosted from Linux and from Apple Silicon; stock toolchains gated to//platforms:is_system_libc;toolchains_musl(dev dependency) scoped to//platforms:muslfor the C toolchain that links the binary and builds the C inring. Exactly one toolchain matches any given build. rustc itself stays the ordinary glibc build — only the target changes — which is what keeps proc-macro and build-script actions, compiled for the exec platform, loadable..bazelrc—--config=release-muslis--config=releaseplus the musl platform. It cannot fold into--config=release:.bazelrcexpands platform-specific config for the barebuildconfig name only.ci.yaml/release.yaml— arelease_configmatrix key (musl on Linux, plain elsewhere), plus a newassert_static_binary.shgate run on Linux in both the per-PRrelease-artifactsjob and the release job. Dropping the flag would not break the build, it would quietly produce a glibc binary again — hence an assertion rather than an assumption. It checks the asset is static, requests no dynamic loader (PT_INTERP), has noNEEDEDentries, references no versionedGLIBC_symbols, and runs.Makefile/README.md/tools/readme_template.md—make release_rust_binary_linux, and docs noting the Linux asset has no glibc requirement.Verification
Built locally, cross-compiled from an Apple Silicon Mac:
No
INTERP, noNEEDED, noGLIBC_strings. The generated@rust_toolchainshub shows the intended mutual exclusion, and each set's incidental exec-triple toolchain is unsatisfiable. Normal host build,bazel build //..., and the full test suite are unchanged; buildifier reports no formatting changes.Notes for reviewers
exec_triple = x86_64-unknown-linux-gnu) was not exercised locally — same declarations as the macOS-hosted one, and the required rust artifacts were confirmed to exist upstream. Therelease-artifactsjob exercises it on this PR.toolchains_muslpullsrules_ccfrom 0.2.17 to 0.2.18 inMODULE.bazel.lock. It is a dev dependency, so consumers of the module never resolve it.tools/readme_template.mdrather than regenerated://tools:generate-readmehit a GitHub API 504 mid-fetch and degraded the contributors table.//cli:E2ETestfails on this machine both with and without the change (nested Bazel cannot resolvelocal_jdklocally); it is unrelated.🤖 Generated with Claude Code