Change:
After I noticed that whenever I modify tests (on Windows), stage 1 cargo gets unnecessary rebuilt which also triggers rebuilds for other tools like stage 0 rustdoc. I'm pretty sure something in my implementation is wrong w.r.t. to properly caching, but I didn't invest time in figuring out why yet, hence opening this bug to track it. The build sequence is something like stage 0 std -stage 1 rustc -stage 1 std -stage 1 tool cargo -stage 0 compiletest / run-make-support / rustdoc. Example repro steps: Run -make sure stage 1 cargo is already built. Modify a test. Observe that stage 1 cargo gets rebuilt even though I didn't modify it.
EDIT: oh, is it because in run-make-support or rustdoc we use or something that is a different staged cargo compared to the one we built with stage 1 rustc...
Actually unassigning because I don't know how to cleanly fix this just yet. EDIT: opened thread https://rust-
Running twice is enough to reproduce cargo rebuild issue. Probably some rustflags invalidating the build cache.
Reopening because we need to revert in favor of a different approach.

Discussion:
cmd.args(lint_flags.split_whitespace()); } <ins> // Conditionally pass `-Zon-broken-pipe=kill` to underlying rustc. Not all binaries want // `-Zon-broken-pipe=kill`, which includes cargo itself. if env::var_os("FORCE_ON_BROKEN_PIPE_KILL").is_some() { cmd.arg("-Z").arg("on-broken-pipe=kill"); } </ins> if target.is_some() { // The stage0 compiler has a special sysroot distinct from what we // actually downloaded, so we just always pass the `--sysroot` option,

cargo.rustdocflag("-Zcrate-attr=warn(rust_2018_idioms)"); <del> // If the rustc output is piped to e.g. `head -n1` we want the process to be // killed, rather than having an error bubble up and cause a panic. </del> <ins> // If the rustc output is piped to e.g. `head -n1` we want the process to be killed, rather than // having an error bubble up and cause a panic. // // FIXME(jieyouxu): this flag is load-bearing for rustc to not ICE on broken pipes, because // rustc internally sometimes uses std `println!` -- but std `println!` by default will panic on // broken pipes, and uncaught panics will manifest as an ICE. The compiler *should* handle this // properly, but this flag is set in the meantime to paper over the I/O errors. // // See <https://github.com/rust-lang/rust/issues/131059> for details. // // Also see the discussion for properly handling I/O errors related to broken pipes, i.e. safe // variants of `println!` in // <https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Internal.20lint.20for.20raw.20.60print!.60.20and.20.60println!.60.3F>. </ins> cargo.rustflag("-Zon-broken-pipe=kill"); if builder.config.llvm_enzyme {

// See https://github.com/rust-lang/rust/issues/116538 cargo.rustflag("-Zunstable-options"); <del> // `-Zon-broken-pipe=kill` breaks cargo tests </del> <ins> // NOTE: The root cause of needing `-Zon-broken-pipe=kill` in the first place is because `rustc` // and `rustdoc` doesn't gracefully handle I/O errors due to usages of raw std `println!` macros // which panics upon encountering broken pipes. `-Zon-broken-pipe=kill` just papers over that // and stops rustc/rustdoc ICEing on e.g. `rustc --print=sysroot | false`. // // cargo explicitly does not want the `-Zon-broken-pipe=kill` paper because it does actually use // variants of `println!` that handles I/O errors gracefully. It's also a breaking change for a // spawn process not written in Rust, especially if the language default handler is not // `SIG_IGN`. Thankfully cargo tests will break if we do set the flag. // // For the cargo discussion, see // <https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/Applying.20.60-Zon-broken-pipe.3Dkill.60.20flags.20in.20bootstrap.3F>. // // For the rustc discussion, see // <https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Internal.20lint.20for.20raw.20.60print!.60.20and.20.60println!.60.3F> // for proper solutions. </ins> if !path.ends_with("cargo") { <del> // If the output is piped to e.g. `head -n1` we want the process to be killed, // rather than having an error bubble up and cause a panic. cargo.rustflag("-Zon-broken-pipe=kill"); </del> <ins> // Use an untracked env var `FORCE_ON_BROKEN_PIPE_KILL` here instead of `RUSTFLAGS`. // `RUSTFLAGS` is tracked by cargo. Conditionally omitting `-Zon-broken-pipe=kill` from // `RUSTFLAGS` causes unnecessary tool rebuilds due to cache invalidation from building e.g. // cargo *without* `-Zon-broken-pipe=kill` but then rustdoc *with* `-Zon-broken-pipe=kill`. cargo.env("FORCE_ON_BROKEN_PIPE_KILL", "-Zon-broken-pipe=kill"); </ins> } cargo

<ins> //! Check that `rustc` and `rustdoc` does not ICE upon encountering a broken pipe due to unhandled //! panics from raw std `println!` usages. //! //! Regression test for <https://github.com/rust-lang/rust/issues/34376>. //@ ignore-cross-compile (needs to run test binary) #![feature(anonymous_pipe)] use std::io::Read; use std::process::{Command, Stdio}; use run_make_support::env_var; #[derive(Debug, PartialEq)] enum Binary { Rustc, Rustdoc, } fn check_broken_pipe_handled_gracefully(bin: Binary, mut cmd: Command) { let (reader, writer) = std::pipe::pipe().unwrap(); drop(reader); // close read-end cmd.stdout(writer).stderr(Stdio::piped()); let mut child = cmd.spawn().unwrap(); let mut stderr = String::new(); child.stderr.as_mut().unwrap().read_to_string(&mut stderr).unwrap(); let status = child.wait().unwrap(); assert!(!status.success(), "{bin:?} unexpectedly succeeded"); const PANIC_ICE_EXIT_CODE: i32 = 101; #[cfg(not(windows))] { // On non-Windows, rustc/rustdoc built with `-Zon-broken-pipe=kill` shouldn't have an exit // code of 101 because it should have an wait status that corresponds to SIGPIPE signal // number. assert_ne!(status.code(), Some(PANIC_ICE_EXIT_CODE), "{bin:?}"); // And the stderr should be empty because rustc/rustdoc should've gotten killed. assert!(stderr.is_empty(), "{bin:?} stderr:n{}", stderr); } #[cfg(windows)] { match bin { // On Windows, rustc has a paper that propagates the panic exit code of 101 but converts // broken pipe errors into fatal errors instead of ICEs. Binary::Rustc => { assert_eq!(status.code(), Some(PANIC_ICE_EXIT_CODE), "{bin:?}"); // But make sure it doesn't manifest as an ICE. assert!(!stderr.contains("internal compiler error"), "{bin:?} ICE'd"); } // On Windows, rustdoc seems to cleanly exit with exit code of 1. Binary::Rustdoc => { assert_eq!(status.code(), Some(1), "{bin:?}"); assert!(!stderr.contains("panic"), "{bin:?} stderr contains panic"); } } } } fn main() { let mut rustc = Command::new(env_var("RUSTC")); rustc.arg("--print=sysroot"); check_broken_pipe_handled_gracefully(Binary::Rustc, rustc); let mut rustdoc = Command::new(env_var("RUSTDOC")); rustdoc.arg("--version"); check_broken_pipe_handled_gracefully(Binary::Rustdoc, rustdoc); } </ins>