Skip to content

Commit 991e584

Browse files
committed
Add repro-check tool
Add a new host tool that checks whether Rust builds are reproducible. It builds the stage-2 toolchain (or a full distribution) twice in separate workspaces and compares the resulting sysroots byte-for-byte using SHA-256. Differences are reported in an HTML file. The tool writes a minimal deterministic `bootstrap.toml` for each build, hashes files in parallel with Rayon, and supports the usual flags (`--jobs`, `--exclude-pattern`, `--path-delta`, `--full-dist`, etc.). Refs #139793, #134589, #144669. Signed-off-by: Sunil Dora <sunilkumar.dora@windriver.com>
1 parent f520900 commit 991e584

File tree

13 files changed

+859
-0
lines changed

13 files changed

+859
-0
lines changed

Cargo.lock

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,21 @@ dependencies = [
239239
"wait-timeout",
240240
]
241241

242+
[[package]]
243+
name = "assert_fs"
244+
version = "1.1.3"
245+
source = "registry+https://github.com/rust-lang/crates.io-index"
246+
checksum = "a652f6cb1f516886fcfee5e7a5c078b9ade62cfcb889524efe5a64d682dd27a9"
247+
dependencies = [
248+
"anstyle",
249+
"doc-comment",
250+
"globwalk",
251+
"predicates",
252+
"predicates-core",
253+
"predicates-tree",
254+
"tempfile",
255+
]
256+
242257
[[package]]
243258
name = "autocfg"
244259
version = "1.5.0"
@@ -544,8 +559,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
544559
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
545560
dependencies = [
546561
"iana-time-zone",
562+
"js-sys",
547563
"num-traits",
548564
"serde",
565+
"wasm-bindgen",
549566
"windows-link 0.2.1",
550567
]
551568

@@ -1265,6 +1282,12 @@ version = "1.0.10"
12651282
source = "registry+https://github.com/rust-lang/crates.io-index"
12661283
checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921"
12671284

1285+
[[package]]
1286+
name = "doc-comment"
1287+
version = "0.3.4"
1288+
source = "registry+https://github.com/rust-lang/crates.io-index"
1289+
checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9"
1290+
12681291
[[package]]
12691292
name = "dyn-clone"
12701293
version = "1.0.20"
@@ -1637,6 +1660,17 @@ dependencies = [
16371660
"regex-syntax",
16381661
]
16391662

1663+
[[package]]
1664+
name = "globwalk"
1665+
version = "0.9.1"
1666+
source = "registry+https://github.com/rust-lang/crates.io-index"
1667+
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
1668+
dependencies = [
1669+
"bitflags",
1670+
"ignore",
1671+
"walkdir",
1672+
]
1673+
16401674
[[package]]
16411675
name = "gsgdt"
16421676
version = "0.1.2"
@@ -3311,6 +3345,27 @@ dependencies = [
33113345
"walkdir",
33123346
]
33133347

