bootstrap/core/config/
config.rs

1//! This module defines the central `Config` struct, which aggregates all components
2//! of the bootstrap configuration into a single unit.
3//!
4//! It serves as the primary public interface for accessing the bootstrap configuration.
5//! The module coordinates the overall configuration parsing process using logic from `parsing.rs`
6//! and provides top-level methods such as `Config::parse()` for initialization, as well as
7//! utility methods for querying and manipulating the complete configuration state.
8//!
9//! Additionally, this module contains the core logic for parsing, validating, and inferring
10//! the final `Config` from various raw inputs.
11//!
12//! It manages the process of reading command-line arguments, environment variables,
13//! and the `bootstrap.toml` file—merging them, applying defaults, and performing
14//! cross-component validation. The main `parse_inner` function and its supporting
15//! helpers reside here, transforming raw `Toml` data into the structured `Config` type.
16
17use std::cell::Cell;
18use std::collections::{BTreeSet, HashMap, HashSet};
19use std::io::IsTerminal;
20use std::path::{Path, PathBuf, absolute};
21use std::str::FromStr;
22use std::sync::{Arc, Mutex};
23use std::{cmp, env, fs};
24
25use build_helper::ci::CiEnv;
26use build_helper::exit;
27use build_helper::git::{GitConfig, PathFreshness, check_path_modifications};
28use serde::Deserialize;
29#[cfg(feature = "tracing")]
30use tracing::{instrument, span};
31
32use crate::core::build_steps::llvm;
33use crate::core::build_steps::llvm::LLVM_INVALIDATION_PATHS;
34pub use crate::core::config::flags::Subcommand;
35use crate::core::config::flags::{Color, Flags};
36use crate::core::config::target_selection::TargetSelectionList;
37use crate::core::config::toml::TomlConfig;
38use crate::core::config::toml::build::{Build, Tool};
39use crate::core::config::toml::change_id::ChangeId;
40use crate::core::config::toml::rust::{
41    LldMode, RustOptimize, check_incompatible_options_for_ci_rustc,
42};
43use crate::core::config::toml::target::Target;
44use crate::core::config::{
45    DebuginfoLevel, DryRun, GccCiMode, LlvmLibunwind, Merge, ReplaceOpt, RustcLto, SplitDebuginfo,
46    StringOrBool, set, threads_from_config,
47};
48use crate::core::download::{
49    DownloadContext, download_beta_toolchain, is_download_ci_available, maybe_download_rustfmt,
50};
51use crate::utils::channel;
52use crate::utils::exec::{ExecutionContext, command};
53use crate::utils::helpers::{exe, get_host_target};
54use crate::{CodegenBackendKind, GitInfo, OnceLock, TargetSelection, check_ci_llvm, helpers, t};
55
56/// Each path in this list is considered "allowed" in the `download-rustc="if-unchanged"` logic.
57/// This means they can be modified and changes to these paths should never trigger a compiler build
58/// when "if-unchanged" is set.
59///
60/// NOTE: Paths must have the ":!" prefix to tell git to ignore changes in those paths during
61/// the diff check.
62///
63/// WARNING: Be cautious when adding paths to this list. If a path that influences the compiler build
64/// is added here, it will cause bootstrap to skip necessary rebuilds, which may lead to risky results.
65/// For example, "src/bootstrap" should never be included in this list as it plays a crucial role in the
66/// final output/compiler, which can be significantly affected by changes made to the bootstrap sources.
67#[rustfmt::skip] // We don't want rustfmt to oneline this list
68pub const RUSTC_IF_UNCHANGED_ALLOWED_PATHS: &[&str] = &[
69    ":!library",
70    ":!src/tools",
71    ":!src/librustdoc",
72    ":!src/rustdoc-json-types",
73    ":!tests",
74    ":!triagebot.toml",
75];
76
77/// Global configuration for the entire build and/or bootstrap.
78///
79/// This structure is parsed from `bootstrap.toml`, and some of the fields are inferred from `git` or build-time parameters.
80///
81/// Note that this structure is not decoded directly into, but rather it is
82/// filled out from the decoded forms of the structs below. For documentation
83/// on each field, see the corresponding fields in
84/// `bootstrap.example.toml`.
85#[derive(Default, Clone)]
86pub struct Config {
87    pub change_id: Option<ChangeId>,
88    pub bypass_bootstrap_lock: bool,
89    pub ccache: Option<String>,
90    /// Call Build::ninja() instead of this.
91    pub ninja_in_file: bool,
92    pub verbose: usize,
93    pub submodules: Option<bool>,
94    pub compiler_docs: bool,
95    pub library_docs_private_items: bool,
96    pub docs_minification: bool,
97    pub docs: bool,
98    pub locked_deps: bool,
99    pub vendor: bool,
100    pub target_config: HashMap<TargetSelection, Target>,
101    pub full_bootstrap: bool,
102    pub bootstrap_cache_path: Option<PathBuf>,
103    pub extended: bool,
104    pub tools: Option<HashSet<String>>,
105    /// Specify build configuration specific for some tool, such as enabled features, see [Tool].
106    /// The key in the map is the name of the tool, and the value is tool-specific configuration.
107    pub tool: HashMap<String, Tool>,
108    pub sanitizers: bool,
109    pub profiler: bool,
110    pub omit_git_hash: bool,
111    pub skip: Vec<PathBuf>,
112    pub include_default_paths: bool,
113    pub rustc_error_format: Option<String>,
114    pub json_output: bool,
115    pub compile_time_deps: bool,
116    pub test_compare_mode: bool,
117    pub color: Color,
118    pub patch_binaries_for_nix: Option<bool>,
119    pub stage0_metadata: build_helper::stage0_parser::Stage0,
120    pub android_ndk: Option<PathBuf>,
121    /// Whether to use the `c` feature of the `compiler_builtins` crate.
122    pub optimized_compiler_builtins: bool,
123
124    pub stdout_is_tty: bool,
125    pub stderr_is_tty: bool,
126
127    pub on_fail: Option<String>,
128    pub explicit_stage_from_cli: bool,
129    pub explicit_stage_from_config: bool,
130    pub stage: u32,
131    pub keep_stage: Vec<u32>,
132    pub keep_stage_std: Vec<u32>,
133    pub src: PathBuf,
134    /// defaults to `bootstrap.toml`
135    pub config: Option<PathBuf>,
136    pub jobs: Option<u32>,
137    pub cmd: Subcommand,
138    pub incremental: bool,
139    pub dump_bootstrap_shims: bool,
140    /// Arguments appearing after `--` to be forwarded to tools,
141    /// e.g. `--fix-broken` or test arguments.
142    pub free_args: Vec<String>,
143
144    /// `None` if we shouldn't download CI compiler artifacts, or the commit to download if we should.
145    pub download_rustc_commit: Option<String>,
146
147    pub deny_warnings: bool,
148    pub backtrace_on_ice: bool,
149
150    // llvm codegen options
151    pub llvm_assertions: bool,
152    pub llvm_tests: bool,
153    pub llvm_enzyme: bool,
154    pub llvm_offload: bool,
155    pub llvm_plugins: bool,
156    pub llvm_optimize: bool,
157    pub llvm_thin_lto: bool,
158    pub llvm_release_debuginfo: bool,
159    pub llvm_static_stdcpp: bool,
160    pub llvm_libzstd: bool,
161    pub llvm_link_shared: Cell<Option<bool>>,
162    pub llvm_clang_cl: Option<String>,
163    pub llvm_targets: Option<String>,
164    pub llvm_experimental_targets: Option<String>,
165    pub llvm_link_jobs: Option<u32>,
166    pub llvm_version_suffix: Option<String>,
167    pub llvm_use_linker: Option<String>,
168    pub llvm_allow_old_toolchain: bool,
169    pub llvm_polly: bool,
170    pub llvm_clang: bool,
171    pub llvm_enable_warnings: bool,
172    pub llvm_from_ci: bool,
173    pub llvm_build_config: HashMap<String, String>,
174
175    pub lld_mode: LldMode,
176    pub lld_enabled: bool,
177    pub llvm_tools_enabled: bool,
178    pub llvm_bitcode_linker_enabled: bool,
179
180    pub llvm_cflags: Option<String>,
181    pub llvm_cxxflags: Option<String>,
182    pub llvm_ldflags: Option<String>,
183    pub llvm_use_libcxx: bool,
184
185    // gcc codegen options
186    pub gcc_ci_mode: GccCiMode,
187
188    // rust codegen options
189    pub rust_optimize: RustOptimize,
190    pub rust_codegen_units: Option<u32>,
191    pub rust_codegen_units_std: Option<u32>,
192
193    pub rustc_debug_assertions: bool,
194    pub std_debug_assertions: bool,
195    pub tools_debug_assertions: bool,
196
197    pub rust_overflow_checks: bool,
198    pub rust_overflow_checks_std: bool,
199    pub rust_debug_logging: bool,
200    pub rust_debuginfo_level_rustc: DebuginfoLevel,
201    pub rust_debuginfo_level_std: DebuginfoLevel,
202    pub rust_debuginfo_level_tools: DebuginfoLevel,
203    pub rust_debuginfo_level_tests: DebuginfoLevel,
204    pub rust_rpath: bool,
205    pub rust_strip: bool,
206    pub rust_frame_pointers: bool,
207    pub rust_stack_protector: Option<String>,
208    pub rustc_default_linker: Option<String>,
209    pub rust_optimize_tests: bool,
210    pub rust_dist_src: bool,
211    pub rust_codegen_backends: Vec<CodegenBackendKind>,
212    pub rust_verify_llvm_ir: bool,
213    pub rust_thin_lto_import_instr_limit: Option<u32>,
214    pub rust_randomize_layout: bool,
215    pub rust_remap_debuginfo: bool,
216    pub rust_new_symbol_mangling: Option<bool>,
217    pub rust_profile_use: Option<String>,
218    pub rust_profile_generate: Option<String>,
219    pub rust_lto: RustcLto,
220    pub rust_validate_mir_opts: Option<u32>,
221    pub rust_std_features: BTreeSet<String>,
222    pub llvm_profile_use: Option<String>,
223    pub llvm_profile_generate: bool,
224    pub llvm_libunwind_default: Option<LlvmLibunwind>,
225    pub enable_bolt_settings: bool,
226
227    pub reproducible_artifacts: Vec<String>,
228
229    pub host_target: TargetSelection,
230    pub hosts: Vec<TargetSelection>,
231    pub targets: Vec<TargetSelection>,
232    pub local_rebuild: bool,
233    pub jemalloc: bool,
234    pub control_flow_guard: bool,
235    pub ehcont_guard: bool,
236
237    // dist misc
238    pub dist_sign_folder: Option<PathBuf>,
239    pub dist_upload_addr: Option<String>,
240    pub dist_compression_formats: Option<Vec<String>>,
241    pub dist_compression_profile: String,
242    pub dist_include_mingw_linker: bool,
243    pub dist_vendor: bool,
244
245    // libstd features
246    pub backtrace: bool, // support for RUST_BACKTRACE
247
248    // misc
249    pub low_priority: bool,
250    pub channel: String,
251    pub description: Option<String>,
252    pub verbose_tests: bool,
253    pub save_toolstates: Option<PathBuf>,
254    pub print_step_timings: bool,
255    pub print_step_rusage: bool,
256
257    // Fallback musl-root for all targets
258    pub musl_root: Option<PathBuf>,
259    pub prefix: Option<PathBuf>,
260    pub sysconfdir: Option<PathBuf>,
261    pub datadir: Option<PathBuf>,
262    pub docdir: Option<PathBuf>,
263    pub bindir: PathBuf,
264    pub libdir: Option<PathBuf>,
265    pub mandir: Option<PathBuf>,
266    pub codegen_tests: bool,
267    pub nodejs: Option<PathBuf>,
268    pub npm: Option<PathBuf>,
269    pub gdb: Option<PathBuf>,
270    pub lldb: Option<PathBuf>,
271    pub python: Option<PathBuf>,
272    pub reuse: Option<PathBuf>,
273    pub cargo_native_static: bool,
274    pub configure_args: Vec<String>,
275    pub out: PathBuf,
276    pub rust_info: channel::GitInfo,
277
278    pub cargo_info: channel::GitInfo,
279    pub rust_analyzer_info: channel::GitInfo,
280    pub clippy_info: channel::GitInfo,
281    pub miri_info: channel::GitInfo,
282    pub rustfmt_info: channel::GitInfo,
283    pub enzyme_info: channel::GitInfo,
284    pub in_tree_llvm_info: channel::GitInfo,
285    pub in_tree_gcc_info: channel::GitInfo,
286
287    // These are either the stage0 downloaded binaries or the locally installed ones.
288    pub initial_cargo: PathBuf,
289    pub initial_rustc: PathBuf,
290    pub initial_cargo_clippy: Option<PathBuf>,
291    pub initial_sysroot: PathBuf,
292    pub initial_rustfmt: Option<PathBuf>,
293
294    /// The paths to work with. For example: with `./x check foo bar` we get
295    /// `paths=["foo", "bar"]`.
296    pub paths: Vec<PathBuf>,
297
298    /// Command for visual diff display, e.g. `diff-tool --color=always`.
299    pub compiletest_diff_tool: Option<String>,
300
301    /// Whether to allow running both `compiletest` self-tests and `compiletest`-managed test suites
302    /// against the stage 0 (rustc, std).
303    ///
304    /// This is only intended to be used when the stage 0 compiler is actually built from in-tree
305    /// sources.
306    pub compiletest_allow_stage0: bool,
307
308    /// Whether to use the precompiled stage0 libtest with compiletest.
309    pub compiletest_use_stage0_libtest: bool,
310
311    /// Default value for `--extra-checks`
312    pub tidy_extra_checks: Option<String>,
313    pub is_running_on_ci: bool,
314
315    /// Cache for determining path modifications
316    pub path_modification_cache: Arc<Mutex<HashMap<Vec<&'static str>, PathFreshness>>>,
317
318    /// Skip checking the standard library if `rust.download-rustc` isn't available.
319    /// This is mostly for RA as building the stage1 compiler to check the library tree
320    /// on each code change might be too much for some computers.
321    pub skip_std_check_if_no_download_rustc: bool,
322
323    pub exec_ctx: ExecutionContext,
324}
325
326impl Config {
327    #[cfg_attr(
328        feature = "tracing",
329        instrument(target = "CONFIG_HANDLING", level = "trace", name = "Config::default_opts")
330    )]
331    pub fn default_opts() -> Config {
332        #[cfg(feature = "tracing")]
333        span!(target: "CONFIG_HANDLING", tracing::Level::TRACE, "constructing default config");
334
335        Config {
336            bypass_bootstrap_lock: false,
337            llvm_optimize: true,
338            ninja_in_file: true,
339            llvm_static_stdcpp: false,
340            llvm_libzstd: false,
341            backtrace: true,
342            rust_optimize: RustOptimize::Bool(true),
343            rust_optimize_tests: true,
344            rust_randomize_layout: false,
345            submodules: None,
346            docs: true,
347            docs_minification: true,
348            rust_rpath: true,
349            rust_strip: false,
350            channel: "dev".to_string(),
351            codegen_tests: true,
352            rust_dist_src: true,
353            rust_codegen_backends: vec![CodegenBackendKind::Llvm],
354            deny_warnings: true,
355            bindir: "bin".into(),
356            dist_include_mingw_linker: true,
357            dist_compression_profile: "fast".into(),
358
359            stdout_is_tty: std::io::stdout().is_terminal(),
360            stderr_is_tty: std::io::stderr().is_terminal(),
361
362            // set by build.rs
363            host_target: get_host_target(),
364
365            src: {
366                let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
367                // Undo `src/bootstrap`
368                manifest_dir.parent().unwrap().parent().unwrap().to_owned()
369            },
370            out: PathBuf::from("build"),
371
372            // This is needed by codegen_ssa on macOS to ship `llvm-objcopy` aliased to
373            // `rust-objcopy` to workaround bad `strip`s on macOS.
374            llvm_tools_enabled: true,
375
376            ..Default::default()
377        }
378    }
379
380    pub fn set_dry_run(&mut self, dry_run: DryRun) {
381        self.exec_ctx.set_dry_run(dry_run);
382    }
383
384    pub fn get_dry_run(&self) -> &DryRun {
385        self.exec_ctx.get_dry_run()
386    }
387
388    #[cfg_attr(
389        feature = "tracing",
390        instrument(target = "CONFIG_HANDLING", level = "trace", name = "Config::parse", skip_all)
391    )]
392    pub fn parse(flags: Flags) -> Config {
393        Self::parse_inner(flags, Self::get_toml)
394    }
395
396    #[cfg_attr(
397        feature = "tracing",
398        instrument(
399            target = "CONFIG_HANDLING",
400            level = "trace",
401            name = "Config::parse_inner",
402            skip_all
403        )
404    )]
405    pub(crate) fn parse_inner(
406        flags: Flags,
407        get_toml: impl Fn(&Path) -> Result<TomlConfig, toml::de::Error>,
408    ) -> Config {
409        // Destructure flags to ensure that we use all its fields
410        // The field variables are prefixed with `flags_` to avoid clashes
411        // with values from TOML config files with same names.
412        let Flags {
413            cmd: flags_cmd,
414            verbose: flags_verbose,
415            incremental: flags_incremental,
416            config: flags_config,
417            build_dir: flags_build_dir,
418            build: flags_build,
419            host: flags_host,
420            target: flags_target,
421            exclude: flags_exclude,
422            skip: flags_skip,
423            include_default_paths: flags_include_default_paths,
424            rustc_error_format: flags_rustc_error_format,
425            on_fail: flags_on_fail,
426            dry_run: flags_dry_run,
427            dump_bootstrap_shims: flags_dump_bootstrap_shims,
428            stage: flags_stage,
429            keep_stage: flags_keep_stage,
430            keep_stage_std: flags_keep_stage_std,
431            src: flags_src,
432            jobs: flags_jobs,
433            warnings: flags_warnings,
434            json_output: flags_json_output,
435            compile_time_deps: flags_compile_time_deps,
436            color: flags_color,
437            bypass_bootstrap_lock: flags_bypass_bootstrap_lock,
438            rust_profile_generate: flags_rust_profile_generate,
439            rust_profile_use: flags_rust_profile_use,
440            llvm_profile_use: flags_llvm_profile_use,
441            llvm_profile_generate: flags_llvm_profile_generate,
442            enable_bolt_settings: flags_enable_bolt_settings,
443            skip_stage0_validation: flags_skip_stage0_validation,
444            reproducible_artifact: flags_reproducible_artifact,
445            paths: mut flags_paths,
446            set: flags_set,
447            free_args: mut flags_free_args,
448            ci: flags_ci,
449            skip_std_check_if_no_download_rustc: flags_skip_std_check_if_no_download_rustc,
450        } = flags;
451
452        let mut config = Config::default_opts();
453        let mut exec_ctx = ExecutionContext::new();
454        exec_ctx.set_verbose(flags_verbose);
455        exec_ctx.set_fail_fast(flags_cmd.fail_fast());
456
457        config.exec_ctx = exec_ctx;
458
459        // Set flags.
460        config.paths = std::mem::take(&mut flags_paths);
461
462        #[cfg(feature = "tracing")]
463        span!(
464            target: "CONFIG_HANDLING",
465            tracing::Level::TRACE,
466            "collecting paths and path exclusions",
467            "flags.paths" = ?flags_paths,
468            "flags.skip" = ?flags_skip,
469            "flags.exclude" = ?flags_exclude
470        );
471
472        #[cfg(feature = "tracing")]
473        span!(
474            target: "CONFIG_HANDLING",
475            tracing::Level::TRACE,
476            "normalizing and combining `flag.skip`/`flag.exclude` paths",
477            "config.skip" = ?config.skip,
478        );
479
480        config.include_default_paths = flags_include_default_paths;
481        config.rustc_error_format = flags_rustc_error_format;
482        config.json_output = flags_json_output;
483        config.compile_time_deps = flags_compile_time_deps;
484        config.on_fail = flags_on_fail;
485        config.cmd = flags_cmd;
486        config.incremental = flags_incremental;
487        config.set_dry_run(if flags_dry_run { DryRun::UserSelected } else { DryRun::Disabled });
488        config.dump_bootstrap_shims = flags_dump_bootstrap_shims;
489        config.keep_stage = flags_keep_stage;
490        config.keep_stage_std = flags_keep_stage_std;
491        config.color = flags_color;
492        config.free_args = std::mem::take(&mut flags_free_args);
493        config.llvm_profile_use = flags_llvm_profile_use;
494        config.llvm_profile_generate = flags_llvm_profile_generate;
495        config.enable_bolt_settings = flags_enable_bolt_settings;
496        config.bypass_bootstrap_lock = flags_bypass_bootstrap_lock;
497        config.is_running_on_ci = flags_ci.unwrap_or(CiEnv::is_ci());
498        config.skip_std_check_if_no_download_rustc = flags_skip_std_check_if_no_download_rustc;
499
500        // Infer the rest of the configuration.
501
502        if let Some(src) = flags_src {
503            config.src = src
504        } else {
505            // Infer the source directory. This is non-trivial because we want to support a downloaded bootstrap binary,
506            // running on a completely different machine from where it was compiled.
507            let mut cmd = helpers::git(None);
508            // NOTE: we cannot support running from outside the repository because the only other path we have available
509            // is set at compile time, which can be wrong if bootstrap was downloaded rather than compiled locally.
510            // We still support running outside the repository if we find we aren't in a git directory.
511
512            // NOTE: We get a relative path from git to work around an issue on MSYS/mingw. If we used an absolute path,
513            // and end up using MSYS's git rather than git-for-windows, we would get a unix-y MSYS path. But as bootstrap
514            // has already been (kinda-cross-)compiled to Windows land, we require a normal Windows path.
515            cmd.arg("rev-parse").arg("--show-cdup");
516            // Discard stderr because we expect this to fail when building from a tarball.
517            let output = cmd.allow_failure().run_capture_stdout(&config);
518            if output.is_success() {
519                let git_root_relative = output.stdout();
520                // We need to canonicalize this path to make sure it uses backslashes instead of forward slashes,
521                // and to resolve any relative components.
522                let git_root = env::current_dir()
523                    .unwrap()
524                    .join(PathBuf::from(git_root_relative.trim()))
525                    .canonicalize()
526                    .unwrap();
527                let s = git_root.to_str().unwrap();
528
529                // Bootstrap is quite bad at handling /? in front of paths
530                let git_root = match s.strip_prefix("\\\\?\\") {
531                    Some(p) => PathBuf::from(p),
532                    None => git_root,
533                };
534                // If this doesn't have at least `stage0`, we guessed wrong. This can happen when,
535                // for example, the build directory is inside of another unrelated git directory.
536                // In that case keep the original `CARGO_MANIFEST_DIR` handling.
537                //
538                // NOTE: this implies that downloadable bootstrap isn't supported when the build directory is outside
539                // the source directory. We could fix that by setting a variable from all three of python, ./x, and x.ps1.
540                if git_root.join("src").join("stage0").exists() {
541                    config.src = git_root;
542                }
543            } else {
544                // We're building from a tarball, not git sources.
545                // We don't support pre-downloaded bootstrap in this case.
546            }
547        }
548
549        if cfg!(test) {
550            // Use the build directory of the original x.py invocation, so that we can set `initial_rustc` properly.
551            config.out = Path::new(
552                &env::var_os("CARGO_TARGET_DIR").expect("cargo test directly is not supported"),
553            )
554            .parent()
555            .unwrap()
556            .to_path_buf();
557        }
558
559        config.stage0_metadata = build_helper::stage0_parser::parse_stage0_file();
560
561        // Locate the configuration file using the following priority (first match wins):
562        // 1. `--config <path>` (explicit flag)
563        // 2. `RUST_BOOTSTRAP_CONFIG` environment variable
564        // 3. `./bootstrap.toml` (local file)
565        // 4. `<root>/bootstrap.toml`
566        // 5. `./config.toml` (fallback for backward compatibility)
567        // 6. `<root>/config.toml`
568        let toml_path = flags_config
569            .clone()
570            .or_else(|| env::var_os("RUST_BOOTSTRAP_CONFIG").map(PathBuf::from));
571        let using_default_path = toml_path.is_none();
572        let mut toml_path = toml_path.unwrap_or_else(|| PathBuf::from("bootstrap.toml"));
573
574        if using_default_path && !toml_path.exists() {
575            toml_path = config.src.join(PathBuf::from("bootstrap.toml"));
576            if !toml_path.exists() {
577                toml_path = PathBuf::from("config.toml");
578                if !toml_path.exists() {
579                    toml_path = config.src.join(PathBuf::from("config.toml"));
580                }
581            }
582        }
583
584        // Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path,
585        // but not if `bootstrap.toml` hasn't been created.
586        let mut toml = if !using_default_path || toml_path.exists() {
587            config.config = Some(if cfg!(not(test)) {
588                toml_path = toml_path.canonicalize().unwrap();
589                toml_path.clone()
590            } else {
591                toml_path.clone()
592            });
593            get_toml(&toml_path).unwrap_or_else(|e| {
594                eprintln!("ERROR: Failed to parse '{}': {e}", toml_path.display());
595                exit!(2);
596            })
597        } else {
598            config.config = None;
599            TomlConfig::default()
600        };
601
602        if cfg!(test) {
603            // When configuring bootstrap for tests, make sure to set the rustc and Cargo to the
604            // same ones used to call the tests (if custom ones are not defined in the toml). If we
605            // don't do that, bootstrap will use its own detection logic to find a suitable rustc
606            // and Cargo, which doesn't work when the caller is specìfying a custom local rustc or
607            // Cargo in their bootstrap.toml.
608            let build = toml.build.get_or_insert_with(Default::default);
609            build.rustc = build.rustc.take().or(std::env::var_os("RUSTC").map(|p| p.into()));
610            build.cargo = build.cargo.take().or(std::env::var_os("CARGO").map(|p| p.into()));
611        }
612
613        if config.git_info(false, &config.src).is_from_tarball() && toml.profile.is_none() {
614            toml.profile = Some("dist".into());
615        }
616
617        // Reverse the list to ensure the last added config extension remains the most dominant.
618        // For example, given ["a.toml", "b.toml"], "b.toml" should take precedence over "a.toml".
619        //
620        // This must be handled before applying the `profile` since `include`s should always take
621        // precedence over `profile`s.
622        for include_path in toml.include.clone().unwrap_or_default().iter().rev() {
623            let include_path = toml_path.parent().unwrap().join(include_path);
624
625            let included_toml = get_toml(&include_path).unwrap_or_else(|e| {
626                eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display());
627                exit!(2);
628            });
629            toml.merge(
630                Some(include_path),
631                &mut Default::default(),
632                included_toml,
633                ReplaceOpt::IgnoreDuplicate,
634            );
635        }
636
637        if let Some(include) = &toml.profile {
638            // Allows creating alias for profile names, allowing
639            // profiles to be renamed while maintaining back compatibility
640            // Keep in sync with `profile_aliases` in bootstrap.py
641            let profile_aliases = HashMap::from([("user", "dist")]);
642            let include = match profile_aliases.get(include.as_str()) {
643                Some(alias) => alias,
644                None => include.as_str(),
645            };
646            let mut include_path = config.src.clone();
647            include_path.push("src");
648            include_path.push("bootstrap");
649            include_path.push("defaults");
650            include_path.push(format!("bootstrap.{include}.toml"));
651            let included_toml = get_toml(&include_path).unwrap_or_else(|e| {
652                eprintln!(
653                    "ERROR: Failed to parse default config profile at '{}': {e}",
654                    include_path.display()
655                );
656                exit!(2);
657            });
658            toml.merge(
659                Some(include_path),
660                &mut Default::default(),
661                included_toml,
662                ReplaceOpt::IgnoreDuplicate,
663            );
664        }
665
666        let mut override_toml = TomlConfig::default();
667        for option in flags_set.iter() {
668            fn get_table(option: &str) -> Result<TomlConfig, toml::de::Error> {
669                toml::from_str(option).and_then(|table: toml::Value| TomlConfig::deserialize(table))
670            }
671
672            let mut err = match get_table(option) {
673                Ok(v) => {
674                    override_toml.merge(
675                        None,
676                        &mut Default::default(),
677                        v,
678                        ReplaceOpt::ErrorOnDuplicate,
679                    );
680                    continue;
681                }
682                Err(e) => e,
683            };
684            // We want to be able to set string values without quotes,
685            // like in `configure.py`. Try adding quotes around the right hand side
686            if let Some((key, value)) = option.split_once('=')
687                && !value.contains('"')
688            {
689                match get_table(&format!(r#"{key}="{value}""#)) {
690                    Ok(v) => {
691                        override_toml.merge(
692                            None,
693                            &mut Default::default(),
694                            v,
695                            ReplaceOpt::ErrorOnDuplicate,
696                        );
697                        continue;
698                    }
699                    Err(e) => err = e,
700                }
701            }
702            eprintln!("failed to parse override `{option}`: `{err}");
703            exit!(2)
704        }
705        toml.merge(None, &mut Default::default(), override_toml, ReplaceOpt::Override);
706
707        config.change_id = toml.change_id.inner;
708
709        let Build {
710            description,
711            build,
712            host,
713            target,
714            build_dir,
715            cargo,
716            rustc,
717            rustfmt,
718            cargo_clippy,
719            docs,
720            compiler_docs,
721            library_docs_private_items,
722            docs_minification,
723            submodules,
724            gdb,
725            lldb,
726            nodejs,
727            npm,
728            python,
729            reuse,
730            locked_deps,
731            vendor,
732            full_bootstrap,
733            bootstrap_cache_path,
734            extended,
735            tools,
736            tool,
737            verbose,
738            sanitizers,
739            profiler,
740            cargo_native_static,
741            low_priority,
742            configure_args,
743            local_rebuild,
744            print_step_timings,
745            print_step_rusage,
746            check_stage,
747            doc_stage,
748            build_stage,
749            test_stage,
750            install_stage,
751            dist_stage,
752            bench_stage,
753            patch_binaries_for_nix,
754            // This field is only used by bootstrap.py
755            metrics: _,
756            android_ndk,
757            optimized_compiler_builtins,
758            jobs,
759            compiletest_diff_tool,
760            compiletest_allow_stage0,
761            compiletest_use_stage0_libtest,
762            tidy_extra_checks,
763            ccache,
764            exclude,
765        } = toml.build.unwrap_or_default();
766
767        let mut paths: Vec<PathBuf> = flags_skip.into_iter().chain(flags_exclude).collect();
768
769        if let Some(exclude) = exclude {
770            paths.extend(exclude);
771        }
772
773        config.skip = paths
774            .into_iter()
775            .map(|p| {
776                // Never return top-level path here as it would break `--skip`
777                // logic on rustc's internal test framework which is utilized
778                // by compiletest.
779                if cfg!(windows) {
780                    PathBuf::from(p.to_str().unwrap().replace('/', "\\"))
781                } else {
782                    p
783                }
784            })
785            .collect();
786
787        config.jobs = Some(threads_from_config(flags_jobs.unwrap_or(jobs.unwrap_or(0))));
788
789        if let Some(flags_build) = flags_build {
790            config.host_target = TargetSelection::from_user(&flags_build);
791        } else if let Some(file_build) = build {
792            config.host_target = TargetSelection::from_user(&file_build);
793        };
794
795        set(&mut config.out, flags_build_dir.or_else(|| build_dir.map(PathBuf::from)));
796        // NOTE: Bootstrap spawns various commands with different working directories.
797        // To avoid writing to random places on the file system, `config.out` needs to be an absolute path.
798        if !config.out.is_absolute() {
799            // `canonicalize` requires the path to already exist. Use our vendored copy of `absolute` instead.
800            config.out = absolute(&config.out).expect("can't make empty path absolute");
801        }
802
803        if cargo_clippy.is_some() && rustc.is_none() {
804            println!(
805                "WARNING: Using `build.cargo-clippy` without `build.rustc` usually fails due to toolchain conflict."
806            );
807        }
808
809        config.patch_binaries_for_nix = patch_binaries_for_nix;
810        config.bootstrap_cache_path = bootstrap_cache_path;
811        config.llvm_assertions =
812            toml.llvm.as_ref().is_some_and(|llvm| llvm.assertions.unwrap_or(false));
813
814        config.initial_rustc = if let Some(rustc) = rustc {
815            if !flags_skip_stage0_validation {
816                config.check_stage0_version(&rustc, "rustc");
817            }
818            rustc
819        } else {
820            let dwn_ctx = DownloadContext::from(&config);
821            download_beta_toolchain(dwn_ctx);
822            config
823                .out
824                .join(config.host_target)
825                .join("stage0")
826                .join("bin")
827                .join(exe("rustc", config.host_target))
828        };
829
830        config.initial_sysroot = t!(PathBuf::from_str(
831            command(&config.initial_rustc)
832                .args(["--print", "sysroot"])
833                .run_in_dry_run()
834                .run_capture_stdout(&config)
835                .stdout()
836                .trim()
837        ));
838
839        config.initial_cargo_clippy = cargo_clippy;
840
841        config.initial_cargo = if let Some(cargo) = cargo {
842            if !flags_skip_stage0_validation {
843                config.check_stage0_version(&cargo, "cargo");
844            }
845            cargo
846        } else {
847            let dwn_ctx = DownloadContext::from(&config);
848            download_beta_toolchain(dwn_ctx);
849            config.initial_sysroot.join("bin").join(exe("cargo", config.host_target))
850        };
851
852        // NOTE: it's important this comes *after* we set `initial_rustc` just above.
853        if config.dry_run() {
854            let dir = config.out.join("tmp-dry-run");
855            t!(fs::create_dir_all(&dir));
856            config.out = dir;
857        }
858
859        config.hosts = if let Some(TargetSelectionList(arg_host)) = flags_host {
860            arg_host
861        } else if let Some(file_host) = host {
862            file_host.iter().map(|h| TargetSelection::from_user(h)).collect()
863        } else {
864            vec![config.host_target]
865        };
866        config.targets = if let Some(TargetSelectionList(arg_target)) = flags_target {
867            arg_target
868        } else if let Some(file_target) = target {
869            file_target.iter().map(|h| TargetSelection::from_user(h)).collect()
870        } else {
871            // If target is *not* configured, then default to the host
872            // toolchains.
873            config.hosts.clone()
874        };
875
876        config.nodejs = nodejs.map(PathBuf::from);
877        config.npm = npm.map(PathBuf::from);
878        config.gdb = gdb.map(PathBuf::from);
879        config.lldb = lldb.map(PathBuf::from);
880        config.python = python.map(PathBuf::from);
881        config.reuse = reuse.map(PathBuf::from);
882        config.submodules = submodules;
883        config.android_ndk = android_ndk;
884        set(&mut config.low_priority, low_priority);
885        set(&mut config.compiler_docs, compiler_docs);
886        set(&mut config.library_docs_private_items, library_docs_private_items);
887        set(&mut config.docs_minification, docs_minification);
888        set(&mut config.docs, docs);
889        set(&mut config.locked_deps, locked_deps);
890        set(&mut config.full_bootstrap, full_bootstrap);
891        set(&mut config.extended, extended);
892        config.tools = tools;
893        set(&mut config.tool, tool);
894        set(&mut config.verbose, verbose);
895        set(&mut config.sanitizers, sanitizers);
896        set(&mut config.profiler, profiler);
897        set(&mut config.cargo_native_static, cargo_native_static);
898        set(&mut config.configure_args, configure_args);
899        set(&mut config.local_rebuild, local_rebuild);
900        set(&mut config.print_step_timings, print_step_timings);
901        set(&mut config.print_step_rusage, print_step_rusage);
902
903        config.verbose = cmp::max(config.verbose, flags_verbose as usize);
904
905        // Verbose flag is a good default for `rust.verbose-tests`.
906        config.verbose_tests = config.is_verbose();
907
908        config.apply_install_config(toml.install);
909
910        let file_content = t!(fs::read_to_string(config.src.join("src/ci/channel")));
911        let ci_channel = file_content.trim_end();
912
913        let toml_channel = toml.rust.as_ref().and_then(|r| r.channel.clone());
914        let is_user_configured_rust_channel = match toml_channel {
915            Some(channel) if channel == "auto-detect" => {
916                config.channel = ci_channel.into();
917                true
918            }
919            Some(channel) => {
920                config.channel = channel;
921                true
922            }
923            None => false,
924        };
925
926        let default = config.channel == "dev";
927        config.omit_git_hash = toml.rust.as_ref().and_then(|r| r.omit_git_hash).unwrap_or(default);
928
929        config.rust_info = config.git_info(config.omit_git_hash, &config.src);
930        config.cargo_info =
931            config.git_info(config.omit_git_hash, &config.src.join("src/tools/cargo"));
932        config.rust_analyzer_info =
933            config.git_info(config.omit_git_hash, &config.src.join("src/tools/rust-analyzer"));
934        config.clippy_info =
935            config.git_info(config.omit_git_hash, &config.src.join("src/tools/clippy"));
936        config.miri_info =
937            config.git_info(config.omit_git_hash, &config.src.join("src/tools/miri"));
938        config.rustfmt_info =
939            config.git_info(config.omit_git_hash, &config.src.join("src/tools/rustfmt"));
940        config.enzyme_info =
941            config.git_info(config.omit_git_hash, &config.src.join("src/tools/enzyme"));
942        config.in_tree_llvm_info = config.git_info(false, &config.src.join("src/llvm-project"));
943        config.in_tree_gcc_info = config.git_info(false, &config.src.join("src/gcc"));
944
945        config.vendor = vendor.unwrap_or(
946            config.rust_info.is_from_tarball()
947                && config.src.join("vendor").exists()
948                && config.src.join(".cargo/config.toml").exists(),
949        );
950
951        if !is_user_configured_rust_channel && config.rust_info.is_from_tarball() {
952            config.channel = ci_channel.into();
953        }
954
955        config.rust_profile_use = flags_rust_profile_use;
956        config.rust_profile_generate = flags_rust_profile_generate;
957
958        config.apply_target_config(toml.target);
959        config.apply_rust_config(toml.rust, flags_warnings);
960
961        config.reproducible_artifacts = flags_reproducible_artifact;
962        config.description = description;
963
964        // We need to override `rust.channel` if it's manually specified when using the CI rustc.
965        // This is because if the compiler uses a different channel than the one specified in bootstrap.toml,
966        // tests may fail due to using a different channel than the one used by the compiler during tests.
967        if let Some(commit) = &config.download_rustc_commit
968            && is_user_configured_rust_channel
969        {
970            println!(
971                "WARNING: `rust.download-rustc` is enabled. The `rust.channel` option will be overridden by the CI rustc's channel."
972            );
973
974            let channel =
975                config.read_file_by_commit(Path::new("src/ci/channel"), commit).trim().to_owned();
976
977            config.channel = channel;
978        }
979
980        config.apply_llvm_config(toml.llvm);
981
982        config.apply_gcc_config(toml.gcc);
983
984        match ccache {
985            Some(StringOrBool::String(ref s)) => config.ccache = Some(s.to_string()),
986            Some(StringOrBool::Bool(true)) => {
987                config.ccache = Some("ccache".to_string());
988            }
989            Some(StringOrBool::Bool(false)) | None => {}
990        }
991
992        if config.llvm_from_ci {
993            let triple = &config.host_target.triple;
994            let ci_llvm_bin = config.ci_llvm_root().join("bin");
995            let build_target = config
996                .target_config
997                .entry(config.host_target)
998                .or_insert_with(|| Target::from_triple(triple));
999
1000            check_ci_llvm!(build_target.llvm_config);
1001            check_ci_llvm!(build_target.llvm_filecheck);
1002            build_target.llvm_config =
1003                Some(ci_llvm_bin.join(exe("llvm-config", config.host_target)));
1004            build_target.llvm_filecheck =
1005                Some(ci_llvm_bin.join(exe("FileCheck", config.host_target)));
1006        }
1007
1008        config.apply_dist_config(toml.dist);
1009
1010        config.initial_rustfmt = if let Some(r) = rustfmt {
1011            Some(r)
1012        } else {
1013            let dwn_ctx = DownloadContext::from(&config);
1014            maybe_download_rustfmt(dwn_ctx)
1015        };
1016
1017        if matches!(config.lld_mode, LldMode::SelfContained)
1018            && !config.lld_enabled
1019            && flags_stage.unwrap_or(0) > 0
1020        {
1021            panic!(
1022                "Trying to use self-contained lld as a linker, but LLD is not being added to the sysroot. Enable it with rust.lld = true."
1023            );
1024        }
1025
1026        if config.lld_enabled && config.is_system_llvm(config.host_target) {
1027            panic!("Cannot enable LLD with `rust.lld = true` when using external llvm-config.");
1028        }
1029
1030        config.optimized_compiler_builtins =
1031            optimized_compiler_builtins.unwrap_or(config.channel != "dev");
1032
1033        config.compiletest_diff_tool = compiletest_diff_tool;
1034
1035        config.compiletest_allow_stage0 = compiletest_allow_stage0.unwrap_or(false);
1036        config.compiletest_use_stage0_libtest = compiletest_use_stage0_libtest.unwrap_or(true);
1037
1038        config.tidy_extra_checks = tidy_extra_checks;
1039
1040        let download_rustc = config.download_rustc_commit.is_some();
1041        config.explicit_stage_from_cli = flags_stage.is_some();
1042        config.explicit_stage_from_config = test_stage.is_some()
1043            || build_stage.is_some()
1044            || doc_stage.is_some()
1045            || dist_stage.is_some()
1046            || install_stage.is_some()
1047            || check_stage.is_some()
1048            || bench_stage.is_some();
1049
1050        config.stage = match config.cmd {
1051            Subcommand::Check { .. } => flags_stage.or(check_stage).unwrap_or(1),
1052            Subcommand::Clippy { .. } | Subcommand::Fix => flags_stage.or(check_stage).unwrap_or(1),
1053            // `download-rustc` only has a speed-up for stage2 builds. Default to stage2 unless explicitly overridden.
1054            Subcommand::Doc { .. } => {
1055                flags_stage.or(doc_stage).unwrap_or(if download_rustc { 2 } else { 1 })
1056            }
1057            Subcommand::Build => {
1058                flags_stage.or(build_stage).unwrap_or(if download_rustc { 2 } else { 1 })
1059            }
1060            Subcommand::Test { .. } | Subcommand::Miri { .. } => {
1061                flags_stage.or(test_stage).unwrap_or(if download_rustc { 2 } else { 1 })
1062            }
1063            Subcommand::Bench { .. } => flags_stage.or(bench_stage).unwrap_or(2),
1064            Subcommand::Dist => flags_stage.or(dist_stage).unwrap_or(2),
1065            Subcommand::Install => flags_stage.or(install_stage).unwrap_or(2),
1066            Subcommand::Perf { .. } => flags_stage.unwrap_or(1),
1067            // These are all bootstrap tools, which don't depend on the compiler.
1068            // The stage we pass shouldn't matter, but use 0 just in case.
1069            Subcommand::Clean { .. }
1070            | Subcommand::Run { .. }
1071            | Subcommand::Setup { .. }
1072            | Subcommand::Format { .. }
1073            | Subcommand::Vendor { .. } => flags_stage.unwrap_or(0),
1074        };
1075
1076        // Now check that the selected stage makes sense, and if not, print a warning and end
1077        match (config.stage, &config.cmd) {
1078            (0, Subcommand::Build) => {
1079                eprintln!("WARNING: cannot build anything on stage 0. Use at least stage 1.");
1080                exit!(1);
1081            }
1082            (0, Subcommand::Check { .. }) => {
1083                eprintln!("WARNING: cannot check anything on stage 0. Use at least stage 1.");
1084                exit!(1);
1085            }
1086            _ => {}
1087        }
1088
1089        if config.compile_time_deps && !matches!(config.cmd, Subcommand::Check { .. }) {
1090            eprintln!(
1091                "WARNING: Can't use --compile-time-deps with any subcommand other than check."
1092            );
1093            exit!(1);
1094        }
1095
1096        // CI should always run stage 2 builds, unless it specifically states otherwise
1097        #[cfg(not(test))]
1098        if flags_stage.is_none() && config.is_running_on_ci {
1099            match config.cmd {
1100                Subcommand::Test { .. }
1101                | Subcommand::Miri { .. }
1102                | Subcommand::Doc { .. }
1103                | Subcommand::Build
1104                | Subcommand::Bench { .. }
1105                | Subcommand::Dist
1106                | Subcommand::Install => {
1107                    assert_eq!(
1108                        config.stage, 2,
1109                        "x.py should be run with `--stage 2` on CI, but was run with `--stage {}`",
1110                        config.stage,
1111                    );
1112                }
1113                Subcommand::Clean { .. }
1114                | Subcommand::Check { .. }
1115                | Subcommand::Clippy { .. }
1116                | Subcommand::Fix
1117                | Subcommand::Run { .. }
1118                | Subcommand::Setup { .. }
1119                | Subcommand::Format { .. }
1120                | Subcommand::Vendor { .. }
1121                | Subcommand::Perf { .. } => {}
1122            }
1123        }
1124
1125        config
1126    }
1127
1128    pub fn dry_run(&self) -> bool {
1129        self.exec_ctx.dry_run()
1130    }
1131
1132    pub fn is_explicit_stage(&self) -> bool {
1133        self.explicit_stage_from_cli || self.explicit_stage_from_config
1134    }
1135
1136    pub(crate) fn test_args(&self) -> Vec<&str> {
1137        let mut test_args = match self.cmd {
1138            Subcommand::Test { ref test_args, .. }
1139            | Subcommand::Bench { ref test_args, .. }
1140            | Subcommand::Miri { ref test_args, .. } => {
1141                test_args.iter().flat_map(|s| s.split_whitespace()).collect()
1142            }
1143            _ => vec![],
1144        };
1145        test_args.extend(self.free_args.iter().map(|s| s.as_str()));
1146        test_args
1147    }
1148
1149    pub(crate) fn args(&self) -> Vec<&str> {
1150        let mut args = match self.cmd {
1151            Subcommand::Run { ref args, .. } => {
1152                args.iter().flat_map(|s| s.split_whitespace()).collect()
1153            }
1154            _ => vec![],
1155        };
1156        args.extend(self.free_args.iter().map(|s| s.as_str()));
1157        args
1158    }
1159
1160    /// Returns the content of the given file at a specific commit.
1161    pub(crate) fn read_file_by_commit(&self, file: &Path, commit: &str) -> String {
1162        assert!(
1163            self.rust_info.is_managed_git_subrepository(),
1164            "`Config::read_file_by_commit` is not supported in non-git sources."
1165        );
1166
1167        let mut git = helpers::git(Some(&self.src));
1168        git.arg("show").arg(format!("{commit}:{}", file.to_str().unwrap()));
1169        git.run_capture_stdout(self).stdout()
1170    }
1171
1172    /// Bootstrap embeds a version number into the name of shared libraries it uploads in CI.
1173    /// Return the version it would have used for the given commit.
1174    pub(crate) fn artifact_version_part(&self, commit: &str) -> String {
1175        let (channel, version) = if self.rust_info.is_managed_git_subrepository() {
1176            let channel =
1177                self.read_file_by_commit(Path::new("src/ci/channel"), commit).trim().to_owned();
1178            let version =
1179                self.read_file_by_commit(Path::new("src/version"), commit).trim().to_owned();
1180            (channel, version)
1181        } else {
1182            let channel = fs::read_to_string(self.src.join("src/ci/channel"));
1183            let version = fs::read_to_string(self.src.join("src/version"));
1184            match (channel, version) {
1185                (Ok(channel), Ok(version)) => {
1186                    (channel.trim().to_owned(), version.trim().to_owned())
1187                }
1188                (channel, version) => {
1189                    let src = self.src.display();
1190                    eprintln!("ERROR: failed to determine artifact channel and/or version");
1191                    eprintln!(
1192                        "HELP: consider using a git checkout or ensure these files are readable"
1193                    );
1194                    if let Err(channel) = channel {
1195                        eprintln!("reading {src}/src/ci/channel failed: {channel:?}");
1196                    }
1197                    if let Err(version) = version {
1198                        eprintln!("reading {src}/src/version failed: {version:?}");
1199                    }
1200                    panic!();
1201                }
1202            }
1203        };
1204
1205        match channel.as_str() {
1206            "stable" => version,
1207            "beta" => channel,
1208            "nightly" => channel,
1209            other => unreachable!("{:?} is not recognized as a valid channel", other),
1210        }
1211    }
1212
1213    /// Try to find the relative path of `bindir`, otherwise return it in full.
1214    pub fn bindir_relative(&self) -> &Path {
1215        let bindir = &self.bindir;
1216        if bindir.is_absolute() {
1217            // Try to make it relative to the prefix.
1218            if let Some(prefix) = &self.prefix
1219                && let Ok(stripped) = bindir.strip_prefix(prefix)
1220            {
1221                return stripped;
1222            }
1223        }
1224        bindir
1225    }
1226
1227    /// Try to find the relative path of `libdir`.
1228    pub fn libdir_relative(&self) -> Option<&Path> {
1229        let libdir = self.libdir.as_ref()?;
1230        if libdir.is_relative() {
1231            Some(libdir)
1232        } else {
1233            // Try to make it relative to the prefix.
1234            libdir.strip_prefix(self.prefix.as_ref()?).ok()
1235        }
1236    }
1237
1238    /// The absolute path to the downloaded LLVM artifacts.
1239    pub(crate) fn ci_llvm_root(&self) -> PathBuf {
1240        assert!(self.llvm_from_ci);
1241        self.out.join(self.host_target).join("ci-llvm")
1242    }
1243
1244    /// Directory where the extracted `rustc-dev` component is stored.
1245    pub(crate) fn ci_rustc_dir(&self) -> PathBuf {
1246        assert!(self.download_rustc());
1247        self.out.join(self.host_target).join("ci-rustc")
1248    }
1249
1250    /// Determine whether llvm should be linked dynamically.
1251    ///
1252    /// If `false`, llvm should be linked statically.
1253    /// This is computed on demand since LLVM might have to first be downloaded from CI.
1254    pub(crate) fn llvm_link_shared(&self) -> bool {
1255        let mut opt = self.llvm_link_shared.get();
1256        if opt.is_none() && self.dry_run() {
1257            // just assume static for now - dynamic linking isn't supported on all platforms
1258            return false;
1259        }
1260
1261        let llvm_link_shared = *opt.get_or_insert_with(|| {
1262            if self.llvm_from_ci {
1263                self.maybe_download_ci_llvm();
1264                let ci_llvm = self.ci_llvm_root();
1265                let link_type = t!(
1266                    std::fs::read_to_string(ci_llvm.join("link-type.txt")),
1267                    format!("CI llvm missing: {}", ci_llvm.display())
1268                );
1269                link_type == "dynamic"
1270            } else {
1271                // unclear how thought-through this default is, but it maintains compatibility with
1272                // previous behavior
1273                false
1274            }
1275        });
1276        self.llvm_link_shared.set(opt);
1277        llvm_link_shared
1278    }
1279
1280    /// Return whether we will use a downloaded, pre-compiled version of rustc, or just build from source.
1281    pub(crate) fn download_rustc(&self) -> bool {
1282        self.download_rustc_commit().is_some()
1283    }
1284
1285    pub(crate) fn download_rustc_commit(&self) -> Option<&str> {
1286        static DOWNLOAD_RUSTC: OnceLock<Option<String>> = OnceLock::new();
1287        if self.dry_run() && DOWNLOAD_RUSTC.get().is_none() {
1288            // avoid trying to actually download the commit
1289            return self.download_rustc_commit.as_deref();
1290        }
1291
1292        DOWNLOAD_RUSTC
1293            .get_or_init(|| match &self.download_rustc_commit {
1294                None => None,
1295                Some(commit) => {
1296                    self.download_ci_rustc(commit);
1297
1298                    // CI-rustc can't be used without CI-LLVM. If `self.llvm_from_ci` is false, it means the "if-unchanged"
1299                    // logic has detected some changes in the LLVM submodule (download-ci-llvm=false can't happen here as
1300                    // we don't allow it while parsing the configuration).
1301                    if !self.llvm_from_ci {
1302                        // This happens when LLVM submodule is updated in CI, we should disable ci-rustc without an error
1303                        // to not break CI. For non-CI environments, we should return an error.
1304                        if self.is_running_on_ci {
1305                            println!("WARNING: LLVM submodule has changes, `download-rustc` will be disabled.");
1306                            return None;
1307                        } else {
1308                            panic!("ERROR: LLVM submodule has changes, `download-rustc` can't be used.");
1309                        }
1310                    }
1311
1312                    if let Some(config_path) = &self.config {
1313                        let ci_config_toml = match self.get_builder_toml("ci-rustc") {
1314                            Ok(ci_config_toml) => ci_config_toml,
1315                            Err(e) if e.to_string().contains("unknown field") => {
1316                                println!("WARNING: CI rustc has some fields that are no longer supported in bootstrap; download-rustc will be disabled.");
1317                                println!("HELP: Consider rebasing to a newer commit if available.");
1318                                return None;
1319                            },
1320                            Err(e) => {
1321                                eprintln!("ERROR: Failed to parse CI rustc bootstrap.toml: {e}");
1322                                exit!(2);
1323                            },
1324                        };
1325
1326                        let current_config_toml = Self::get_toml(config_path).unwrap();
1327
1328                        // Check the config compatibility
1329                        // FIXME: this doesn't cover `--set` flags yet.
1330                        let res = check_incompatible_options_for_ci_rustc(
1331                            self.host_target,
1332                            current_config_toml,
1333                            ci_config_toml,
1334                        );
1335
1336                        // Primarily used by CI runners to avoid handling download-rustc incompatible
1337                        // options one by one on shell scripts.
1338                        let disable_ci_rustc_if_incompatible = env::var_os("DISABLE_CI_RUSTC_IF_INCOMPATIBLE")
1339                            .is_some_and(|s| s == "1" || s == "true");
1340
1341                        if disable_ci_rustc_if_incompatible && res.is_err() {
1342                            println!("WARNING: download-rustc is disabled with `DISABLE_CI_RUSTC_IF_INCOMPATIBLE` env.");
1343                            return None;
1344                        }
1345
1346                        res.unwrap();
1347                    }
1348
1349                    Some(commit.clone())
1350                }
1351            })
1352            .as_deref()
1353    }
1354
1355    /// Runs a function if verbosity is greater than 0
1356    pub fn verbose(&self, f: impl Fn()) {
1357        self.exec_ctx.verbose(f);
1358    }
1359
1360    pub fn any_sanitizers_to_build(&self) -> bool {
1361        self.target_config
1362            .iter()
1363            .any(|(ts, t)| !ts.is_msvc() && t.sanitizers.unwrap_or(self.sanitizers))
1364    }
1365
1366    pub fn any_profiler_enabled(&self) -> bool {
1367        self.target_config.values().any(|t| matches!(&t.profiler, Some(p) if p.is_string_or_true()))
1368            || self.profiler
1369    }
1370
1371    /// Returns whether or not submodules should be managed by bootstrap.
1372    pub fn submodules(&self) -> bool {
1373        // If not specified in config, the default is to only manage
1374        // submodules if we're currently inside a git repository.
1375        self.submodules.unwrap_or(self.rust_info.is_managed_git_subrepository())
1376    }
1377
1378    pub fn git_config(&self) -> GitConfig<'_> {
1379        GitConfig {
1380            nightly_branch: &self.stage0_metadata.config.nightly_branch,
1381            git_merge_commit_email: &self.stage0_metadata.config.git_merge_commit_email,
1382        }
1383    }
1384
1385    /// Given a path to the directory of a submodule, update it.
1386    ///
1387    /// `relative_path` should be relative to the root of the git repository, not an absolute path.
1388    ///
1389    /// This *does not* update the submodule if `bootstrap.toml` explicitly says
1390    /// not to, or if we're not in a git repository (like a plain source
1391    /// tarball). Typically [`crate::Build::require_submodule`] should be
1392    /// used instead to provide a nice error to the user if the submodule is
1393    /// missing.
1394    #[cfg_attr(
1395        feature = "tracing",
1396        instrument(
1397            level = "trace",
1398            name = "Config::update_submodule",
1399            skip_all,
1400            fields(relative_path = ?relative_path),
1401        ),
1402    )]
1403    pub(crate) fn update_submodule(&self, relative_path: &str) {
1404        if self.rust_info.is_from_tarball() || !self.submodules() {
1405            return;
1406        }
1407
1408        let absolute_path = self.src.join(relative_path);
1409
1410        // NOTE: This check is required because `jj git clone` doesn't create directories for
1411        // submodules, they are completely ignored. The code below assumes this directory exists,
1412        // so create it here.
1413        if !absolute_path.exists() {
1414            t!(fs::create_dir_all(&absolute_path));
1415        }
1416
1417        // NOTE: The check for the empty directory is here because when running x.py the first time,
1418        // the submodule won't be checked out. Check it out now so we can build it.
1419        if !self.git_info(false, &absolute_path).is_managed_git_subrepository()
1420            && !helpers::dir_is_empty(&absolute_path)
1421        {
1422            return;
1423        }
1424
1425        // Submodule updating actually happens during in the dry run mode. We need to make sure that
1426        // all the git commands below are actually executed, because some follow-up code
1427        // in bootstrap might depend on the submodules being checked out. Furthermore, not all
1428        // the command executions below work with an empty output (produced during dry run).
1429        // Therefore, all commands below are marked with `run_in_dry_run()`, so that they also run in
1430        // dry run mode.
1431        let submodule_git = || {
1432            let mut cmd = helpers::git(Some(&absolute_path));
1433            cmd.run_in_dry_run();
1434            cmd
1435        };
1436
1437        // Determine commit checked out in submodule.
1438        let checked_out_hash =
1439            submodule_git().args(["rev-parse", "HEAD"]).run_capture_stdout(self).stdout();
1440        let checked_out_hash = checked_out_hash.trim_end();
1441        // Determine commit that the submodule *should* have.
1442        let recorded = helpers::git(Some(&self.src))
1443            .run_in_dry_run()
1444            .args(["ls-tree", "HEAD"])
1445            .arg(relative_path)
1446            .run_capture_stdout(self)
1447            .stdout();
1448
1449        let actual_hash = recorded
1450            .split_whitespace()
1451            .nth(2)
1452            .unwrap_or_else(|| panic!("unexpected output `{recorded}`"));
1453
1454        if actual_hash == checked_out_hash {
1455            // already checked out
1456            return;
1457        }
1458
1459        println!("Updating submodule {relative_path}");
1460
1461        helpers::git(Some(&self.src))
1462            .allow_failure()
1463            .run_in_dry_run()
1464            .args(["submodule", "-q", "sync"])
1465            .arg(relative_path)
1466            .run(self);
1467
1468        // Try passing `--progress` to start, then run git again without if that fails.
1469        let update = |progress: bool| {
1470            // Git is buggy and will try to fetch submodules from the tracking branch for *this* repository,
1471            // even though that has no relation to the upstream for the submodule.
1472            let current_branch = helpers::git(Some(&self.src))
1473                .allow_failure()
1474                .run_in_dry_run()
1475                .args(["symbolic-ref", "--short", "HEAD"])
1476                .run_capture(self);
1477
1478            let mut git = helpers::git(Some(&self.src)).allow_failure();
1479            git.run_in_dry_run();
1480            if current_branch.is_success() {
1481                // If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name.
1482                // This syntax isn't accepted by `branch.{branch}`. Strip it.
1483                let branch = current_branch.stdout();
1484                let branch = branch.trim();
1485                let branch = branch.strip_prefix("heads/").unwrap_or(branch);
1486                git.arg("-c").arg(format!("branch.{branch}.remote=origin"));
1487            }
1488            git.args(["submodule", "update", "--init", "--recursive", "--depth=1"]);
1489            if progress {
1490                git.arg("--progress");
1491            }
1492            git.arg(relative_path);
1493            git
1494        };
1495        if !update(true).allow_failure().run(self) {
1496            update(false).allow_failure().run(self);
1497        }
1498
1499        // Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error).
1500        // diff-index reports the modifications through the exit status
1501        let has_local_modifications =
1502            !submodule_git().allow_failure().args(["diff-index", "--quiet", "HEAD"]).run(self);
1503        if has_local_modifications {
1504            submodule_git().allow_failure().args(["stash", "push"]).run(self);
1505        }
1506
1507        submodule_git().allow_failure().args(["reset", "-q", "--hard"]).run(self);
1508        submodule_git().allow_failure().args(["clean", "-qdfx"]).run(self);
1509
1510        if has_local_modifications {
1511            submodule_git().allow_failure().args(["stash", "pop"]).run(self);
1512        }
1513    }
1514
1515    #[cfg(test)]
1516    pub fn check_stage0_version(&self, _program_path: &Path, _component_name: &'static str) {}
1517
1518    /// check rustc/cargo version is same or lower with 1 apart from the building one
1519    #[cfg(not(test))]
1520    pub fn check_stage0_version(&self, program_path: &Path, component_name: &'static str) {
1521        use build_helper::util::fail;
1522
1523        if self.dry_run() {
1524            return;
1525        }
1526
1527        let stage0_output =
1528            command(program_path).arg("--version").run_capture_stdout(self).stdout();
1529        let mut stage0_output = stage0_output.lines().next().unwrap().split(' ');
1530
1531        let stage0_name = stage0_output.next().unwrap();
1532        if stage0_name != component_name {
1533            fail(&format!(
1534                "Expected to find {component_name} at {} but it claims to be {stage0_name}",
1535                program_path.display()
1536            ));
1537        }
1538
1539        let stage0_version =
1540            semver::Version::parse(stage0_output.next().unwrap().split('-').next().unwrap().trim())
1541                .unwrap();
1542        let source_version = semver::Version::parse(
1543            fs::read_to_string(self.src.join("src/version")).unwrap().trim(),
1544        )
1545        .unwrap();
1546        if !(source_version == stage0_version
1547            || (source_version.major == stage0_version.major
1548                && (source_version.minor == stage0_version.minor
1549                    || source_version.minor == stage0_version.minor + 1)))
1550        {
1551            let prev_version = format!("{}.{}.x", source_version.major, source_version.minor - 1);
1552            fail(&format!(
1553                "Unexpected {component_name} version: {stage0_version}, we should use {prev_version}/{source_version} to build source with {source_version}"
1554            ));
1555        }
1556    }
1557
1558    /// Returns the commit to download, or `None` if we shouldn't download CI artifacts.
1559    pub fn download_ci_rustc_commit(
1560        &self,
1561        download_rustc: Option<StringOrBool>,
1562        debug_assertions_requested: bool,
1563        llvm_assertions: bool,
1564    ) -> Option<String> {
1565        if !is_download_ci_available(&self.host_target.triple, llvm_assertions) {
1566            return None;
1567        }
1568
1569        // If `download-rustc` is not set, default to rebuilding.
1570        let if_unchanged = match download_rustc {
1571            // Globally default `download-rustc` to `false`, because some contributors don't use
1572            // profiles for reasons such as:
1573            // - They need to seamlessly switch between compiler/library work.
1574            // - They don't want to use compiler profile because they need to override too many
1575            //   things and it's easier to not use a profile.
1576            None | Some(StringOrBool::Bool(false)) => return None,
1577            Some(StringOrBool::Bool(true)) => false,
1578            Some(StringOrBool::String(s)) if s == "if-unchanged" => {
1579                if !self.rust_info.is_managed_git_subrepository() {
1580                    println!(
1581                        "ERROR: `download-rustc=if-unchanged` is only compatible with Git managed sources."
1582                    );
1583                    crate::exit!(1);
1584                }
1585
1586                true
1587            }
1588            Some(StringOrBool::String(other)) => {
1589                panic!("unrecognized option for download-rustc: {other}")
1590            }
1591        };
1592
1593        let commit = if self.rust_info.is_managed_git_subrepository() {
1594            // Look for a version to compare to based on the current commit.
1595            // Only commits merged by bors will have CI artifacts.
1596            let freshness = self.check_path_modifications(RUSTC_IF_UNCHANGED_ALLOWED_PATHS);
1597            self.verbose(|| {
1598                eprintln!("rustc freshness: {freshness:?}");
1599            });
1600            match freshness {
1601                PathFreshness::LastModifiedUpstream { upstream } => upstream,
1602                PathFreshness::HasLocalModifications { upstream } => {
1603                    if if_unchanged {
1604                        return None;
1605                    }
1606
1607                    if self.is_running_on_ci {
1608                        eprintln!("CI rustc commit matches with HEAD and we are in CI.");
1609                        eprintln!(
1610                            "`rustc.download-ci` functionality will be skipped as artifacts are not available."
1611                        );
1612                        return None;
1613                    }
1614
1615                    upstream
1616                }
1617                PathFreshness::MissingUpstream => {
1618                    eprintln!("No upstream commit found");
1619                    return None;
1620                }
1621            }
1622        } else {
1623            channel::read_commit_info_file(&self.src)
1624                .map(|info| info.sha.trim().to_owned())
1625                .expect("git-commit-info is missing in the project root")
1626        };
1627
1628        if debug_assertions_requested {
1629            eprintln!(
1630                "WARN: `rust.debug-assertions = true` will prevent downloading CI rustc as alt CI \
1631                rustc is not currently built with debug assertions."
1632            );
1633            return None;
1634        }
1635
1636        Some(commit)
1637    }
1638
1639    pub fn parse_download_ci_llvm(
1640        &self,
1641        download_ci_llvm: Option<StringOrBool>,
1642        asserts: bool,
1643    ) -> bool {
1644        // We don't ever want to use `true` on CI, as we should not
1645        // download upstream artifacts if there are any local modifications.
1646        let default = if self.is_running_on_ci {
1647            StringOrBool::String("if-unchanged".to_string())
1648        } else {
1649            StringOrBool::Bool(true)
1650        };
1651        let download_ci_llvm = download_ci_llvm.unwrap_or(default);
1652
1653        let if_unchanged = || {
1654            if self.rust_info.is_from_tarball() {
1655                // Git is needed for running "if-unchanged" logic.
1656                println!("ERROR: 'if-unchanged' is only compatible with Git managed sources.");
1657                crate::exit!(1);
1658            }
1659
1660            // Fetching the LLVM submodule is unnecessary for self-tests.
1661            #[cfg(not(test))]
1662            self.update_submodule("src/llvm-project");
1663
1664            // Check for untracked changes in `src/llvm-project` and other important places.
1665            let has_changes = self.has_changes_from_upstream(LLVM_INVALIDATION_PATHS);
1666
1667            // Return false if there are untracked changes, otherwise check if CI LLVM is available.
1668            if has_changes { false } else { llvm::is_ci_llvm_available_for_target(self, asserts) }
1669        };
1670
1671        match download_ci_llvm {
1672            StringOrBool::Bool(b) => {
1673                if !b && self.download_rustc_commit.is_some() {
1674                    panic!(
1675                        "`llvm.download-ci-llvm` cannot be set to `false` if `rust.download-rustc` is set to `true` or `if-unchanged`."
1676                    );
1677                }
1678
1679                if b && self.is_running_on_ci {
1680                    // On CI, we must always rebuild LLVM if there were any modifications to it
1681                    panic!(
1682                        "`llvm.download-ci-llvm` cannot be set to `true` on CI. Use `if-unchanged` instead."
1683                    );
1684                }
1685
1686                // If download-ci-llvm=true we also want to check that CI llvm is available
1687                b && llvm::is_ci_llvm_available_for_target(self, asserts)
1688            }
1689            StringOrBool::String(s) if s == "if-unchanged" => if_unchanged(),
1690            StringOrBool::String(other) => {
1691                panic!("unrecognized option for download-ci-llvm: {other:?}")
1692            }
1693        }
1694    }
1695
1696    /// Returns true if any of the `paths` have been modified locally.
1697    pub fn has_changes_from_upstream(&self, paths: &[&'static str]) -> bool {
1698        match self.check_path_modifications(paths) {
1699            PathFreshness::LastModifiedUpstream { .. } => false,
1700            PathFreshness::HasLocalModifications { .. } | PathFreshness::MissingUpstream => true,
1701        }
1702    }
1703
1704    /// Checks whether any of the given paths have been modified w.r.t. upstream.
1705    pub fn check_path_modifications(&self, paths: &[&'static str]) -> PathFreshness {
1706        // Checking path modifications through git can be relatively expensive (>100ms).
1707        // We do not assume that the sources would change during bootstrap's execution,
1708        // so we can cache the results here.
1709        // Note that we do not use a static variable for the cache, because it would cause problems
1710        // in tests that create separate `Config` instsances.
1711        self.path_modification_cache
1712            .lock()
1713            .unwrap()
1714            .entry(paths.to_vec())
1715            .or_insert_with(|| {
1716                check_path_modifications(&self.src, &self.git_config(), paths, CiEnv::current())
1717                    .unwrap()
1718            })
1719            .clone()
1720    }
1721
1722    pub fn ci_env(&self) -> CiEnv {
1723        if self.is_running_on_ci { CiEnv::GitHubActions } else { CiEnv::None }
1724    }
1725
1726    pub fn sanitizers_enabled(&self, target: TargetSelection) -> bool {
1727        self.target_config.get(&target).and_then(|t| t.sanitizers).unwrap_or(self.sanitizers)
1728    }
1729
1730    pub fn needs_sanitizer_runtime_built(&self, target: TargetSelection) -> bool {
1731        // MSVC uses the Microsoft-provided sanitizer runtime, but all other runtimes we build.
1732        !target.is_msvc() && self.sanitizers_enabled(target)
1733    }
1734
1735    pub fn profiler_path(&self, target: TargetSelection) -> Option<&str> {
1736        match self.target_config.get(&target)?.profiler.as_ref()? {
1737            StringOrBool::String(s) => Some(s),
1738            StringOrBool::Bool(_) => None,
1739        }
1740    }
1741
1742    pub fn profiler_enabled(&self, target: TargetSelection) -> bool {
1743        self.target_config
1744            .get(&target)
1745            .and_then(|t| t.profiler.as_ref())
1746            .map(StringOrBool::is_string_or_true)
1747            .unwrap_or(self.profiler)
1748    }
1749
1750    pub fn codegen_backends(&self, target: TargetSelection) -> &[CodegenBackendKind] {
1751        self.target_config
1752            .get(&target)
1753            .and_then(|cfg| cfg.codegen_backends.as_deref())
1754            .unwrap_or(&self.rust_codegen_backends)
1755    }
1756
1757    pub fn jemalloc(&self, target: TargetSelection) -> bool {
1758        self.target_config.get(&target).and_then(|cfg| cfg.jemalloc).unwrap_or(self.jemalloc)
1759    }
1760
1761    pub fn default_codegen_backend(&self, target: TargetSelection) -> Option<CodegenBackendKind> {
1762        self.codegen_backends(target).first().cloned()
1763    }
1764
1765    pub fn rpath_enabled(&self, target: TargetSelection) -> bool {
1766        self.target_config.get(&target).and_then(|t| t.rpath).unwrap_or(self.rust_rpath)
1767    }
1768
1769    pub fn optimized_compiler_builtins(&self, target: TargetSelection) -> bool {
1770        self.target_config
1771            .get(&target)
1772            .and_then(|t| t.optimized_compiler_builtins)
1773            .unwrap_or(self.optimized_compiler_builtins)
1774    }
1775
1776    pub fn llvm_enabled(&self, target: TargetSelection) -> bool {
1777        self.codegen_backends(target).contains(&CodegenBackendKind::Llvm)
1778    }
1779
1780    pub fn llvm_libunwind(&self, target: TargetSelection) -> LlvmLibunwind {
1781        self.target_config
1782            .get(&target)
1783            .and_then(|t| t.llvm_libunwind)
1784            .or(self.llvm_libunwind_default)
1785            .unwrap_or(if target.contains("fuchsia") {
1786                LlvmLibunwind::InTree
1787            } else {
1788                LlvmLibunwind::No
1789            })
1790    }
1791
1792    pub fn split_debuginfo(&self, target: TargetSelection) -> SplitDebuginfo {
1793        self.target_config
1794            .get(&target)
1795            .and_then(|t| t.split_debuginfo)
1796            .unwrap_or_else(|| SplitDebuginfo::default_for_platform(target))
1797    }
1798
1799    /// Checks if the given target is the same as the host target.
1800    pub fn is_host_target(&self, target: TargetSelection) -> bool {
1801        self.host_target == target
1802    }
1803
1804    /// Returns `true` if this is an external version of LLVM not managed by bootstrap.
1805    /// In particular, we expect llvm sources to be available when this is false.
1806    ///
1807    /// NOTE: this is not the same as `!is_rust_llvm` when `llvm_has_patches` is set.
1808    pub fn is_system_llvm(&self, target: TargetSelection) -> bool {
1809        match self.target_config.get(&target) {
1810            Some(Target { llvm_config: Some(_), .. }) => {
1811                let ci_llvm = self.llvm_from_ci && self.is_host_target(target);
1812                !ci_llvm
1813            }
1814            // We're building from the in-tree src/llvm-project sources.
1815            Some(Target { llvm_config: None, .. }) => false,
1816            None => false,
1817        }
1818    }
1819
1820    /// Returns `true` if this is our custom, patched, version of LLVM.
1821    ///
1822    /// This does not necessarily imply that we're managing the `llvm-project` submodule.
1823    pub fn is_rust_llvm(&self, target: TargetSelection) -> bool {
1824        match self.target_config.get(&target) {
1825            // We're using a user-controlled version of LLVM. The user has explicitly told us whether the version has our patches.
1826            // (They might be wrong, but that's not a supported use-case.)
1827            // In particular, this tries to support `submodules = false` and `patches = false`, for using a newer version of LLVM that's not through `rust-lang/llvm-project`.
1828            Some(Target { llvm_has_rust_patches: Some(patched), .. }) => *patched,
1829            // The user hasn't promised the patches match.
1830            // This only has our patches if it's downloaded from CI or built from source.
1831            _ => !self.is_system_llvm(target),
1832        }
1833    }
1834
1835    pub fn exec_ctx(&self) -> &ExecutionContext {
1836        &self.exec_ctx
1837    }
1838
1839    pub fn git_info(&self, omit_git_hash: bool, dir: &Path) -> GitInfo {
1840        GitInfo::new(omit_git_hash, dir, self)
1841    }
1842}
1843
1844impl AsRef<ExecutionContext> for Config {
1845    fn as_ref(&self) -> &ExecutionContext {
1846        &self.exec_ctx
1847    }
1848}