fix(ci): repair broken rustup shim chain on macos-latest#14040
Conversation
The macos-latest runner image ships Homebrew's rustup-init and symlinks
~/.cargo/bin/{cargo,rustc,rustup,...} to it. rustup-init is the
installer binary, not the multi-call rustup proxy, so `cargo test` ends
up invoking the installer's argument parser and fails with "unexpected
argument 'test'". The previous `command -v rustup` probe saw the
symlink and skipped reinstallation, leaving the broken chain in place.
Probe with `rustup show` to detect a genuinely working rustup. When it
fails, wipe ~/.cargo/bin and reinstall via the official script.
~/.rustup/toolchains is untouched so the rustup-cache restore still
hits.
There was a problem hiding this comment.
Pull request overview
Repairs CI on the macos-latest runner image, which now ships Homebrew's rustup-init and exposes it via ~/.cargo/bin/{cargo,rustc,rustup,...} symlinks. Because rustup-init is the installer (not the multi-call rustup proxy), cargo test invocations were failing with unexpected argument 'test'. The probe is changed from command -v rustup to rustup show, which only succeeds against a real rustup; on failure the broken shim directory is wiped and the official rustup installer is run with --no-modify-path (preserving the cached ~/.rustup/toolchains).
Changes:
- Replace
command -v rustuppresence check withrustup showfunctional check. - On failure,
rm -rfthe cargobindirectory to remove broken Homebrew shims before reinstalling. - Pass
--no-modify-pathto the rustup install script and explicitly append the bin path to$GITHUB_PATH.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
📦 Binary Size-limit
🙈 Size remains the same at 61.95MB |
Rsdoctor Bundle Diff AnalysisFound 6 projects in monorepo, 6 projects with changes. 📊 Quick Summary
📋 Detailed Reports (Click to expand)📁 popular-libsPath:
📁 react-10kPath:
📁 react-1kPath:
📁 react-5kPath:
📁 romePath:
📁 ui-componentsPath:
Generated by Rsdoctor GitHub Action |
Merging this PR will not alter performance
Comparing Footnotes
|
The previous probe `rustup show` failed too eagerly on Linux runners that had a usable rustup but no default toolchain, which then wiped ~/.cargo/bin and reinstalled rustup. The cache restore later put the nightly toolchain back into ~/.rustup/toolchains/, but the fresh rustup metadata didn't know rust-src was already there. Any later toolchain resolve (e.g. `rustc -vV` inside Swatinem/rust-cache) tried to install rust-src per rust-toolchain.toml and crashed on the file conflict. Narrow the detector: only wipe when ~/.cargo/bin/rustup actually resolves to a `rustup-init` binary (the macos-latest Homebrew case). Healthy installs on Linux/macOS are left alone.
The previous detector spent a multi-line case statement on what is essentially a single comparison: does `rustup` resolve to `rustup-init`? `readlink -f` already follows the whole symlink chain and returns an empty string when the target is missing, so a single `[[ ... == ... ]]` short-circuit handles all three cases (missing, healthy, broken).
BSD `readlink` on macOS does not reliably support `-f`. With the error swallowed by `2>/dev/null`, the substitution returned an empty string, the comparison never matched, and the broken symlink chain was left in place. `realpath` is available natively on both macOS (BSD) and Linux and resolves symlink chains, so it gives a portable canonical path to test against.
Two rounds of trying to sniff out the broken state at runtime (readlink -f, then realpath) silently no-op'd on the macos-latest runner — the symlink check returned something that didn't match `*/rustup-init`, so the wipe never ran and `cargo test` kept hitting the installer's arg parser. macos-latest's pre-installed rustup is always the broken Homebrew symlink chain; there's no useful state to preserve in ~/.cargo/bin there. Stop guessing — wipe unconditionally on macOS in a separate step and let the existing installer lay down real rustup proxies. ~/.rustup/toolchains is untouched, so the rustup-cache restore still hits.
macos-latest ships TWO rustup-related binaries from Homebrew: - /opt/homebrew/bin/rustup-init (the installer) - /opt/homebrew/bin/rustup (a working rustup proxy for rustup-only subcommands) After the wipe step removes the broken ~/.cargo/bin/ shim chain, the existing `command -v rustup` probe still found Homebrew's real rustup on PATH and skipped the installer. `rustup toolchain install` worked via Homebrew's rustup, but cargo/rustc proxies were never recreated in ~/.cargo/bin, so `cargo test` ended with "cargo: command not found". Switch the probe to `command -v cargo`. Homebrew doesn't ship a standalone cargo, so the probe accurately reflects whether the proxy shims exist. On Linux this is equivalent — rustup always installs cargo alongside.
I've shipped four iterations of the rustup shim fix without nailing down the actual PATH / shim state on macos-latest. Print the path resolution and inventory of ~/.cargo/bin, /opt/homebrew, ~/.rustup right before `cargo test`, and open an SSH tmate session on failure so we can inspect the runner live instead of guessing from logs. Both the diagnostics step and the tmate session are gated on the macOS matrix entry — Linux/Windows runs are unaffected. Revert this once the macOS shim issue is resolved.
The previous wipe step at the top of the composite action was getting undone: Swatinem/rust-cache (the Cargo cache step) defaults to cache-bin: true and captures ~/.cargo/bin. On restore it puts back the runner image's original Apr-21 Homebrew rustup-init symlink chain, overwriting the fresh proxies we'd just installed. Move the wipe + curl install to AFTER the Cargo cache step. The cargo cache restore happens first (potentially with broken symlinks), then we lay down real proxies. Post-action cache save then captures the fixed state going forward. Diagnostic from tmate session: ls -la ~/.cargo/bin showed Apr 21 12:12 timestamps on cargo/rustc/rustup → confirmed symlinks were never deleted because Swatinem put them back.
Detect the homebrew symlink chain with plain POSIX readlink (no -f, which BSD readlink on macOS doesn't reliably handle) and run rustup-init -y to lay down a real rustup with cargo/rustc proxies. Runs after the Cargo cache step so the Swatinem restore can't undo the repair.
Summary
macos-latestrunner image ships Homebrew'srustup-initand populates~/.cargo/bin/{cargo,rustc,rustup,...}as symlinks pointing back to it.rustup-initis the installer binary, not the multi-call rustup proxy.command -v rustupprobe sees the symlink and skips reinstall.rustup toolchain installstill works via therustupargv proxy, butcargo testinvokes the installer's arg parser and dies withunexpected argument 'test'— surfaced on https://github.com/web-infra-dev/rspack/actions/runs/25848161725/job/75948085190.rustup show, which only succeeds against a real rustup. On failure, wipe~/.cargo/bin(only the broken symlinks —~/.rustup/toolchainsis untouched, so the rustup-cache restore still hits) and install a real rustup via the official script.Test plan
Run Rust Tests / Rust watcher test - "macos-latest"(the job that originally failed) and confirmcargo test -p rspack_watcherproceeds past arg-parsing.runner.os != 'Windows'branch covers Linux too) are unaffected —rustup showsucceeds on a healthy install so the new branch is a no-op there.