diff --git a/src/bootstrap/src/bin/main.rs b/src/bootstrap/src/bin/main.rs index 44eab9b87783b..63e443bac2363 100644 --- a/src/bootstrap/src/bin/main.rs +++ b/src/bootstrap/src/bin/main.rs @@ -13,7 +13,7 @@ use std::time::Instant; use std::{env, process}; use bootstrap::{ - Build, CONFIG_CHANGE_HISTORY, ChangeId, Config, Flags, Subcommand, debug, + Build, CONFIG_CHANGE_HISTORY, ChangeId, Config, Flags, StepStack, Subcommand, debug, find_recent_config_change_ids, human_readable_changes, t, }; @@ -27,6 +27,25 @@ fn main() { let _start_time = Instant::now(); + // Always print backtraces to provide richer errors, to help debug hard-to-reproduce panics + // when the user didn't specify RUST_BACKTRACE + if std::env::var("RUST_BACKTRACE").is_err() { + unsafe { + std::env::set_var("RUST_BACKTRACE", "1"); + } + } + + let default_panic_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + default_panic_hook(info); + StepStack::with_current(|stack| { + eprintln!("\nBootstrap has panicked, currently active steps:"); + for step in stack.get_active_steps() { + eprintln!("{} at {}", step.info, step.location); + } + }); + })); + let args = env::args().skip(1).collect::>(); if Flags::try_parse_verbose_help(&args) { diff --git a/src/bootstrap/src/core/build_steps/clippy.rs b/src/bootstrap/src/core/build_steps/clippy.rs index d3b533c954293..f03ae75c4bbf2 100644 --- a/src/bootstrap/src/core/build_steps/clippy.rs +++ b/src/bootstrap/src/core/build_steps/clippy.rs @@ -13,8 +13,6 @@ //! to pass a prebuilt Clippy from the outside when running `cargo clippy`, but that would be //! (as usual) a massive undertaking/refactoring. -use build_helper::exit; - use super::compile::{ArtifactKeepMode, run_cargo, rustc_cargo, std_cargo}; use super::tool::{SourceType, prepare_tool_cargo}; use crate::builder::{Builder, ShouldRun}; @@ -23,7 +21,7 @@ use crate::core::build_steps::compile::std_crates_for_run_make; use crate::core::builder; use crate::core::builder::{Alias, Kind, RunConfig, Step, StepMetadata, crate_description}; use crate::utils::build_stamp::{self, BuildStamp}; -use crate::{Compiler, Mode, Subcommand, TargetSelection}; +use crate::{Compiler, Mode, Subcommand, TargetSelection, exit}; /// Disable the most spammy clippy lints const IGNORED_RULES_FOR_STD_AND_RUSTC: &[&str] = &[ diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 3216fd671c3f8..4944737fe047b 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -38,7 +38,7 @@ use crate::utils::helpers::{ }; use crate::{ CLang, CodegenBackendKind, Compiler, DependencyType, FileType, GitRepo, LLVM_TOOLS, Mode, - debug, trace, + debug, exit, trace, }; /// Build a standard library for the given `target` using the given `build_compiler`. @@ -2068,7 +2068,7 @@ impl Step for Sysroot { sysroot_lib_rustlib_src_rust.display(), ); } - build_helper::exit!(1); + exit!(1); } } @@ -2086,7 +2086,7 @@ impl Step for Sysroot { builder.src.display(), e, ); - build_helper::exit!(1); + exit!(1); } } diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index 6f1b8532f031b..1a453ad6a7a55 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -14,7 +14,6 @@ use std::path::{Path, PathBuf}; use std::sync::OnceLock; use std::{env, fs}; -use build_helper::exit; use build_helper::git::PathFreshness; use crate::core::build_steps::llvm; @@ -25,7 +24,7 @@ use crate::utils::exec::command; use crate::utils::helpers::{ self, exe, get_clang_cl_resource_dir, libdir, t, unhashed_basename, up_to_date, }; -use crate::{CLang, GitRepo, Kind, trace}; +use crate::{CLang, GitRepo, Kind, exit, trace}; #[derive(Clone)] pub struct LlvmResult { diff --git a/src/bootstrap/src/core/build_steps/run.rs b/src/bootstrap/src/core/build_steps/run.rs index 935844daa9d7c..1d704230902e4 100644 --- a/src/bootstrap/src/core/build_steps/run.rs +++ b/src/bootstrap/src/core/build_steps/run.rs @@ -5,7 +5,6 @@ use std::path::PathBuf; -use build_helper::exit; use build_helper::git::get_git_untracked_files; use clap_complete::{Generator, shells}; @@ -17,7 +16,7 @@ use crate::core::builder::{Builder, Kind, RunConfig, ShouldRun, Step, StepMetada use crate::core::config::TargetSelection; use crate::core::config::flags::{get_completion, top_level_help}; use crate::utils::exec::command; -use crate::{Mode, t}; +use crate::{Mode, exit, t}; #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct BuildManifest; diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 697f659816120..61529da631a06 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -13,8 +13,6 @@ use std::path::{Path, PathBuf}; use std::process::Command; use std::{env, fs, iter}; -use build_helper::exit; - use crate::core::build_steps::compile::{ArtifactKeepMode, Std, run_cargo}; use crate::core::build_steps::doc::{DocumentationFormat, prepare_doc_compiler}; use crate::core::build_steps::gcc::{Gcc, GccTargetPair, add_cg_gcc_cargo_flags}; @@ -44,7 +42,7 @@ use crate::utils::helpers::{ up_to_date, }; use crate::utils::render_tests::{add_flags_and_try_run_tests, try_run_tests}; -use crate::{CLang, CodegenBackendKind, GitRepo, Mode, PathSet, TestTarget, envify}; +use crate::{CLang, CodegenBackendKind, GitRepo, Mode, PathSet, TestTarget, envify, exit}; mod compiletest; pub mod failed_tests; diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 66e14dae9b9ec..0f68988a683cf 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -21,16 +21,20 @@ use crate::core::build_steps::{ check, clean, clippy, compile, dist, doc, gcc, install, llvm, run, setup, test, tool, vendor, }; use crate::core::builder::cli_paths::CLIStepPath; +use crate::core::builder::step_stack::StepRecord; +pub use crate::core::builder::step_stack::StepStack; use crate::core::config::flags::Subcommand; use crate::core::config::{DryRun, TargetSelection}; use crate::utils::build_stamp::BuildStamp; use crate::utils::cache::Cache; use crate::utils::exec::{BootstrapCommand, ExecutionContext, command}; use crate::utils::helpers::{self, LldThreads, add_dylib_path, exe, libdir, linker_args, t}; +use crate::utils::tracing::format_location; use crate::{Build, Crate, trace}; mod cargo; mod cli_paths; +mod step_stack; #[cfg(test)] mod tests; @@ -1112,6 +1116,7 @@ impl<'a> Builder<'a> { Subcommand::Perf { .. } => (Kind::Perf, &paths[..]), }; + StepStack::with_current(|stack| stack.clear()); Self::new_internal(build, kind, paths.to_owned()) } @@ -1574,6 +1579,12 @@ Alternatively, you can set `build.local-rebuild=true` and use a stage0 compiler graph.register_step_execution(&step, parent, self.config.dry_run()); } + // The location has to be gathered in this function, to be correctly propagated with + // #[track_caller]. + let location = format_location(*std::panic::Location::caller()); + StepStack::with_current(|stack| { + stack.push(StepRecord { info: pretty_print_step(&step), location }); + }); stack.push(Box::new(step.clone())); } @@ -1599,7 +1610,7 @@ Alternatively, you can set `build.local-rebuild=true` and use a stage0 compiler "step", step_name = pretty_step_name::(), args = step_debug_args(&step), - location = crate::utils::tracing::format_location(*std::panic::Location::caller()) + location = format_location(*std::panic::Location::caller()) ); span.entered() }; @@ -1626,6 +1637,10 @@ Alternatively, you can set `build.local-rebuild=true` and use a stage0 compiler let mut stack = self.stack.borrow_mut(); let cur_step = stack.pop().expect("step stack empty"); assert_eq!(cur_step.downcast_ref(), Some(&step)); + + StepStack::with_current(|stack| { + stack.pop(); + }); } self.cache.put(step, out.clone()); out diff --git a/src/bootstrap/src/core/builder/step_stack.rs b/src/bootstrap/src/core/builder/step_stack.rs new file mode 100644 index 0000000000000..5a6bf31809a64 --- /dev/null +++ b/src/bootstrap/src/core/builder/step_stack.rs @@ -0,0 +1,48 @@ +use std::cell::RefCell; + +thread_local! { + static STEP_STACK: RefCell = const { RefCell::new(StepStack::new()) }; +} + +/// This type serves for recording the stack of executed steps in a thread-local variable. +/// +/// It is used to print the currently running step stack in places where we do not have easy +/// access to the currently used builder, e.g. in exit! macros or the panic handler. +pub struct StepStack { + stack: Vec, +} + +pub struct StepRecord { + pub info: String, + pub location: String, +} + +impl StepStack { + /// Return the currently active step stack for this thread. + pub fn with_current(func: F) + where + F: FnOnce(&mut StepStack), + { + STEP_STACK.with(|stack| func(&mut stack.borrow_mut())); + } + + const fn new() -> Self { + Self { stack: Vec::new() } + } + + pub fn get_active_steps(&self) -> impl Iterator { + self.stack.iter() + } + + pub fn clear(&mut self) { + self.stack.clear(); + } + + pub fn push(&mut self, record: StepRecord) { + self.stack.push(record); + } + + pub fn pop(&mut self) { + self.stack.pop(); + } +} diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 267e21c599728..59307b515a6de 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -22,7 +22,6 @@ use std::sync::{Arc, Mutex}; use std::{cmp, env, fs}; use build_helper::ci::CiEnv; -use build_helper::exit; use build_helper::git::{GitConfig, PathFreshness, check_path_modifications}; use serde::Deserialize; #[cfg(feature = "tracing")] @@ -57,8 +56,10 @@ use crate::core::download::{ }; use crate::utils::channel; use crate::utils::exec::{ExecutionContext, command}; -use crate::utils::helpers::{exe, get_host_target}; -use crate::{CodegenBackendKind, GitInfo, OnceLock, TargetSelection, check_ci_llvm, helpers, t}; +use crate::utils::helpers::{exe, fail, get_host_target}; +use crate::{ + CodegenBackendKind, GitInfo, OnceLock, TargetSelection, check_ci_llvm, exit, helpers, t, +}; /// Each path in this list is considered "allowed" in the `download-rustc="if-unchanged"` logic. /// This means they can be modified and changes to these paths should never trigger a compiler build @@ -2173,7 +2174,7 @@ fn postprocess_toml( } } eprintln!("failed to parse override `{option}`: `{err}"); - exit!(2) + exit!(2); } toml.merge(None, &mut Default::default(), override_toml, ReplaceOpt::Override); } @@ -2195,8 +2196,6 @@ pub fn check_stage0_version( src_dir: &Path, exec_ctx: &ExecutionContext, ) { - use build_helper::util::fail; - if exec_ctx.dry_run() { return; } diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index bf171f1de34e0..f5ab76b3078d3 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -757,7 +757,7 @@ pub fn get_completion(shell: &dyn Generator, path: &Path) -> Option { } else { std::fs::read_to_string(path).unwrap_or_else(|_| { eprintln!("couldn't read {}", path.display()); - crate::exit!(1) + crate::exit!(1); }) }; let mut buf = Vec::new(); diff --git a/src/bootstrap/src/core/config/mod.rs b/src/bootstrap/src/core/config/mod.rs index c29849cf5a5d6..c07e7d28fbb26 100644 --- a/src/bootstrap/src/core/config/mod.rs +++ b/src/bootstrap/src/core/config/mod.rs @@ -30,7 +30,6 @@ pub mod toml; use std::collections::HashSet; use std::path::PathBuf; -use build_helper::exit; pub use config::*; use serde::de::Unexpected; use serde::{Deserialize, Deserializer}; @@ -41,8 +40,8 @@ pub use toml::change_id::ChangeId; pub use toml::rust::BootstrapOverrideLld; pub use toml::target::Target; -use crate::Display; use crate::str::FromStr; +use crate::{Display, exit}; // We are using a decl macro instead of a derive proc macro here to reduce the compile time of bootstrap. #[macro_export] diff --git a/src/bootstrap/src/core/download.rs b/src/bootstrap/src/core/download.rs index 2ed7d55b00da6..078012be72c59 100644 --- a/src/bootstrap/src/core/download.rs +++ b/src/bootstrap/src/core/download.rs @@ -248,11 +248,11 @@ impl Config { #[cfg(not(test))] pub(crate) fn maybe_download_ci_llvm(&self) { - use build_helper::exit; use build_helper::git::PathFreshness; use crate::core::build_steps::llvm::detect_llvm_freshness; use crate::core::config::toml::llvm::check_incompatible_options_for_ci_llvm; + use crate::exit; if !self.llvm_from_ci { return; diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 2c8e002123abf..f7ffc682e2bd7 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -26,7 +26,6 @@ use std::time::{Instant, SystemTime}; use std::{env, fs, io, str}; use build_helper::ci::gha; -use build_helper::exit; use cc::Tool; use termcolor::{ColorChoice, StandardStream, WriteColor}; use utils::build_stamp::BuildStamp; @@ -42,9 +41,9 @@ use crate::utils::helpers::{self, dir_is_empty, exe, libdir, set_file_times, spl mod core; mod utils; -pub use core::builder::PathSet; #[cfg(feature = "tracing")] pub use core::builder::STEP_SPAN_TARGET; +pub use core::builder::{PathSet, StepStack}; pub use core::config::flags::{Flags, Subcommand}; pub use core::config::{ChangeId, Config}; @@ -2149,3 +2148,10 @@ pub fn prepare_behaviour_dump_dir(build: &Build) { t!(INITIALIZED.set(true)); } } + +#[macro_export] +macro_rules! exit { + ($code:expr) => { + $crate::utils::helpers::detail_exit($code, cfg!(test)); + }; +} diff --git a/src/bootstrap/src/utils/exec.rs b/src/bootstrap/src/utils/exec.rs index f9b0d2dbb2095..d3a7c4ec6e1a1 100644 --- a/src/bootstrap/src/utils/exec.rs +++ b/src/bootstrap/src/utils/exec.rs @@ -23,10 +23,9 @@ use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use build_helper::drop_bomb::DropBomb; -use build_helper::exit; use crate::core::config::DryRun; -use crate::{PathBuf, t}; +use crate::{PathBuf, exit, t}; /// What should be done when the command fails. #[derive(Debug, Copy, Clone)] diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs index db740d8dcf624..f0a39b0801ad2 100644 --- a/src/bootstrap/src/utils/helpers.rs +++ b/src/bootstrap/src/utils/helpers.rs @@ -10,13 +10,14 @@ use std::thread::panicking; use std::time::{Instant, SystemTime, UNIX_EPOCH}; use std::{env, fs, io, panic, str}; +use build_helper::ci::CiEnv; use object::read::archive::ArchiveFile; -use crate::BootstrapOverrideLld; use crate::core::builder::Builder; use crate::core::config::{Config, TargetSelection}; use crate::utils::exec::{BootstrapCommand, command}; pub use crate::utils::shared_helpers::{dylib_path, dylib_path_var}; +use crate::{BootstrapOverrideLld, StepStack}; #[cfg(test)] mod tests; @@ -562,3 +563,35 @@ pub fn set_file_times>(path: P, times: fs::FileTimes) -> io::Resu }; f.set_times(times) } + +/// If code is not 0 (successful exit status), exit status is 101 (rust's default error code.) +/// If `is_test` true and code is an error code, it will cause a panic. +pub fn detail_exit(code: i32, is_test: bool) -> ! { + // if in test and code is an error code, panic with status code provided + if is_test { + panic!("status code: {code}"); + } else { + // If we're in CI, print the current bootstrap invocation command, to make it easier to + // figure out what exactly has failed. + if CiEnv::is_ci() { + // Skip the first argument, as it will be some absolute path to the bootstrap binary. + let bootstrap_args = + std::env::args().skip(1).map(|a| a.to_string()).collect::>().join(" "); + eprintln!("Bootstrap failed while executing `{bootstrap_args}`"); + eprintln!("Currently active steps:"); + StepStack::with_current(|stack| { + for step in stack.get_active_steps() { + eprintln!("{} at {}", step.info, step.location); + } + }); + } + + // otherwise, exit with provided status code + std::process::exit(code); + } +} + +pub fn fail(s: &str) -> ! { + eprintln!("\n\n{s}\n\n"); + detail_exit(1, cfg!(test)); +} diff --git a/src/bootstrap/src/utils/tracing.rs b/src/bootstrap/src/utils/tracing.rs index 4e75347f4cbae..541aa088b9c30 100644 --- a/src/bootstrap/src/utils/tracing.rs +++ b/src/bootstrap/src/utils/tracing.rs @@ -66,7 +66,6 @@ macro_rules! trace_io { } } -#[cfg(feature = "tracing")] pub fn format_location(location: std::panic::Location<'static>) -> String { format!("{}:{}", location.file(), location.line()) } diff --git a/src/build_helper/src/util.rs b/src/build_helper/src/util.rs index 1bdbb7515e252..6866d4f055846 100644 --- a/src/build_helper/src/util.rs +++ b/src/build_helper/src/util.rs @@ -1,63 +1,6 @@ use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; -use std::process::Command; - -use crate::ci::CiEnv; - -/// Invokes `build_helper::util::detail_exit` with `cfg!(test)` -/// -/// This is a macro instead of a function so that it uses `cfg(test)` in the *calling* crate, not in build helper. -#[macro_export] -macro_rules! exit { - ($code:expr) => { - $crate::util::detail_exit($code, cfg!(test)); - }; -} - -/// If code is not 0 (successful exit status), exit status is 101 (rust's default error code.) -/// If `is_test` true and code is an error code, it will cause a panic. -pub fn detail_exit(code: i32, is_test: bool) -> ! { - // if in test and code is an error code, panic with status code provided - if is_test { - panic!("status code: {code}"); - } else { - // If we're in CI, print the current bootstrap invocation command, to make it easier to - // figure out what exactly has failed. - if CiEnv::is_ci() { - // Skip the first argument, as it will be some absolute path to the bootstrap binary. - let bootstrap_args = - std::env::args().skip(1).map(|a| a.to_string()).collect::>().join(" "); - eprintln!("Bootstrap failed while executing `{bootstrap_args}`"); - } - - // otherwise, exit with provided status code - std::process::exit(code); - } -} - -pub fn fail(s: &str) -> ! { - eprintln!("\n\n{s}\n\n"); - detail_exit(1, cfg!(test)); -} - -pub fn try_run(cmd: &mut Command, print_cmd_on_fail: bool) -> Result<(), ()> { - let status = match cmd.status() { - Ok(status) => status, - Err(e) => fail(&format!("failed to execute command: {cmd:?}\nerror: {e}")), - }; - if !status.success() { - if print_cmd_on_fail { - println!( - "\n\ncommand did not execute successfully: {cmd:?}\n\ - expected success, got: {status}\n\n" - ); - } - Err(()) - } else { - Ok(()) - } -} /// Returns the submodule paths from the `.gitmodules` file in the given directory. pub fn parse_gitmodules(target_dir: &Path) -> Vec { diff --git a/src/ci/run.sh b/src/ci/run.sh index 2aba67d28aee8..339d9ffa55faf 100755 --- a/src/ci/run.sh +++ b/src/ci/run.sh @@ -206,9 +206,6 @@ if [ "$ENABLE_GCC_CODEGEN" = "1" ]; then RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-new-symbol-mangling" fi -# If bootstrap fails, we want to see its backtrace -export RUST_BACKTRACE=1 - # Print the date from the local machine and the date from an external source to # check for clock drifts. An HTTP URL is used instead of HTTPS since on Azure # Pipelines it happened that the certificates were marked as expired. diff --git a/src/tools/rustdoc-gui-test/src/main.rs b/src/tools/rustdoc-gui-test/src/main.rs index 47cbd4fb20a70..043526b18c187 100644 --- a/src/tools/rustdoc-gui-test/src/main.rs +++ b/src/tools/rustdoc-gui-test/src/main.rs @@ -4,7 +4,6 @@ use std::process::Command; use std::sync::Arc; use build_helper::npm; -use build_helper::util::try_run; use compiletest::rustdoc_gui_test::RustdocGuiTestProps; use config::Config; @@ -93,3 +92,26 @@ fn main() -> Result<(), ()> { try_run(&mut command, config.verbose) } + +fn fail(s: &str) -> ! { + eprintln!("\n\n{s}\n\n"); + std::process::exit(1); +} + +fn try_run(cmd: &mut Command, print_cmd_on_fail: bool) -> Result<(), ()> { + let status = match cmd.status() { + Ok(status) => status, + Err(e) => fail(&format!("failed to execute command: {cmd:?}\nerror: {e}")), + }; + if !status.success() { + if print_cmd_on_fail { + println!( + "\n\ncommand did not execute successfully: {cmd:?}\n\ + expected success, got: {status}\n\n" + ); + } + Err(()) + } else { + Ok(()) + } +}