core/build.rs: refuse a detector archive built by another rustc, in plain words

The archive links only against the exact rustc that built it; a mismatch
used to surface as pages of undefined std symbols from the linker. The
detector repo now writes rustc-version.txt next to the archive (and ships it
with the release); when it is present, build.rs compares it with its own
compiler and names both versions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
This commit is contained in:
Paul Bakaus
2026-09-01 15:53:18 -07:00
co-authored by Claude Fable 5.1
parent 3d8b13812f
commit 836516a7a0
+30
View File
@@ -74,6 +74,7 @@ fn main() {
bundle.display()
);
check_rustc_version(&dir);
println!("cargo:rustc-link-search=native={}", dir.display());
println!("cargo:rustc-link-lib=static={LIB_NAME}");
println!(
@@ -166,6 +167,10 @@ fn fetch_into(cache: &Path, lib: &Path, version: &str, short: &str, file_name: &
let tmp = cache.join(format!(".{file_name}.part.{}", std::process::id()));
fs::write(&tmp, &bytes).unwrap_or_else(|e| panic!("cannot write {}: {e}", tmp.display()));
fs::rename(&tmp, lib).unwrap_or_else(|e| panic!("cannot move {} into place: {e}", tmp.display()));
// Best effort: the compiler that built the release, for check_rustc_version.
if let Ok(v) = download(&format!("{base}/detector-v{version}/rustc-version.txt")) {
let _ = fs::write(cache.join("rustc-version.txt"), v);
}
println!("cargo:warning=fetched detector v{version} for {short} into {}", lib.display());
}
@@ -254,3 +259,28 @@ fn sha256_hex(bytes: &[u8]) -> String {
let digest = Sha256::digest(bytes);
digest.iter().map(|b| format!("{b:02x}")).collect()
}
/// The archive's objects reference std by mangled symbol name, so they only
/// link against the exact rustc that built them. `cargo xtask detector-archive`
/// writes `rustc-version.txt` next to the archive and the release ships it;
/// when it is there, refuse a mismatch with a message that names both
/// versions instead of leaving the linker to print pages of undefined symbols.
fn check_rustc_version(dir: &Path) {
let recorded = match fs::read_to_string(dir.join("rustc-version.txt")) {
Ok(s) => s.trim().to_string(),
Err(_) => return,
};
let rustc = env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
let out = match std::process::Command::new(&rustc).arg("-V").output() {
Ok(o) => String::from_utf8_lossy(&o.stdout).trim().to_string(),
Err(_) => return,
};
if !recorded.is_empty() && recorded != out {
panic!(
"the detector archive in {} was built by `{recorded}` but this build uses `{out}`. \
Both must be the exact version rust-toolchain.toml pins (run `rustup show`); a toolchain \
bump needs a new detector release (docs/ENGINE.md).",
dir.display()
);
}
}