Skip to content

Commit b9a578e

Browse files
authored
Rollup merge of #141909 - Shourya742:2025-06-01-add-execution-context, r=Kobzol
Add central execution context to bootstrap This PR continues the effort toward command centralization as outlined in #126819. It introduces a centralized execution context through which all commands will be executed. Previously, centralization was limited to build methods; this PR extends it to the `config` module and updates the remaining methods accordingly. Best reviewed commit by commit. r? ``@Kobzol``
2 parents 04025fa + 51fbd14 commit b9a578e

File tree

14 files changed

+364
-272
lines changed

14 files changed

+364
-272
lines changed

src/bootstrap/src/core/build_steps/tool.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ use crate::core::builder::{
2323
Builder, Cargo as CargoCommand, RunConfig, ShouldRun, Step, cargo_profile_var,
2424
};
2525
use crate::core::config::{DebuginfoLevel, RustcLto, TargetSelection};
26-
use crate::utils::channel::GitInfo;
2726
use crate::utils::exec::{BootstrapCommand, command};
2827
use crate::utils::helpers::{add_dylib_path, exe, t};
2928
use crate::{Compiler, FileType, Kind, Mode, gha};
@@ -278,7 +277,7 @@ pub fn prepare_tool_cargo(
278277
cargo.env("CFG_VER_DESCRIPTION", description);
279278
}
280279

281-
let info = GitInfo::new(builder.config.omit_git_hash, &dir);
280+
let info = builder.config.git_info(builder.config.omit_git_hash, &dir);
282281
if let Some(sha) = info.sha() {
283282
cargo.env("CFG_COMMIT_HASH", sha);
284283
}

src/bootstrap/src/core/builder/cargo.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ impl Builder<'_> {
551551
let libdir = self.rustc_libdir(compiler);
552552

