Skip to content

2025 05 12 add execution context #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rustbook
Submodule rustbook added at 62d261
4 changes: 2 additions & 2 deletions src/bootstrap/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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`");
Expand Down
16 changes: 10 additions & 6 deletions src/bootstrap/src/core/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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(
Expand All @@ -1478,6 +1481,7 @@ impl Config {
pub(crate) fn parse_inner(
mut flags: Flags,
get_toml: impl Fn(&Path) -> Result<TomlConfig, toml::de::Error>,
exec_context: &mut ExecutionContext,
) -> Config {
let mut config = Config::default_opts();

Expand Down Expand Up @@ -1600,19 +1604,19 @@ 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"));
}
}
}

// 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()
Expand Down
4 changes: 2 additions & 2 deletions src/bootstrap/src/core/config/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}");
Expand Down
3 changes: 2 additions & 1 deletion src/bootstrap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();

Expand Down
254 changes: 254 additions & 0 deletions src/bootstrap/src/utils/context.rs
Original file line number Diff line number Diff line change
@@ -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<HashMap<(PathBuf, Vec<Vec<u8>>, Option<PathBuf>), Result<CommandOutput, String>>>,
file_contents_cache: Mutex<HashMap<PathBuf, std::io::Result<String>>>,
path_exist_cache: Mutex<HashMap<PathBuf, bool>>,
path_modifications_cache: Mutex<HashMap<(PathBuf, Interned<String>), 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<CommandOutput, String> {
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<CommandOutput, String> {
let command_key = {
let command = cmd.as_command_mut();
let key_program = PathBuf::from(command.get_program());
let key_args: Vec<Vec<u8>> =
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<CommandOutput, String> {
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<bool, String> {
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())
}
}
6 changes: 3 additions & 3 deletions src/bootstrap/src/utils/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 }
}
Expand Down Expand Up @@ -213,6 +212,7 @@ impl From<Command> for BootstrapCommand {
}
}

#[derive(Clone)]
/// Represents the current status of `BootstrapCommand`.
enum CommandStatus {
/// The command has started and finished with some status.
Expand All @@ -229,6 +229,7 @@ pub fn command<S: AsRef<OsStr>>(program: S) -> BootstrapCommand {
BootstrapCommand::new(program)
}

#[derive(Clone)]
/// Represents the output of an executed process.
pub struct CommandOutput {
status: CommandStatus,
Expand Down Expand Up @@ -280,7 +281,6 @@ impl CommandOutput {
!self.is_success()
}

#[expect(dead_code)]
pub fn status(&self) -> Option<ExitStatus> {
match self.status {
CommandStatus::Finished(status) => Some(status),
Expand Down
Loading
Loading