diff --git a/config.toml.example b/config.toml.example index a9281a31b13..18c1f160c03 100644 --- a/config.toml.example +++ b/config.toml.example @@ -301,6 +301,10 @@ # As a side-effect also generates MIR for all libraries. #test-miri = false +# After building or testing extended tools (e.g. clippy and rustfmt), append the +# result (broken, compiling, testing) into this JSON file. +#save-toolstates = "/path/to/toolstates.json" + # ============================================================================= # Options for specific targets # diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs index 103d7f2ba18..8f3133bc9d8 100644 --- a/src/bootstrap/check.rs +++ b/src/bootstrap/check.rs @@ -65,19 +65,21 @@ impl fmt::Display for TestKind { } } -fn try_run_expecting(build: &Build, cmd: &mut Command, expect: BuildExpectation) { +fn try_run_expecting(build: &Build, cmd: &mut Command, expect: BuildExpectation) -> bool { if !build.fail_fast { if !build.try_run(cmd, expect) { let mut failures = build.delayed_failures.borrow_mut(); failures.push(format!("{:?}", cmd)); + return false; } } else { build.run_expecting(cmd, expect); } + true } fn try_run(build: &Build, cmd: &mut Command) { - try_run_expecting(build, cmd, BuildExpectation::None) + try_run_expecting(build, cmd, BuildExpectation::None); } fn try_run_quiet(build: &Build, cmd: &mut Command) { @@ -257,11 +259,13 @@ impl Step for Rls { builder.add_rustc_lib_path(compiler, &mut cargo); - try_run_expecting( + if try_run_expecting( build, &mut cargo, builder.build.config.toolstate.rls.passes(ToolState::Testing), - ); + ) { + build.save_toolstate("rls", ToolState::Testing); + } } } @@ -305,11 +309,13 @@ impl Step for Rustfmt { builder.add_rustc_lib_path(compiler, &mut cargo); - try_run_expecting( + if try_run_expecting( build, &mut cargo, builder.build.config.toolstate.rustfmt.passes(ToolState::Testing), - ); + ) { + build.save_toolstate("rustfmt", ToolState::Testing); + } } } @@ -354,11 +360,13 @@ impl Step for Miri { builder.add_rustc_lib_path(compiler, &mut cargo); - try_run_expecting( + if try_run_expecting( build, &mut cargo, builder.build.config.toolstate.miri.passes(ToolState::Testing), - ); + ) { + build.save_toolstate("miri", ToolState::Testing); + } } else { eprintln!("failed to test miri: could not build"); } @@ -411,11 +419,13 @@ impl Step for Clippy { builder.add_rustc_lib_path(compiler, &mut cargo); - try_run_expecting( + if try_run_expecting( build, &mut cargo, builder.build.config.toolstate.clippy.passes(ToolState::Testing), - ); + ) { + build.save_toolstate("clippy-driver", ToolState::Testing); + } } else { eprintln!("failed to test clippy: could not build"); } diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index d43cd54ddce..9dd37d8e456 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -112,6 +112,8 @@ pub struct Config { pub channel: String, pub quiet_tests: bool, pub test_miri: bool, + pub save_toolstates: Option, + // Fallback musl-root for all targets pub musl_root: Option, pub prefix: Option, @@ -279,6 +281,7 @@ struct Rust { dist_src: Option, quiet_tests: Option, test_miri: Option, + save_toolstates: Option, } /// TOML representation of how each build target is configured. @@ -473,6 +476,7 @@ impl Config { set(&mut config.test_miri, rust.test_miri); config.rustc_default_linker = rust.default_linker.clone(); config.musl_root = rust.musl_root.clone().map(PathBuf::from); + config.save_toolstates = rust.save_toolstates.clone().map(PathBuf::from); match rust.codegen_units { Some(0) => config.rust_codegen_units = Some(num_cpus::get() as u32), diff --git a/src/bootstrap/configure.py b/src/bootstrap/configure.py index 579422c9799..48ca2838e4f 100755 --- a/src/bootstrap/configure.py +++ b/src/bootstrap/configure.py @@ -77,6 +77,7 @@ o("debuginfo", "rust.debuginfo", "build with debugger metadata") o("debuginfo-lines", "rust.debuginfo-lines", "build with line number debugger metadata") o("debuginfo-only-std", "rust.debuginfo-only-std", "build only libstd with debugging information") o("debug-jemalloc", "rust.debug-jemalloc", "build jemalloc with --enable-debug --enable-fill") +v("save-toolstates", "rust.save-toolstates", "save build and test status of external tools into this file") v("prefix", "install.prefix", "set installation prefix") v("localstatedir", "install.localstatedir", "local state directory") diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs index 68329922592..2f00c313a0c 100644 --- a/src/bootstrap/lib.rs +++ b/src/bootstrap/lib.rs @@ -190,6 +190,7 @@ mod job { pub use config::Config; use flags::Subcommand; use cache::{Interned, INTERNER}; +use toolstate::ToolState; /// A structure representing a Rust compiler. /// @@ -874,6 +875,30 @@ impl Build { } } + /// Updates the actual toolstate of a tool. + /// + /// The toolstates are saved to the file specified by the key + /// `rust.save-toolstates` in `config.toml`. If unspecified, nothing will be + /// done. The file is updated immediately after this function completes. + pub fn save_toolstate(&self, tool: &str, state: ToolState) { + use std::io::{Seek, SeekFrom}; + + if let Some(ref path) = self.config.save_toolstates { + let mut file = t!(fs::OpenOptions::new() + .create(true) + .read(true) + .write(true) + .open(path)); + + let mut current_toolstates: HashMap, ToolState> = + serde_json::from_reader(&mut file).unwrap_or_default(); + current_toolstates.insert(tool.into(), state); + t!(file.seek(SeekFrom::Start(0))); + t!(file.set_len(0)); + t!(serde_json::to_writer(file, ¤t_toolstates)); + } + } + /// Get a list of crates from a root crate. /// /// Returns Vec<(crate, path to crate, is_root_crate)> diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index fe1c8292340..fa9bdc43c37 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -115,7 +115,19 @@ impl Step for ToolBuild { println!("Building stage{} tool {} ({})", compiler.stage, tool, target); let mut cargo = prepare_tool_cargo(builder, compiler, target, "build", path); - if !build.try_run(&mut cargo, expectation) { + let is_expected = build.try_run(&mut cargo, expectation); + // If the expectation is "Failing", `try_run` returning true actually + // means a build-failure is successfully observed, i.e. the tool is + // broken. Thus the XOR here. + // Sorry for the complicated logic, but we can remove this expectation + // logic after #45861 is fully fixed. + build.save_toolstate(tool, if is_expected ^ (expectation == BuildExpectation::Failing) { + ToolState::Compiling + } else { + ToolState::Broken + }); + + if !is_expected { if expectation == BuildExpectation::None { exit(1); } else { diff --git a/src/bootstrap/toolstate.rs b/src/bootstrap/toolstate.rs index 328cbf0e5d7..00dbcc86af4 100644 --- a/src/bootstrap/toolstate.rs +++ b/src/bootstrap/toolstate.rs @@ -10,7 +10,7 @@ use build_helper::BuildExpectation; -#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] /// Whether a tool can be compiled, tested or neither pub enum ToolState { /// The tool compiles successfully, but the test suite fails