553553
let sysroot_str = sysroot.as_os_str().to_str().expect("sysroot should be UTF-8");
554-
if self.is_verbose() && !matches!(self.config.dry_run, DryRun::SelfCheck) {
554+
if self.is_verbose() && !matches!(self.config.get_dry_run(), DryRun::SelfCheck) {
555555
println!("using sysroot {sysroot_str}");
556556
}
557557

src/bootstrap/src/core/builder/mod.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use crate::core::config::flags::Subcommand;
2222
use crate::core::config::{DryRun, TargetSelection};
2323
use crate::utils::cache::Cache;
2424
use crate::utils::exec::{BootstrapCommand, command};
25+
use crate::utils::execution_context::ExecutionContext;
2526
use crate::utils::helpers::{self, LldThreads, add_dylib_path, exe, libdir, linker_args, t};
2627
use crate::{Build, Crate, trace};
2728

@@ -442,13 +443,15 @@ impl StepDescription {
442443

443444
fn is_excluded(&self, builder: &Builder<'_>, pathset: &PathSet) -> bool {
444445
if builder.config.skip.iter().any(|e| pathset.has(e, builder.kind)) {
445-
if !matches!(builder.config.dry_run, DryRun::SelfCheck) {
446+
if !matches!(builder.config.get_dry_run(), DryRun::SelfCheck) {
446447
println!("Skipping {pathset:?} because it is excluded");
447448
}
448449
return true;
449450
}
450451

451-
if !builder.config.skip.is_empty() && !matches!(builder.config.dry_run, DryRun::SelfCheck) {
452+
if !builder.config.skip.is_empty()
453+
&& !matches!(builder.config.get_dry_run(), DryRun::SelfCheck)
454+
{
452455
builder.verbose(|| {
453456
println!(
454457
"{:?} not skipped for {:?} -- not in {:?}",
@@ -1633,4 +1636,14 @@ impl<'a> Builder<'a> {
16331636
self.info(&format!("{err}\n"));
16341637
}
16351638
}
1639+
1640+
pub fn exec_ctx(&self) -> &ExecutionContext {
1641+
&self.config.exec_ctx
1642+
}
1643+
}
1644+
1645+
impl<'a> AsRef<ExecutionContext> for Builder<'a> {
1646+
fn as_ref(&self) -> &ExecutionContext {
1647+
self.exec_ctx()
1648+
}
16361649
}

src/bootstrap/src/core/builder/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fn configure_with_args(cmd: &[String], host: &[&str], target: &[&str]) -> Config
2222
let mut config = Config::parse(Flags::parse(cmd));
2323
// don't save toolstates
2424
config.save_toolstates = None;
25-
config.dry_run = DryRun::SelfCheck;
25+
config.set_dry_run(DryRun::SelfCheck);
2626

2727
// Ignore most submodules, since we don't need them for a dry run, and the
2828
// tests run much faster without them.

src/bootstrap/src/core/config/config.rs

Lines changed: 76 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use std::{cmp, env, fs};
2424

2525
use build_helper::ci::CiEnv;
2626
use build_helper::exit;
27-
use build_helper::git::{GitConfig, PathFreshness, check_path_modifications, output_result};
27+
use build_helper::git::{GitConfig, PathFreshness, check_path_modifications};
2828
use serde::Deserialize;
2929
#[cfg(feature = "tracing")]
3030
use tracing::{instrument, span};
@@ -47,8 +47,10 @@ use crate::core::config::{
4747
};
4848
use crate::core::download::is_download_ci_available;
4949
use crate::utils::channel;
50+
use crate::utils::exec::command;
51+
use crate::utils::execution_context::ExecutionContext;
5052
use crate::utils::helpers::exe;
51-
use crate::{Command, GitInfo, OnceLock, TargetSelection, check_ci_llvm, helpers, output, t};
53+
use crate::{GitInfo, OnceLock, TargetSelection, check_ci_llvm, helpers, t};
5254

5355
/// Each path from this function is considered "allowed" in the `download-rustc="if-unchanged"` logic.
5456
/// This means they can be modified and changes to these paths should never trigger a compiler build
@@ -142,7 +144,6 @@ pub struct Config {
142144
pub jobs: Option<u32>,
143145
pub cmd: Subcommand,
144146
pub incremental: bool,
145-
pub dry_run: DryRun,
146147
pub dump_bootstrap_shims: bool,
147148
/// Arguments appearing after `--` to be forwarded to tools,
148149
/// e.g. `--fix-broken` or test arguments.
@@ -317,6 +318,8 @@ pub struct Config {
317318
/// This is mostly for RA as building the stage1 compiler to check the library tree
318319
/// on each code change might be too much for some computers.
319320
pub skip_std_check_if_no_download_rustc: bool,
321+
322+
pub exec_ctx: ExecutionContext,
320323
}
321324

322325
impl Config {
@@ -373,6 +376,14 @@ impl Config {
373376
}
374377
}
375378

379+
pub fn set_dry_run(&mut self, dry_run: DryRun) {
380+
self.exec_ctx.set_dry_run(dry_run);
381+
}
382+
383+
pub fn get_dry_run(&self) -> &DryRun {
384+
self.exec_ctx.get_dry_run()
385+
}
386+
376387
#[cfg_attr(
377388
feature = "tracing",
378389
instrument(target = "CONFIG_HANDLING", level = "trace", name = "Config::parse", skip_all)
@@ -395,6 +406,11 @@ impl Config {
395406
get_toml: impl Fn(&Path) -> Result<TomlConfig, toml::de::Error>,
396407
) -> Config {
397408
let mut config = Config::default_opts();
409+
let mut exec_ctx = ExecutionContext::new();
410+
exec_ctx.set_verbose(flags.verbose);
411+
exec_ctx.set_fail_fast(flags.cmd.fail_fast());
412+
413+
config.exec_ctx = exec_ctx;
398414

399415
// Set flags.
400416
config.paths = std::mem::take(&mut flags.paths);
@@ -423,7 +439,7 @@ impl Config {
423439
config.on_fail = flags.on_fail;
424440
config.cmd = flags.cmd;
425441
config.incremental = flags.incremental;
426-
config.dry_run = if flags.dry_run { DryRun::UserSelected } else { DryRun::Disabled };
442+
config.set_dry_run(if flags.dry_run { DryRun::UserSelected } else { DryRun::Disabled });
427443
config.dump_bootstrap_shims = flags.dump_bootstrap_shims;
428444
config.keep_stage = flags.keep_stage;
429445
config.keep_stage_std = flags.keep_stage_std;
@@ -453,14 +469,9 @@ impl Config {
453469
// has already been (kinda-cross-)compiled to Windows land, we require a normal Windows path.
454470
cmd.arg("rev-parse").arg("--show-cdup");
455471
// Discard stderr because we expect this to fail when building from a tarball.
456-
let output = cmd
457-
.as_command_mut()
458-
.stderr(std::process::Stdio::null())
459-
.output()
460-
.ok()
461-
.and_then(|output| if output.status.success() { Some(output) } else { None });
462-
if let Some(output) = output {
463-
let git_root_relative = String::from_utf8(output.stdout).unwrap();
472+
let output = cmd.allow_failure().run_capture_stdout(&config);
473+
if output.is_success() {
474+
let git_root_relative = output.stdout();
464475
// We need to canonicalize this path to make sure it uses backslashes instead of forward slashes,
465476
// and to resolve any relative components.
466477
let git_root = env::current_dir()
@@ -555,7 +566,7 @@ impl Config {
555566
build.cargo = build.cargo.take().or(std::env::var_os("CARGO").map(|p| p.into()));
556567
}
557568

558-
if GitInfo::new(false, &config.src).is_from_tarball() && toml.profile.is_none() {
569+
if config.git_info(false, &config.src).is_from_tarball() && toml.profile.is_none() {
559570
toml.profile = Some("dist".into());
560571
}
561572

@@ -762,7 +773,12 @@ impl Config {
762773
};
763774

764775
config.initial_sysroot = t!(PathBuf::from_str(
765-
output(Command::new(&config.initial_rustc).args(["--print", "sysroot"])).trim()
776+
command(&config.initial_rustc)
777+
.args(["--print", "sysroot"])
778+
.run_always()
779+
.run_capture_stdout(&config)
780+
.stdout()
781+
.trim()
766782
));
767783

768784
config.initial_cargo_clippy = cargo_clippy;
@@ -858,19 +874,21 @@ impl Config {
858874
let default = config.channel == "dev";
859875
config.omit_git_hash = toml.rust.as_ref().and_then(|r| r.omit_git_hash).unwrap_or(default);
860876

861-
config.rust_info = GitInfo::new(config.omit_git_hash, &config.src);
862-
config.cargo_info = GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/cargo"));
877+
config.rust_info = config.git_info(config.omit_git_hash, &config.src);
878+
config.cargo_info =
879+
config.git_info(config.omit_git_hash, &config.src.join("src/tools/cargo"));
863880
config.rust_analyzer_info =
864-
GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/rust-analyzer"));
881+
config.git_info(config.omit_git_hash, &config.src.join("src/tools/rust-analyzer"));
865882
config.clippy_info =
866-
GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/clippy"));
867-
config.miri_info = GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/miri"));
883+
config.git_info(config.omit_git_hash, &config.src.join("src/tools/clippy"));
884+
config.miri_info =
885+
config.git_info(config.omit_git_hash, &config.src.join("src/tools/miri"));
868886
config.rustfmt_info =
869-
GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/rustfmt"));
887+
config.git_info(config.omit_git_hash, &config.src.join("src/tools/rustfmt"));
870888
config.enzyme_info =
871-
GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/enzyme"));
872-
config.in_tree_llvm_info = GitInfo::new(false, &config.src.join("src/llvm-project"));
873-
config.in_tree_gcc_info = GitInfo::new(false, &config.src.join("src/gcc"));
889+
config.git_info(config.omit_git_hash, &config.src.join("src/tools/enzyme"));
890+
config.in_tree_llvm_info = config.git_info(false, &config.src.join("src/llvm-project"));
891+
config.in_tree_gcc_info = config.git_info(false, &config.src.join("src/gcc"));
874892

875893
config.vendor = vendor.unwrap_or(
876894
config.rust_info.is_from_tarball()
@@ -1030,28 +1048,13 @@ impl Config {
10301048
}
10311049

10321050
pub fn dry_run(&self) -> bool {
1033-
match self.dry_run {
1034-
DryRun::Disabled => false,
1035-
DryRun::SelfCheck | DryRun::UserSelected => true,
1036-
}
1051+
self.exec_ctx.dry_run()
10371052
}
10381053

10391054
pub fn is_explicit_stage(&self) -> bool {
10401055
self.explicit_stage_from_cli || self.explicit_stage_from_config
10411056
}
10421057

1043-
/// Runs a command, printing out nice contextual information if it fails.
1044-
/// Exits if the command failed to execute at all, otherwise returns its
1045-
/// `status.success()`.
1046-
#[deprecated = "use `Builder::try_run` instead where possible"]
1047-
pub(crate) fn try_run(&self, cmd: &mut Command) -> Result<(), ()> {
1048-
if self.dry_run() {
1049-
return Ok(());
1050-
}
1051-
self.verbose(|| println!("running: {cmd:?}"));
1052-
build_helper::util::try_run(cmd, self.is_verbose())
1053-
}
1054-
10551058
pub(crate) fn test_args(&self) -> Vec<&str> {
10561059
let mut test_args = match self.cmd {
10571060
Subcommand::Test { ref test_args, .. }
@@ -1085,7 +1088,7 @@ impl Config {
10851088

10861089
let mut git = helpers::git(Some(&self.src));
10871090
git.arg("show").arg(format!("{commit}:{}", file.to_str().unwrap()));
1088-
output(git.as_command_mut())
1091+
git.run_capture_stdout(self).stdout()
10891092
}
10901093

10911094
/// Bootstrap embeds a version number into the name of shared libraries it uploads in CI.
@@ -1273,9 +1276,7 @@ impl Config {
12731276

12741277
/// Runs a function if verbosity is greater than 0
12751278
pub fn verbose(&self, f: impl Fn()) {
1276-
if self.is_verbose() {
1277-
f()
1278-
}
1279+
self.exec_ctx.verbose(f);
12791280
}
12801281

12811282
pub fn any_sanitizers_to_build(&self) -> bool {
@@ -1337,7 +1338,7 @@ impl Config {
13371338

13381339
// NOTE: The check for the empty directory is here because when running x.py the first time,
13391340
// the submodule won't be checked out. Check it out now so we can build it.
1340-
if !GitInfo::new(false, &absolute_path).is_managed_git_subrepository()
1341+
if !self.git_info(false, &absolute_path).is_managed_git_subrepository()
13411342
&& !helpers::dir_is_empty(&absolute_path)
13421343
{
13431344
return;
@@ -1356,16 +1357,16 @@ impl Config {
13561357
};
13571358

13581359
// Determine commit checked out in submodule.
1359-
let checked_out_hash = output(submodule_git().args(["rev-parse", "HEAD"]).as_command_mut());
1360+
let checked_out_hash =
1361+
submodule_git().args(["rev-parse", "HEAD"]).run_capture_stdout(self).stdout();
13601362
let checked_out_hash = checked_out_hash.trim_end();
13611363
// Determine commit that the submodule *should* have.
1362-
let recorded = output(
1363-
helpers::git(Some(&self.src))
1364-
.run_always()
1365-
.args(["ls-tree", "HEAD"])
1366-
.arg(relative_path)
1367-
.as_command_mut(),
1368-
);
1364+
let recorded = helpers::git(Some(&self.src))
1365+
.run_always()
1366+
.args(["ls-tree", "HEAD"])
1367+
.arg(relative_path)
1368+
.run_capture_stdout(self)
1369+
.stdout();
13691370

13701371
let actual_hash = recorded
13711372
.split_whitespace()
@@ -1389,20 +1390,18 @@ impl Config {
13891390
let update = |progress: bool| {
13901391
// Git is buggy and will try to fetch submodules from the tracking branch for *this* repository,
13911392
// even though that has no relation to the upstream for the submodule.
1392-
let current_branch = output_result(
1393-
helpers::git(Some(&self.src))
1394-
.allow_failure()
1395-
.run_always()
1396-
.args(["symbolic-ref", "--short", "HEAD"])
1397-
.as_command_mut(),
1398-
)
1399-
.map(|b| b.trim().to_owned());
1393+
let current_branch = helpers::git(Some(&self.src))
1394+
.allow_failure()
1395+
.run_always()
1396+
.args(["symbolic-ref", "--short", "HEAD"])
1397+
.run_capture(self);
14001398

14011399
let mut git = helpers::git(Some(&self.src)).allow_failure();
14021400
git.run_always();
1403-
if let Ok(branch) = current_branch {
1401+
if current_branch.is_success() {
14041402
// If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name.
14051403
// This syntax isn't accepted by `branch.{branch}`. Strip it.
1404+
let branch = current_branch.stdout();
14061405
let branch = branch.strip_prefix("heads/").unwrap_or(&branch);
14071406
git.arg("-c").arg(format!("branch.{branch}.remote=origin"));
14081407
}
@@ -1448,7 +1447,8 @@ impl Config {
14481447
return;
14491448
}
14501449

1451-
let stage0_output = output(Command::new(program_path).arg("--version"));
1450+
let stage0_output =
1451+
command(program_path).arg("--version").run_capture_stdout(self).stdout();
14521452
let mut stage0_output = stage0_output.lines().next().unwrap().split(' ');
14531453

14541454
let stage0_name = stage0_output.next().unwrap();
@@ -1754,4 +1754,18 @@ impl Config {
17541754
_ => !self.is_system_llvm(target),
17551755
}
17561756
}
1757+
1758+
pub fn exec_ctx(&self) -> &ExecutionContext {
1759+
&self.exec_ctx
1760+
}
1761+
1762+
pub fn git_info(&self, omit_git_hash: bool, dir: &Path) -> GitInfo {
1763+
GitInfo::new(omit_git_hash, dir, self)
1764+
}
1765+
}
1766+
1767+
impl AsRef<ExecutionContext> for Config {
1768+
fn as_ref(&self) -> &ExecutionContext {
1769+
&self.exec_ctx
1770+
}
17571771
}

src/bootstrap/src/core/config/flags.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,8 @@ impl Flags {
209209
HelpVerboseOnly::try_parse_from(normalize_args(args))
210210
{
211211
println!("NOTE: updating submodules before printing available paths");
212-
let config = Config::parse(Self::parse(&[String::from("build")]));
212+
let flags = Self::parse(&[String::from("build")]);
213+
let config = Config::parse(flags);
213214
let build = Build::new(config);
214215
let paths = Builder::get_help(&build, subcommand);
215216
if let Some(s) = paths {

0 commit comments

Comments
 (0)