diff --git a/rustbook b/rustbook new file mode 160000 index 0000000000000..62d261edd3f64 --- /dev/null +++ b/rustbook @@ -0,0 +1 @@ +Subproject commit 62d261edd3f64a95be42f87a07b9d5cab1497d7a diff --git a/src/bootstrap/src/bin/main.rs b/src/bootstrap/src/bin/main.rs index 833f80279517a..7a74aba646ac0 100644 --- a/src/bootstrap/src/bin/main.rs +++ b/src/bootstrap/src/bin/main.rs @@ -31,7 +31,7 @@ fn main() { debug!("parsing flags"); let flags = Flags::parse(&args); debug!("parsing config based on flags"); - let config = Config::parse(flags); + let (config, exec_context) = Config::parse(flags); let mut build_lock; let _build_lock_guard; @@ -96,7 +96,7 @@ fn main() { let out_dir = config.out.clone(); debug!("creating new build based on config"); - Build::new(config).build(); + Build::new(config, exec_context).build(); if suggest_setup { println!("WARNING: you have not made a `bootstrap.toml`"); diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 65a3e7667e7f0..536c098471631 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -30,6 +30,7 @@ use crate::core::config::flags::{Color, Flags, Warnings}; use crate::core::download::is_download_ci_available; use crate::utils::cache::{INTERNER, Interned}; use crate::utils::channel::{self, GitInfo}; +use crate::utils::context::ExecutionContext; use crate::utils::helpers::{self, exe, output, t}; /// Each path in this list is considered "allowed" in the `download-rustc="if-unchanged"` logic. @@ -1462,8 +1463,10 @@ impl Config { feature = "tracing", instrument(target = "CONFIG_HANDLING", level = "trace", name = "Config::parse", skip_all) )] - pub fn parse(flags: Flags) -> Config { - Self::parse_inner(flags, Self::get_toml) + pub fn parse(flags: Flags) -> (Config, ExecutionContext) { + let mut exec_context = ExecutionContext::new(flags.dry_run, flags.verbose as usize, false); + let config = Self::parse_inner(flags, Self::get_toml, &mut exec_context); + (config, exec_context) } #[cfg_attr( @@ -1478,6 +1481,7 @@ impl Config { pub(crate) fn parse_inner( mut flags: Flags, get_toml: impl Fn(&Path) -> Result, + exec_context: &mut ExecutionContext, ) -> Config { let mut config = Config::default_opts(); @@ -1600,11 +1604,11 @@ impl Config { let using_default_path = toml_path.is_none(); let mut toml_path = toml_path.unwrap_or_else(|| PathBuf::from("bootstrap.toml")); - if using_default_path && !toml_path.exists() { + if using_default_path && !exec_context.path_exists(&toml_path) { toml_path = config.src.join(PathBuf::from("bootstrap.toml")); - if !toml_path.exists() { + if !exec_context.path_exists(&toml_path) { toml_path = PathBuf::from("config.toml"); - if !toml_path.exists() { + if !exec_context.path_exists(&toml_path) { toml_path = config.src.join(PathBuf::from("config.toml")); } } @@ -1612,7 +1616,7 @@ impl Config { // Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path, // but not if `bootstrap.toml` hasn't been created. - let mut toml = if !using_default_path || toml_path.exists() { + let mut toml = if !using_default_path || exec_context.path_exists(&toml_path) { config.config = Some(if cfg!(not(test)) { toml_path = toml_path.canonicalize().unwrap(); toml_path.clone() diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index 08bd87e03a13b..b597ca2c970ce 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -203,8 +203,8 @@ impl Flags { HelpVerboseOnly::try_parse_from(normalize_args(args)) { println!("NOTE: updating submodules before printing available paths"); - let config = Config::parse(Self::parse(&[String::from("build")])); - let build = Build::new(config); + let (config, exec_context) = Config::parse(Self::parse(&[String::from("build")])); + let build = Build::new(config, exec_context); let paths = Builder::get_help(&build, subcommand); if let Some(s) = paths { println!("{s}"); diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 1e6acad5c0fc9..1d2b356682acf 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -36,6 +36,7 @@ use utils::channel::GitInfo; use crate::core::builder; use crate::core::builder::Kind; use crate::core::config::{DryRun, LldMode, LlvmLibunwind, TargetSelection, flags}; +use crate::utils::context::ExecutionContext; use crate::utils::exec::{BehaviorOnFailure, BootstrapCommand, CommandOutput, OutputMode, command}; use crate::utils::helpers::{ self, dir_is_empty, exe, libdir, output, set_file_times, split_debuginfo, symlink_dir, @@ -333,7 +334,7 @@ impl Build { /// line and the filesystem `config`. /// /// By default all build output will be placed in the current directory. - pub fn new(mut config: Config) -> Build { + pub fn new(mut config: Config, _exec_context: ExecutionContext) -> Build { let src = config.src.clone(); let out = config.out.clone(); diff --git a/src/bootstrap/src/utils/context.rs b/src/bootstrap/src/utils/context.rs new file mode 100644 index 0000000000000..b1b3c7fe3d026 --- /dev/null +++ b/src/bootstrap/src/utils/context.rs @@ -0,0 +1,254 @@ +#![allow(dead_code)] +use std::collections::HashMap; +use std::ffi::OsStr; +use std::os::unix::ffi::OsStrExt; +use std::path::{Path, PathBuf}; +use std::sync::Mutex; + +use build_helper::ci::CiEnv; +use build_helper::git::{GitConfig, PathFreshness}; + +use super::cache::{INTERNER, Interned}; +use super::exec::{BehaviorOnFailure, BootstrapCommand, OutputMode}; +use crate::CommandOutput; + +pub struct ExecutionContext { + dry_run: bool, + verbose: usize, + fail_fast: bool, + + command_output_cache: + Mutex>, Option), Result>>, + file_contents_cache: Mutex>>, + path_exist_cache: Mutex>, + path_modifications_cache: Mutex), PathFreshness>>, +} + +impl ExecutionContext { + pub fn new(dry_run: bool, verbose: usize, fail_fast: bool) -> Self { + Self { + dry_run, + verbose, + fail_fast, + command_output_cache: Mutex::new(HashMap::new()), + file_contents_cache: Mutex::new(HashMap::new()), + path_exist_cache: Mutex::new(HashMap::new()), + path_modifications_cache: Mutex::new(HashMap::new()), + } + } + + fn execute_bootstrap_command_internal( + &self, + cmd: &mut BootstrapCommand, + stdout_mode: OutputMode, + stderr_mode: OutputMode, + ) -> Result { + if self.dry_run && !cmd.run_always { + self.verbose_print(&format!("(dry run) {:?}", cmd)); + cmd.mark_as_executed(); + return Ok(CommandOutput::default()); + } + + self.verbose_print(&format!("running: {:?}", cmd)); + + let command = cmd.as_command_mut(); + command.stdout(stdout_mode.stdio()); + command.stderr(stderr_mode.stdio()); + + let output = match command.output() { + Ok(output) => { + self.verbose_print(&format!("finished running {:?}", command)); + CommandOutput::from_output(output, stdout_mode, stderr_mode) + } + Err(e) => { + let error_msg = format!("failed to execute {:?}: {}", command, e); + self.verbose_print(&error_msg); + let output = CommandOutput::did_not_start(stdout_mode, stderr_mode); + self.handle_failure(cmd, &output, &error_msg); + return Err(error_msg); + } + }; + + cmd.mark_as_executed(); + + if output.is_failure() && cmd.failure_behavior != BehaviorOnFailure::Ignore { + let error_msg = format!("command failed: {:?}", cmd); + self.handle_failure(cmd, &output, &error_msg); + Err(error_msg) + } else { + Ok(output) + } + } + + fn handle_failure(&self, cmd: &BootstrapCommand, output: &CommandOutput, error_msg: &str) { + if let Some(stderr) = output.stderr_if_present() { + eprintln!("{}\nStderr:\n{}", error_msg, stderr); + } else { + eprintln!("{}", error_msg); + } + + match cmd.failure_behavior { + BehaviorOnFailure::Exit => { + if self.fail_fast { + self.fatal_error(&format!("Exiting due to command failure: {:?}", cmd)); + } else { + eprintln!("(Failure Delayed)"); + } + } + BehaviorOnFailure::DelayFail => { + eprintln!("(Failure delayed)"); + } + BehaviorOnFailure::Ignore => {} + } + } + + pub fn read_file(&mut self, path: &Path) -> String { + let mut cache = self.file_contents_cache.lock().unwrap(); + if let Some(cached_result) = cache.get(path) { + self.verbose_print(&format!("(cached) Reading file: {:?}", path.display())); + return cached_result.as_ref().expect("Should be present").clone().to_owned(); + } + self.verbose_print(&format!("Reading file: {}", path.display())); + let result = std::fs::read_to_string(path); + let value = result.as_ref().expect("Should be present").to_owned(); + cache.insert(path.to_path_buf(), result); + value + } + + pub fn path_exists(&mut self, path: &Path) -> bool { + let mut cache = self.path_exist_cache.lock().unwrap(); + if let Some(cached_result) = cache.get(path) { + self.verbose_print(&format!("(cached) Checking path existence: {}", path.display())); + return *cached_result; + } + + self.verbose_print(&format!("Checking path existence: {}", path.display())); + let result = path.exists(); + cache.insert(path.to_path_buf(), result); + result + } + + pub fn run_cmd( + &mut self, + mut cmd: BootstrapCommand, + stdout_mode: OutputMode, + stderr_mode: OutputMode, + ) -> Result { + let command_key = { + let command = cmd.as_command_mut(); + let key_program = PathBuf::from(command.get_program()); + let key_args: Vec> = + command.get_args().map(|a| a.as_bytes().to_vec()).collect(); + let key_cwd = command.get_current_dir().map(|p| p.to_path_buf()); + (key_program, key_args, key_cwd) + }; + + let mut cache = self.command_output_cache.lock().unwrap(); + if let Some(cached_result) = cache.get(&command_key) { + self.verbose_print(&format!("(cache) Running BootstrapCommand: {:?}", cmd)); + return cached_result.clone(); + } + + let result = self.execute_bootstrap_command_internal(&mut cmd, stdout_mode, stderr_mode); + cache.insert(command_key.clone(), result.clone()); + result + } + + pub fn check_path_modifications<'a>( + &'a mut self, + src_dir: &Path, + git_config: &GitConfig<'a>, + paths: &[&'static str], + ) -> PathFreshness { + let cache_key = (src_dir.to_path_buf(), INTERNER.intern_str(&paths.join(","))); + + let mut cache = self.path_modifications_cache.lock().unwrap(); + if let Some(cached_result) = cache.get(&cache_key) { + self.verbose_print(&format!( + "(cached) check_path_modifications for paths: {:?}", + paths + )); + return cached_result.clone(); + } + + self.verbose_print(&format!("Running check_path_modification for paths: {:?}", paths)); + let result = build_helper::git::check_path_modifications( + src_dir, + git_config, + paths, + CiEnv::current(), + ) + .expect("check_path_modification_with_context failed"); + cache.insert(cache_key, result.clone()); + result + } + + pub fn fatal_error(&self, msg: &str) { + eprintln!("fatal error: {}", msg); + std::process::exit(1); + } + + pub fn warn(&self, msg: &str) { + eprintln!("warning: {}", msg); + } + + pub fn verbose_print(&self, msg: &str) { + if self.verbose > 0 { + println!("{}", msg); + } + } + + pub fn verbose(&self, f: impl Fn()) { + if self.verbose > 0 { + f(); + } + } + + pub fn is_dry_run(&self) -> bool { + self.dry_run + } + + pub fn is_verbose(&self) -> bool { + self.verbose > 0 + } + + pub fn is_fail_fast(&self) -> bool { + self.fail_fast + } + + pub fn git_command_for_path_check( + &mut self, + cwd: Option<&Path>, + args: &[&OsStr], + ) -> Result { + let program = Path::new("git"); + let mut cmd = BootstrapCommand::new(program); + if let Some(dir) = cwd { + cmd.current_dir(dir); + }; + cmd.args(args); + cmd = cmd.allow_failure(); + cmd.run_always(); + let output = self.run_cmd(cmd, OutputMode::Capture, OutputMode::Capture)?; + Ok(output) + } + + pub fn git_command_status_for_diff_index( + &mut self, + cwd: Option<&Path>, + base: &str, + paths: &[&str], + ) -> Result { + let program = Path::new("git"); + let mut cmd = BootstrapCommand::new(program); + if let Some(dir) = cwd { + cmd.current_dir(dir); + }; + cmd.args(["diff-index", "--quiet", base, "--"]).args(paths); + cmd = cmd.allow_failure(); + cmd.run_always(); + let output = self.run_cmd(cmd, OutputMode::Print, OutputMode::Print)?; + + Ok(!output.is_success()) + } +} diff --git a/src/bootstrap/src/utils/exec.rs b/src/bootstrap/src/utils/exec.rs index d07300e21d003..e305817cead2f 100644 --- a/src/bootstrap/src/utils/exec.rs +++ b/src/bootstrap/src/utils/exec.rs @@ -14,7 +14,7 @@ use build_helper::drop_bomb::DropBomb; use crate::Build; /// What should be done when the command fails. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum BehaviorOnFailure { /// Immediately stop bootstrap. Exit, @@ -125,7 +125,6 @@ impl BootstrapCommand { Self { failure_behavior: BehaviorOnFailure::DelayFail, ..self } } - #[expect(dead_code)] pub fn fail_fast(self) -> Self { Self { failure_behavior: BehaviorOnFailure::Exit, ..self } } @@ -213,6 +212,7 @@ impl From for BootstrapCommand { } } +#[derive(Clone)] /// Represents the current status of `BootstrapCommand`. enum CommandStatus { /// The command has started and finished with some status. @@ -229,6 +229,7 @@ pub fn command>(program: S) -> BootstrapCommand { BootstrapCommand::new(program) } +#[derive(Clone)] /// Represents the output of an executed process. pub struct CommandOutput { status: CommandStatus, @@ -280,7 +281,6 @@ impl CommandOutput { !self.is_success() } - #[expect(dead_code)] pub fn status(&self) -> Option { match self.status { CommandStatus::Finished(status) => Some(status), diff --git a/src/bootstrap/src/utils/mod.rs b/src/bootstrap/src/utils/mod.rs index 169fcec303e90..fea8dbee266a2 100644 --- a/src/bootstrap/src/utils/mod.rs +++ b/src/bootstrap/src/utils/mod.rs @@ -7,13 +7,13 @@ pub(crate) mod cache; pub(crate) mod cc_detect; pub(crate) mod change_tracker; pub(crate) mod channel; +pub(crate) mod context; pub(crate) mod exec; pub(crate) mod helpers; pub(crate) mod job; pub(crate) mod render_tests; pub(crate) mod shared_helpers; pub(crate) mod tarball; - pub(crate) mod tracing; #[cfg(feature = "build-metrics")]