Skip to content

Commit d021298

Browse files
committed
Make rust-toolchain.toml the single source of truth for rustc commit
Replace the hardcoded commit hash in CI and manual checkout steps with automated reads from rust-toolchain.toml's [metadata] section. Both local test scripts and CI now use yq to parse the commit, and the scripts handle bare repos (via worktrees) and regular clones. Also converts lessons-learned.md into ADR-001 (compat layer decision).
1 parent 70e1c4f commit d021298

File tree

7 files changed

+152
-66
lines changed

7 files changed

+152
-66
lines changed

.github/workflows/test.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,25 @@ jobs:
9999
ref: ${{ github.event.pull_request.head.sha }}
100100
submodules: recursive
101101

102+
- name: 'Install yq'
103+
uses: mikefarah/yq-action@v4
104+
105+
- name: 'Read rustc commit from rust-toolchain.toml'
106+
id: rustc-meta
107+
run: |
108+
set -euo pipefail
109+
COMMIT=$(yq -r '.metadata.rustc-commit' rust-toolchain.toml)
110+
if [ -z "$COMMIT" ] || [ "$COMMIT" = "null" ]; then
111+
echo "::error::metadata.rustc-commit not found in rust-toolchain.toml"
112+
exit 1
113+
fi
114+
echo "rustc-commit=$COMMIT" >> "$GITHUB_OUTPUT"
115+
102116
- name: 'Check out Rust repo'
103117
uses: actions/checkout@v4
104118
with:
105119
repository: rust-lang/rust
106-
ref: a2545fd6fc66b4323f555223a860c451885d1d2b # hash of Hardcoded Rust version
120+
ref: ${{ steps.rustc-meta.outputs.rustc-commit }}
107121
path: rust
108122
fetch-depth: 1
109123

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# ADR-001: Compatibility layer for rustc internal APIs
2+
3+
**Status:** Accepted
4+
**Date:** 2025-02-21
5+
6+
## Context
7+
8+
stable-mir-json hooks into rustc's internal APIs (`rustc_middle`, `rustc_smir`, `rustc_span`, etc.) to extract MIR data. These APIs are unstable; they change regularly across nightly releases, and the crate names themselves get renamed (the `stable_mir` crate became `rustc_public`, `rustc_smir` became `rustc_public_bridge`, etc.). Before this decision, rustc internals were used directly throughout the codebase: `printer.rs`, `mk_graph/`, `driver.rs`, and various helpers all had their own `extern crate` declarations and direct imports. So a toolchain bump meant hunting through every file that touched a changed API; not fun, and easy to miss things.
9+
10+
## Decision
11+
12+
Route all rustc internal API usage through a single `src/compat/` module. The module re-exports crate names (so a rename like `stable_mir` to `rustc_public` is a one-line alias change in `compat/mod.rs`) and wraps unstable functions behind stable signatures (so a changed calling convention is absorbed in one place).
13+
14+
The compat layer does *not* try to abstract over stable MIR's own public API. When `stable_mir` (the public, downstream-facing API) changes its types, any consumer has to adapt; that's by design. The boundary is: if it's a rustc implementation detail, it goes through compat; if it's the stable MIR contract, it flows through directly.
15+
16+
`src/driver.rs` is the one exception; it uses `rustc_driver` and `rustc_interface` directly because it *is* the rustc integration point. Everything else goes through compat.
17+
18+
## Consequences
19+
20+
**What the compat layer absorbs (rustc internals):**
21+
22+
| Change | Absorbed in |
23+
|--------|-------------|
24+
| `collect_and_partition_mono_items` API changes | `compat/mono_collect.rs` |
25+
| `RunCompiler::new().run()` becoming `run_compiler()` | `driver.rs` |
26+
| `stable_mir` renamed to `rustc_public` | `compat/mod.rs` (re-exported as alias) |
27+
| `rustc_smir` renamed to `rustc_public_bridge` | `compat/mod.rs`, `driver.rs` |
28+
| `IndexedVal` trait moving between crates | `compat/mod.rs` (re-exported) |
29+
| `FileNameDisplayPreference` variants changing | `compat/spans.rs` |
30+
31+
None of these changes leaked into `printer.rs` or `mk_graph/`. The abstraction worked as designed.
32+
33+
**What still propagates (stable MIR public API evolution):**
34+
35+
- `Rvalue::AddressOf` changed from `Mutability` to `RawPtrKind`
36+
- `StatementKind::Deinit` and `Rvalue::NullaryOp` removed
37+
- `AggregateKind::CoroutineClosure` added
38+
- `Coroutine` and `Dynamic` field count changes
39+
- `Ty::visit()` return type changed from `()` to `ControlFlow<T>`
40+
41+
These affect `printer.rs` and `mk_graph/` regardless of the compat layer. Any consumer of stable MIR would need to handle them; there's nothing we can (or should) do about that.
42+
43+
**The mk_graph gap (now fixed).** Turns out the `mk_graph/` files originally declared their own `extern crate stable_mir`, bypassing the abstraction entirely. This was introduced in commit `e9395d9` (PR #111) before the compat layer existed; it wasn't an oversight so much as a timing issue. The 13-month toolchain bump exposed the cost: when `stable_mir` was renamed to `rustc_public`, all 5 mk_graph files needed updating, while `printer.rs` needed zero import changes because it already went through compat. Commit `307dcb8` closed this gap by routing all mk_graph imports through `use crate::compat::stable_mir`.
44+
45+
## Validation
46+
47+
We stress-tested the abstraction against two toolchain bumps to see if it actually holds up in practice:
48+
49+
- **6-month jump** (nightly-2024-11-29 to nightly-2025-06-01, rustc 1.85 to 1.89): all internal API changes contained in `compat/` and `driver.rs`
50+
- **13-month jump** (nightly-2024-11-29 to nightly-2026-01-15, rustc 1.85 to 1.94): same containment, plus the major `stable_mir` to `rustc_public` crate rename absorbed by a single alias in `compat/mod.rs`
51+
52+
Both branches compile and are available for reference: `spike/toolchain-2025-06` and `spike/toolchain-2026-01`, each with a detailed `rustc-<version>.md` breakdown.

lessons-learned.md

Lines changed: 0 additions & 60 deletions
This file was deleted.

rust-toolchain.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ components = ["llvm-tools", "rustc-dev", "rust-src", "rust-analyzer"]
44

55
# Ignored by rustup; used by our test scripts.
66
# This is the rustc commit that backs the nightly above.
7-
# UI tests (make test-ui, make remake-ui-tests) need a rust compiler
8-
# checkout at this commit: git -C $RUST_DIR_ROOT checkout <commit>
7+
# UI test scripts automatically checkout this commit in RUST_DIR_ROOT.
98
[metadata]
109
rustc-commit = "a2545fd6fc66b4323f555223a860c451885d1d2b"

tests/ui/ensure_rustc_commit.sh

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Ensures a rust checkout (regular or bare+worktree) is at the commit
4+
# specified in rust-toolchain.toml's [metadata] rustc-commit field.
5+
#
6+
# Usage: source this script after setting RUST_DIR to the repo root.
7+
# It sets RUST_SRC_DIR to the directory containing the source files
8+
# (which may differ from RUST_DIR if a worktree is created).
9+
10+
set -u
11+
12+
: "${RUST_DIR:?RUST_DIR must be set before sourcing ensure_rustc_commit.sh}"
13+
14+
# Require yq (mikefarah/yq) for TOML parsing
15+
if ! command -v yq &> /dev/null; then
16+
echo "Error: yq is required but not installed."
17+
echo "Install via: brew install yq | apt install yq | nix shell nixpkgs#yq-go"
18+
echo "See: https://github.com/mikefarah/yq#install"
19+
exit 1
20+
fi
21+
22+
_SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
23+
_REPO_ROOT=$( cd -- "$_SCRIPT_DIR/../.." &> /dev/null && pwd )
24+
25+
# Read the expected rustc commit from rust-toolchain.toml
26+
RUSTC_COMMIT=$(yq -r '.metadata.rustc-commit' "$_REPO_ROOT/rust-toolchain.toml")
27+
if [ -z "$RUSTC_COMMIT" ] || [ "$RUSTC_COMMIT" = "null" ]; then
28+
echo "Error: Could not read metadata.rustc-commit from $_REPO_ROOT/rust-toolchain.toml"
29+
exit 1
30+
fi
31+
32+
SHORT_COMMIT="${RUSTC_COMMIT:0:12}"
33+
34+
# Detect whether RUST_DIR is a bare repo
35+
IS_BARE=$(git -C "$RUST_DIR" rev-parse --is-bare-repository 2>/dev/null)
36+
37+
if [ "$IS_BARE" = "true" ]; then
38+
# Bare repo: use worktrees. Check if one already exists at this commit.
39+
WORKTREE_DIR="$RUST_DIR/$SHORT_COMMIT"
40+
41+
if [ -d "$WORKTREE_DIR" ]; then
42+
WORKTREE_COMMIT=$(git -C "$WORKTREE_DIR" rev-parse HEAD 2>/dev/null)
43+
if [ "${WORKTREE_COMMIT}" = "${RUSTC_COMMIT}" ]; then
44+
echo "Worktree already exists at ${WORKTREE_DIR} (${SHORT_COMMIT})"
45+
RUST_SRC_DIR="$WORKTREE_DIR"
46+
else
47+
echo "Error: Worktree at ${WORKTREE_DIR} is at wrong commit (${WORKTREE_COMMIT})"
48+
exit 1
49+
fi
50+
else
51+
echo "Creating worktree at ${WORKTREE_DIR} for commit ${SHORT_COMMIT}..."
52+
git -C "$RUST_DIR" worktree add "$WORKTREE_DIR" "$RUSTC_COMMIT" --detach --quiet || {
53+
echo "Error: Failed to create worktree for commit ${RUSTC_COMMIT} in ${RUST_DIR}"
54+
exit 1
55+
}
56+
RUST_SRC_DIR="$WORKTREE_DIR"
57+
fi
58+
else
59+
# Regular repo: checkout the commit directly
60+
CURRENT_COMMIT=$(git -C "$RUST_DIR" rev-parse HEAD 2>/dev/null)
61+
if [ "${CURRENT_COMMIT}" != "${RUSTC_COMMIT}" ]; then
62+
echo "Checking out rustc commit ${SHORT_COMMIT} in ${RUST_DIR}..."
63+
git -C "$RUST_DIR" checkout "$RUSTC_COMMIT" --quiet || {
64+
echo "Error: Failed to checkout commit ${RUSTC_COMMIT} in ${RUST_DIR}"
65+
exit 1
66+
}
67+
else
68+
echo "Rust checkout already at expected commit ${SHORT_COMMIT}"
69+
fi
70+
RUST_SRC_DIR="$RUST_DIR"
71+
fi
72+
73+
export RUST_SRC_DIR

tests/ui/remake_ui_tests.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ fi
2020

2121
RUST_DIR="$1"
2222
UI_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
23+
24+
# Ensure the rust checkout is at the expected commit (handles bare repos)
25+
source "$UI_DIR/ensure_rustc_commit.sh"
26+
2327
UI_SOURCES="${UI_DIR}/ui_sources.txt"
2428
FAILING_TSV="${UI_DIR}/failing.tsv"
2529
PASSING_TSV="${UI_DIR}/passing.tsv"
@@ -37,7 +41,7 @@ fi
3741

3842
echo "Running UI tests..."
3943
while read -r test; do
40-
full_path="$RUST_DIR/$test"
44+
full_path="$RUST_SRC_DIR/$test"
4145

4246
if [ ! -f "$full_path" ]; then
4347
echo "Error: Test file '$full_path' not found."

tests/ui/run_ui_tests.sh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ else
1212
VERBOSE="$2"
1313
fi
1414

15-
RUST_DIR_ROOT="$1"
15+
RUST_DIR="$1"
1616
UI_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
17+
18+
# Ensure the rust checkout is at the expected commit (handles bare repos)
19+
source "$UI_DIR/ensure_rustc_commit.sh"
20+
1721
PASSING_TSV="${UI_DIR}/passing.tsv"
1822

1923
KEEP_FILES=${KEEP_FILES:-""}
@@ -29,7 +33,7 @@ passed=0
2933
total=0
3034

3135
while read -r test; do
32-
test_path="${RUST_DIR_ROOT}/${test}"
36+
test_path="${RUST_SRC_DIR}/${test}"
3337
test_name="$(basename "$test" .rs)"
3438
json_file="${PWD}/${test_name}.smir.json"
3539

0 commit comments

Comments
 (0)