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