3348+
[[package]]
3349+
name = "repro-check"
3350+
version = "0.1.0"
3351+
dependencies = [
3352+
"anyhow",
3353+
"assert_fs",
3354+
"chrono",
3355+
"clap",
3356+
"env_logger",
3357+
"hex",
3358+
"ignore",
3359+
"log",
3360+
"num_cpus",
3361+
"rayon",
3362+
"serde",
3363+
"sha2",
3364+
"tempfile",
3365+
"toml 0.8.23",
3366+
"walkdir",
3367+
]
3368+
33143369
[[package]]
33153370
name = "run_make_support"
33163371
version = "0.0.0"

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ members = [
3333
"src/tools/remote-test-client",
3434
"src/tools/remote-test-server",
3535
"src/tools/replace-version-placeholder",
36+
"src/tools/repro-check",
3637
"src/tools/run-make-support",
3738
"src/tools/rust-installer",
3839
"src/tools/rustdoc",

src/bootstrap/src/core/build_steps/tool.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,7 @@ bootstrap_tool!(
501501
UnicodeTableGenerator, "src/tools/unicode-table-generator", "unicode-table-generator";
502502
FeaturesStatusDump, "src/tools/features-status-dump", "features-status-dump";
503503
OptimizedDist, "src/tools/opt-dist", "opt-dist", submodules = &["src/tools/rustc-perf"];
504+
ReproCheck, "src/tools/repro-check", "repro-check";
504505
RunMakeSupport, "src/tools/run-make-support", "run_make_support", artifact_kind = ToolArtifactKind::Library;
505506
);
506507

src/bootstrap/src/core/builder/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,7 @@ impl<'a> Builder<'a> {
801801
llvm::CrtBeginEnd,
802802
tool::RustdocGUITest,
803803
tool::OptimizedDist,
804+
tool::ReproCheck,
804805
tool::CoverageDump,
805806
tool::LlvmBitcodeLinker,
806807
tool::RustcPerf,

src/tools/repro-check/Cargo.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "repro-check"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
anyhow = "1.0"
8+
clap = { version = "4.5", features = ["derive", "env"] }
9+
chrono = { version = "0.4", features = ["clock"] }
10+
env_logger = "0.11"
11+
hex = "0.4"
12+
ignore = "0.4"
13+
log = "0.4"
14+
num_cpus = "1.16"
15+
rayon = "1.10"
16+
serde = { version = "1.0", features = ["derive"] }
17+
sha2 = "0.10"
18+
toml = "0.8"
19+
walkdir = "2.5"
20+
21+
[dev-dependencies]
22+
assert_fs = "1.1"
23+
tempfile = "3.12"

src/tools/repro-check/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# repro-check
2+
3+
repro-check is a lightweight tool designed to verify the reproducibility of Rust compiler builds.
4+
5+
It works by creating two separate copies of the Rust source tree, building stage-2 (or a full distribution)
6+
in each copy, and then comparing the resulting sysroots using SHA-256 checksums.
7+
If any discrepancies are detected, repro-check generates a detailed HTML report highlighting the differences
8+
and exits with a non-zero status.
9+
10+
This tool is ideal for developers and CI systems aiming to ensure deterministic, reproducible builds of Rust.
11+
12+
## How to build and run:
13+
14+
```bash
15+
./x.py build src/tools/repro-check
16+
./build/<your-host-triple>/stage1-tools-bin/repro-check --help
17+
18+
# simplest run (host target, stage 2 only)
19+
./build/x86_64-unknown-linux-gnu/stage1-tools-bin/repro-check
20+
21+
# Recommended usage
22+
./build/x86_64-unknown-linux-gnu/stage1-tools-bin/repro-check \
23+
--jobs 16 \
24+
--exclude-pattern .so \
25+
--path-delta 10 \
26+
--html-output my-report.html
27+
28+
# full distribution (takes a long time)
29+
./build/x86_64-unknown-linux-gnu/stage1-tools-bin/repro-check --full-dist
30+
31+
# start from scratch
32+
./build/x86_64-unknown-linux-gnu/stage1-tools-bin/repro-check --clean
33+
34+
All flags:
35+
36+
--src-root <dir> path to the rust checkout (default: current dir)
37+
--target <triple> build for this target (default: host)
38+
--jobs <n> parallel jobs (default: number of CPUs)
39+
--html-output <file> report file (default: repro_report.html)
40+
--exclude-pattern <pat> ignore files ending with <pat> (can be repeated)
41+
--path-delta <n> make the second build’s path longer by n segments (default 10)
42+
--full-dist build a complete distribution instead of stage 2
43+
--clean delete the workspace first
44+
--skip-copy reuse an existing workspace (don’t copy source)
45+
--verbose print more details
46+
47+
- Tests: Run `cargo test` in src/tools/repro-check.

src/tools/repro-check/src/build.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use std::env;
2+
use std::path::Path;
3+
use std::process::{Command, Stdio};
4+
5+
use anyhow::{Context, Result};
6+
use log::{info, warn};
7+
8+
// Runs x.py in the given environment root. Handles the build or dist command,
9+
// stage limiting, and job config.
10+
pub fn run_xpy(env_root: &Path, jobs: u32, target: Option<&str>, full_dist: bool) -> Result<()> {
11+
let x_py = env_root.join("x.py");
12+
13+
let python = env::var("BOOTSTRAP_PYTHON").unwrap_or_else(|_| {
14+
if cfg!(windows) { "python".to_string() } else { "python3".to_string() }
15+
});
16+
17+
let mut cmd = Command::new(&python);
18+
cmd.arg(&x_py);
19+
20+
let build_cmd = if full_dist { "dist" } else { "build" };
21+
cmd.arg(build_cmd);
22+
23+
if !full_dist {
24+
cmd.arg("--stage").arg("2");
25+
cmd.arg("compiler");
26+
}
27+
28+
if let Some(t) = target {
29+
cmd.arg("--target").arg(t);
30+
}
31+
32+
cmd.arg("-j").arg(jobs.to_string());
33+
cmd.arg("--config").arg("bootstrap.toml");
34+
cmd.current_dir(env_root);
35+
cmd.stdout(Stdio::inherit());
36+
cmd.stderr(Stdio::inherit());
37+
38+
info!("Kicking off: {} {}", python, x_py.display());
39+
40+
let status = cmd.status().with_context(|| format!("Couldn't run x.py in {:?}", env_root))?;
41+
if !status.success() {
42+
return Err(anyhow::anyhow!("Build bombed in {:?}", env_root));
43+
}
44+
45+
Ok(())
46+
}
47+
48+
// Figures out the host triple by asking rustc, or guessing if that fails.
49+
pub fn detect_host(src_root: &Path) -> Result<String> {
50+
let output = Command::new("rustc")
51+
.arg("-vV")
52+
.output()
53+
.context("Couldn't query rustc for version info")?;
54+
55+
if !output.status.success() {
56+
warn!("rustc -vV didn't work; falling back to a guess.");
57+
}
58+
59+
let out_str = String::from_utf8_lossy(&output.stdout);
60+
for line in out_str.lines() {
61+
if line.starts_with("host: ") {
62+
return Ok(line.trim_start_matches("host: ").trim().to_string());
63+
}
64+
}
65+
66+
let arch = if cfg!(target_arch = "x86_64") {
67+
"x86_64"
68+
} else if cfg!(target_arch = "aarch64") {
69+
"aarch64"
70+
} else {
71+
"unknown"
72+
};
73+
let os = if cfg!(target_os = "windows") {
74+
"windows"
75+
} else if cfg!(target_os = "macos") {
76+
"apple-darwin"
77+
} else {
78+
"linux-gnu"
79+
};
80+
info!("Detected host from src root {:?}: {arch}-unknown-{os}", src_root);
81+
Ok(format!("{arch}-unknown-{os}"))
82+
}

0 commit comments

Comments
 (0)