bootstrap/core/config/
config.rs

1//! Serialized configuration of a build.
2//!
3//! This module implements parsing `bootstrap.toml` configuration files to tweak
4//! how the build runs.
5
6use std::cell::{Cell, RefCell};
7use std::collections::{BTreeSet, HashMap, HashSet};
8use std::fmt::{self, Display};
9use std::io::IsTerminal;
10use std::path::{Path, PathBuf, absolute};
11use std::process::Command;
12use std::str::FromStr;
13use std::sync::OnceLock;
14use std::{cmp, env, fs};
15
16use build_helper::ci::CiEnv;
17use build_helper::exit;
18use build_helper::git::{GitConfig, get_closest_merge_commit, output_result};
19use serde::{Deserialize, Deserializer};
20use serde_derive::Deserialize;
21#[cfg(feature = "tracing")]
22use tracing::{instrument, span};
23
24use crate::core::build_steps::compile::CODEGEN_BACKEND_PREFIX;
25use crate::core::build_steps::llvm;
26use crate::core::build_steps::llvm::LLVM_INVALIDATION_PATHS;
27pub use crate::core::config::flags::Subcommand;
28use crate::core::config::flags::{Color, Flags, Warnings};
29use crate::core::download::is_download_ci_available;
30use crate::utils::cache::{INTERNER, Interned};
31use crate::utils::channel::{self, GitInfo};
32use crate::utils::helpers::{self, exe, output, t};
33
34/// Each path in this list is considered "allowed" in the `download-rustc="if-unchanged"` logic.
35/// This means they can be modified and changes to these paths should never trigger a compiler build
36/// when "if-unchanged" is set.
37///
38/// NOTE: Paths must have the ":!" prefix to tell git to ignore changes in those paths during
39/// the diff check.
40///
41/// WARNING: Be cautious when adding paths to this list. If a path that influences the compiler build
42/// is added here, it will cause bootstrap to skip necessary rebuilds, which may lead to risky results.
43/// For example, "src/bootstrap" should never be included in this list as it plays a crucial role in the
44/// final output/compiler, which can be significantly affected by changes made to the bootstrap sources.
45#[rustfmt::skip] // We don't want rustfmt to oneline this list
46pub(crate) const RUSTC_IF_UNCHANGED_ALLOWED_PATHS: &[&str] = &[
47    ":!src/tools",
48    ":!src/librustdoc",
49    ":!src/rustdoc-json-types",
50    ":!tests",
51    ":!triagebot.toml",
52];
53
54macro_rules! check_ci_llvm {
55    ($name:expr) => {
56        assert!(
57            $name.is_none(),
58            "setting {} is incompatible with download-ci-llvm.",
59            stringify!($name).replace("_", "-")
60        );
61    };
62}
63
64/// This file is embedded in the overlay directory of the tarball sources. It is
65/// useful in scenarios where developers want to see how the tarball sources were
66/// generated.
67///
68/// We also use this file to compare the host's bootstrap.toml against the CI rustc builder
69/// configuration to detect any incompatible options.
70pub(crate) const BUILDER_CONFIG_FILENAME: &str = "builder-config";
71
72#[derive(Clone, Default)]
73pub enum DryRun {
74    /// This isn't a dry run.
75    #[default]
76    Disabled,
77    /// This is a dry run enabled by bootstrap itself, so it can verify that no work is done.
78    SelfCheck,
79    /// This is a dry run enabled by the `--dry-run` flag.
80    UserSelected,
81}
82
83#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
84pub enum DebuginfoLevel {
85    #[default]
86    None,
87    LineDirectivesOnly,
88    LineTablesOnly,
89    Limited,
90    Full,
91}
92
93// NOTE: can't derive(Deserialize) because the intermediate trip through toml::Value only
94// deserializes i64, and derive() only generates visit_u64
95impl<'de> Deserialize<'de> for DebuginfoLevel {
96    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
97    where
98        D: Deserializer<'de>,
99    {
100        use serde::de::Error;
101
102        Ok(match Deserialize::deserialize(deserializer)? {
103            StringOrInt::String(s) if s == "none" => DebuginfoLevel::None,
104            StringOrInt::Int(0) => DebuginfoLevel::None,
105            StringOrInt::String(s) if s == "line-directives-only" => {
106                DebuginfoLevel::LineDirectivesOnly
107            }
108            StringOrInt::String(s) if s == "line-tables-only" => DebuginfoLevel::LineTablesOnly,
109            StringOrInt::String(s) if s == "limited" => DebuginfoLevel::Limited,
110            StringOrInt::Int(1) => DebuginfoLevel::Limited,
111            StringOrInt::String(s) if s == "full" => DebuginfoLevel::Full,
112            StringOrInt::Int(2) => DebuginfoLevel::Full,
113            StringOrInt::Int(n) => {
114                let other = serde::de::Unexpected::Signed(n);
115                return Err(D::Error::invalid_value(other, &"expected 0, 1, or 2"));
116            }
117            StringOrInt::String(s) => {
118                let other = serde::de::Unexpected::Str(&s);
119                return Err(D::Error::invalid_value(
120                    other,
121                    &"expected none, line-tables-only, limited, or full",
122                ));
123            }
124        })
125    }
126}
127
128/// Suitable for passing to `-C debuginfo`
129impl Display for DebuginfoLevel {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        use DebuginfoLevel::*;
132        f.write_str(match self {
133            None => "0",
134            LineDirectivesOnly => "line-directives-only",
135            LineTablesOnly => "line-tables-only",
136            Limited => "1",
137            Full => "2",
138        })
139    }
140}
141
142/// LLD in bootstrap works like this:
143/// - Self-contained lld: use `rust-lld` from the compiler's sysroot
144/// - External: use an external `lld` binary
145///
146/// It is configured depending on the target:
147/// 1) Everything except MSVC
148/// - Self-contained: `-Clinker-flavor=gnu-lld-cc -Clink-self-contained=+linker`
149/// - External: `-Clinker-flavor=gnu-lld-cc`
150/// 2) MSVC
151/// - Self-contained: `-Clinker=<path to rust-lld>`
152/// - External: `-Clinker=lld`
153#[derive(Copy, Clone, Default, Debug, PartialEq)]
154pub enum LldMode {
155    /// Do not use LLD
156    #[default]
157    Unused,
158    /// Use `rust-lld` from the compiler's sysroot
159    SelfContained,
160    /// Use an externally provided `lld` binary.
161    /// Note that the linker name cannot be overridden, the binary has to be named `lld` and it has
162    /// to be in $PATH.
163    External,
164}
165
166impl LldMode {
167    pub fn is_used(&self) -> bool {
168        match self {
169            LldMode::SelfContained | LldMode::External => true,
170            LldMode::Unused => false,
171        }
172    }
173}
174
175/// Determines how will GCC be provided.
176#[derive(Default, Clone)]
177pub enum GccCiMode {
178    /// Build GCC from the local `src/gcc` submodule.
179    #[default]
180    BuildLocally,
181    /// Try to download GCC from CI.
182    /// If it is not available on CI, it will be built locally instead.
183    DownloadFromCi,
184}
185
186/// Global configuration for the entire build and/or bootstrap.
187///
188/// This structure is parsed from `bootstrap.toml`, and some of the fields are inferred from `git` or build-time parameters.
189///
190/// Note that this structure is not decoded directly into, but rather it is
191/// filled out from the decoded forms of the structs below. For documentation
192/// each field, see the corresponding fields in
193/// `bootstrap.example.toml`.
194#[derive(Default, Clone)]
195pub struct Config {
196    pub change_id: Option<usize>,
197    pub bypass_bootstrap_lock: bool,
198    pub ccache: Option<String>,
199    /// Call Build::ninja() instead of this.
200    pub ninja_in_file: bool,
201    pub verbose: usize,
202    pub submodules: Option<bool>,
203    pub compiler_docs: bool,
204    pub library_docs_private_items: bool,
205    pub docs_minification: bool,
206    pub docs: bool,
207    pub locked_deps: bool,
208    pub vendor: bool,
209    pub target_config: HashMap<TargetSelection, Target>,
210    pub full_bootstrap: bool,
211    pub bootstrap_cache_path: Option<PathBuf>,
212    pub extended: bool,
213    pub tools: Option<HashSet<String>>,
214    pub sanitizers: bool,
215    pub profiler: bool,
216    pub omit_git_hash: bool,
217    pub skip: Vec<PathBuf>,
218    pub include_default_paths: bool,
219    pub rustc_error_format: Option<String>,
220    pub json_output: bool,
221    pub test_compare_mode: bool,
222    pub color: Color,
223    pub patch_binaries_for_nix: Option<bool>,
224    pub stage0_metadata: build_helper::stage0_parser::Stage0,
225    pub android_ndk: Option<PathBuf>,
226    /// Whether to use the `c` feature of the `compiler_builtins` crate.
227    pub optimized_compiler_builtins: bool,
228
229    pub stdout_is_tty: bool,
230    pub stderr_is_tty: bool,
231
232    pub on_fail: Option<String>,
233    pub explicit_stage_from_cli: bool,
234    pub explicit_stage_from_config: bool,
235    pub stage: u32,
236    pub keep_stage: Vec<u32>,
237    pub keep_stage_std: Vec<u32>,
238    pub src: PathBuf,
239    /// defaults to `bootstrap.toml`
240    pub config: Option<PathBuf>,
241    pub jobs: Option<u32>,
242    pub cmd: Subcommand,
243    pub incremental: bool,
244    pub dry_run: DryRun,
245    pub dump_bootstrap_shims: bool,
246    /// Arguments appearing after `--` to be forwarded to tools,
247    /// e.g. `--fix-broken` or test arguments.
248    pub free_args: Vec<String>,
249
250    /// `None` if we shouldn't download CI compiler artifacts, or the commit to download if we should.
251    #[cfg(not(test))]
252    download_rustc_commit: Option<String>,
253    #[cfg(test)]
254    pub download_rustc_commit: Option<String>,
255
256    pub deny_warnings: bool,
257    pub backtrace_on_ice: bool,
258
259    // llvm codegen options
260    pub llvm_assertions: bool,
261    pub llvm_tests: bool,
262    pub llvm_enzyme: bool,
263    pub llvm_offload: bool,
264    pub llvm_plugins: bool,
265    pub llvm_optimize: bool,
266    pub llvm_thin_lto: bool,
267    pub llvm_release_debuginfo: bool,
268    pub llvm_static_stdcpp: bool,
269    pub llvm_libzstd: bool,
270    /// `None` if `llvm_from_ci` is true and we haven't yet downloaded llvm.
271    #[cfg(not(test))]
272    llvm_link_shared: Cell<Option<bool>>,
273    #[cfg(test)]
274    pub llvm_link_shared: Cell<Option<bool>>,
275    pub llvm_clang_cl: Option<String>,
276    pub llvm_targets: Option<String>,
277    pub llvm_experimental_targets: Option<String>,
278    pub llvm_link_jobs: Option<u32>,
279    pub llvm_version_suffix: Option<String>,
280    pub llvm_use_linker: Option<String>,
281    pub llvm_allow_old_toolchain: bool,
282    pub llvm_polly: bool,
283    pub llvm_clang: bool,
284    pub llvm_enable_warnings: bool,
285    pub llvm_from_ci: bool,
286    pub llvm_build_config: HashMap<String, String>,
287
288    pub lld_mode: LldMode,
289    pub lld_enabled: bool,
290    pub llvm_tools_enabled: bool,
291    pub llvm_bitcode_linker_enabled: bool,
292
293    pub llvm_cflags: Option<String>,
294    pub llvm_cxxflags: Option<String>,
295    pub llvm_ldflags: Option<String>,
296    pub llvm_use_libcxx: bool,
297
298    // gcc codegen options
299    pub gcc_ci_mode: GccCiMode,
300
301    // rust codegen options
302    pub rust_optimize: RustOptimize,
303    pub rust_codegen_units: Option<u32>,
304    pub rust_codegen_units_std: Option<u32>,
305
306    pub rustc_debug_assertions: bool,
307    pub std_debug_assertions: bool,
308
309    pub rust_overflow_checks: bool,
310    pub rust_overflow_checks_std: bool,
311    pub rust_debug_logging: bool,
312    pub rust_debuginfo_level_rustc: DebuginfoLevel,
313    pub rust_debuginfo_level_std: DebuginfoLevel,
314    pub rust_debuginfo_level_tools: DebuginfoLevel,
315    pub rust_debuginfo_level_tests: DebuginfoLevel,
316    pub rust_rpath: bool,
317    pub rust_strip: bool,
318    pub rust_frame_pointers: bool,
319    pub rust_stack_protector: Option<String>,
320    pub rustc_default_linker: Option<String>,
321    pub rust_optimize_tests: bool,
322    pub rust_dist_src: bool,
323    pub rust_codegen_backends: Vec<String>,
324    pub rust_verify_llvm_ir: bool,
325    pub rust_thin_lto_import_instr_limit: Option<u32>,
326    pub rust_randomize_layout: bool,
327    pub rust_remap_debuginfo: bool,
328    pub rust_new_symbol_mangling: Option<bool>,
329    pub rust_profile_use: Option<String>,
330    pub rust_profile_generate: Option<String>,
331    pub rust_lto: RustcLto,
332    pub rust_validate_mir_opts: Option<u32>,
333    pub rust_std_features: BTreeSet<String>,
334    pub llvm_profile_use: Option<String>,
335    pub llvm_profile_generate: bool,
336    pub llvm_libunwind_default: Option<LlvmLibunwind>,
337    pub enable_bolt_settings: bool,
338
339    pub reproducible_artifacts: Vec<String>,
340
341    pub build: TargetSelection,
342    pub hosts: Vec<TargetSelection>,
343    pub targets: Vec<TargetSelection>,
344    pub local_rebuild: bool,
345    #[cfg(not(test))]
346    jemalloc: bool,
347    #[cfg(test)]
348    pub jemalloc: bool,
349    pub control_flow_guard: bool,
350    pub ehcont_guard: bool,
351
352    // dist misc
353    pub dist_sign_folder: Option<PathBuf>,
354    pub dist_upload_addr: Option<String>,
355    pub dist_compression_formats: Option<Vec<String>>,
356    pub dist_compression_profile: String,
357    pub dist_include_mingw_linker: bool,
358    pub dist_vendor: bool,
359
360    // libstd features
361    pub backtrace: bool, // support for RUST_BACKTRACE
362
363    // misc
364    pub low_priority: bool,
365    pub channel: String,
366    pub description: Option<String>,
367    pub verbose_tests: bool,
368    pub save_toolstates: Option<PathBuf>,
369    pub print_step_timings: bool,
370    pub print_step_rusage: bool,
371
372    // Fallback musl-root for all targets
373    pub musl_root: Option<PathBuf>,
374    pub prefix: Option<PathBuf>,
375    pub sysconfdir: Option<PathBuf>,
376    pub datadir: Option<PathBuf>,
377    pub docdir: Option<PathBuf>,
378    pub bindir: PathBuf,
379    pub libdir: Option<PathBuf>,
380    pub mandir: Option<PathBuf>,
381    pub codegen_tests: bool,
382    pub nodejs: Option<PathBuf>,
383    pub npm: Option<PathBuf>,
384    pub gdb: Option<PathBuf>,
385    pub lldb: Option<PathBuf>,
386    pub python: Option<PathBuf>,
387    pub reuse: Option<PathBuf>,
388    pub cargo_native_static: bool,
389    pub configure_args: Vec<String>,
390    pub out: PathBuf,
391    pub rust_info: channel::GitInfo,
392
393    pub cargo_info: channel::GitInfo,
394    pub rust_analyzer_info: channel::GitInfo,
395    pub clippy_info: channel::GitInfo,
396    pub miri_info: channel::GitInfo,
397    pub rustfmt_info: channel::GitInfo,
398    pub enzyme_info: channel::GitInfo,
399    pub in_tree_llvm_info: channel::GitInfo,
400    pub in_tree_gcc_info: channel::GitInfo,
401
402    // These are either the stage0 downloaded binaries or the locally installed ones.
403    pub initial_cargo: PathBuf,
404    pub initial_rustc: PathBuf,
405    pub initial_cargo_clippy: Option<PathBuf>,
406    pub initial_sysroot: PathBuf,
407
408    #[cfg(not(test))]
409    initial_rustfmt: RefCell<RustfmtState>,
410    #[cfg(test)]
411    pub initial_rustfmt: RefCell<RustfmtState>,
412
413    /// The paths to work with. For example: with `./x check foo bar` we get
414    /// `paths=["foo", "bar"]`.
415    pub paths: Vec<PathBuf>,
416
417    /// Command for visual diff display, e.g. `diff-tool --color=always`.
418    pub compiletest_diff_tool: Option<String>,
419
420    pub is_running_on_ci: bool,
421}
422
423#[derive(Clone, Debug, Default)]
424pub enum RustfmtState {
425    SystemToolchain(PathBuf),
426    Downloaded(PathBuf),
427    Unavailable,
428    #[default]
429    LazyEvaluated,
430}
431
432#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
433pub enum LlvmLibunwind {
434    #[default]
435    No,
436    InTree,
437    System,
438}
439
440impl FromStr for LlvmLibunwind {
441    type Err = String;
442
443    fn from_str(value: &str) -> Result<Self, Self::Err> {
444        match value {
445            "no" => Ok(Self::No),
446            "in-tree" => Ok(Self::InTree),
447            "system" => Ok(Self::System),
448            invalid => Err(format!("Invalid value '{invalid}' for rust.llvm-libunwind config.")),
449        }
450    }
451}
452
453#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
454pub enum SplitDebuginfo {
455    Packed,
456    Unpacked,
457    #[default]
458    Off,
459}
460
461impl std::str::FromStr for SplitDebuginfo {
462    type Err = ();
463
464    fn from_str(s: &str) -> Result<Self, Self::Err> {
465        match s {
466            "packed" => Ok(SplitDebuginfo::Packed),
467            "unpacked" => Ok(SplitDebuginfo::Unpacked),
468            "off" => Ok(SplitDebuginfo::Off),
469            _ => Err(()),
470        }
471    }
472}
473
474impl SplitDebuginfo {
475    /// Returns the default `-Csplit-debuginfo` value for the current target. See the comment for
476    /// `rust.split-debuginfo` in `bootstrap.example.toml`.
477    fn default_for_platform(target: TargetSelection) -> Self {
478        if target.contains("apple") {
479            SplitDebuginfo::Unpacked
480        } else if target.is_windows() {
481            SplitDebuginfo::Packed
482        } else {
483            SplitDebuginfo::Off
484        }
485    }
486}
487
488/// LTO mode used for compiling rustc itself.
489#[derive(Default, Clone, PartialEq, Debug)]
490pub enum RustcLto {
491    Off,
492    #[default]
493    ThinLocal,
494    Thin,
495    Fat,
496}
497
498impl std::str::FromStr for RustcLto {
499    type Err = String;
500
501    fn from_str(s: &str) -> Result<Self, Self::Err> {
502        match s {
503            "thin-local" => Ok(RustcLto::ThinLocal),
504            "thin" => Ok(RustcLto::Thin),
505            "fat" => Ok(RustcLto::Fat),
506            "off" => Ok(RustcLto::Off),
507            _ => Err(format!("Invalid value for rustc LTO: {s}")),
508        }
509    }
510}
511
512#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
513// N.B.: This type is used everywhere, and the entire codebase relies on it being Copy.
514// Making !Copy is highly nontrivial!
515pub struct TargetSelection {
516    pub triple: Interned<String>,
517    file: Option<Interned<String>>,
518    synthetic: bool,
519}
520
521/// Newtype over `Vec<TargetSelection>` so we can implement custom parsing logic
522#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
523pub struct TargetSelectionList(Vec<TargetSelection>);
524
525pub fn target_selection_list(s: &str) -> Result<TargetSelectionList, String> {
526    Ok(TargetSelectionList(
527        s.split(',').filter(|s| !s.is_empty()).map(TargetSelection::from_user).collect(),
528    ))
529}
530
531impl TargetSelection {
532    pub fn from_user(selection: &str) -> Self {
533        let path = Path::new(selection);
534
535        let (triple, file) = if path.exists() {
536            let triple = path
537                .file_stem()
538                .expect("Target specification file has no file stem")
539                .to_str()
540                .expect("Target specification file stem is not UTF-8");
541
542            (triple, Some(selection))
543        } else {
544            (selection, None)
545        };
546
547        let triple = INTERNER.intern_str(triple);
548        let file = file.map(|f| INTERNER.intern_str(f));
549
550        Self { triple, file, synthetic: false }
551    }
552
553    pub fn create_synthetic(triple: &str, file: &str) -> Self {
554        Self {
555            triple: INTERNER.intern_str(triple),
556            file: Some(INTERNER.intern_str(file)),
557            synthetic: true,
558        }
559    }
560
561    pub fn rustc_target_arg(&self) -> &str {
562        self.file.as_ref().unwrap_or(&self.triple)
563    }
564
565    pub fn contains(&self, needle: &str) -> bool {
566        self.triple.contains(needle)
567    }
568
569    pub fn starts_with(&self, needle: &str) -> bool {
570        self.triple.starts_with(needle)
571    }
572
573    pub fn ends_with(&self, needle: &str) -> bool {
574        self.triple.ends_with(needle)
575    }
576
577    // See src/bootstrap/synthetic_targets.rs
578    pub fn is_synthetic(&self) -> bool {
579        self.synthetic
580    }
581
582    pub fn is_msvc(&self) -> bool {
583        self.contains("msvc")
584    }
585
586    pub fn is_windows(&self) -> bool {
587        self.contains("windows")
588    }
589
590    pub fn is_windows_gnu(&self) -> bool {
591        self.ends_with("windows-gnu")
592    }
593
594    pub fn is_cygwin(&self) -> bool {
595        self.is_windows() &&
596        // ref. https://cygwin.com/pipermail/cygwin/2022-February/250802.html
597        env::var("OSTYPE").is_ok_and(|v| v.to_lowercase().contains("cygwin"))
598    }
599
600    pub fn needs_crt_begin_end(&self) -> bool {
601        self.contains("musl") && !self.contains("unikraft")
602    }
603
604    /// Path to the file defining the custom target, if any.
605    pub fn filepath(&self) -> Option<&Path> {
606        self.file.as_ref().map(Path::new)
607    }
608}
609
610impl fmt::Display for TargetSelection {
611    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
612        write!(f, "{}", self.triple)?;
613        if let Some(file) = self.file {
614            write!(f, "({file})")?;
615        }
616        Ok(())
617    }
618}
619
620impl fmt::Debug for TargetSelection {
621    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
622        write!(f, "{self}")
623    }
624}
625
626impl PartialEq<&str> for TargetSelection {
627    fn eq(&self, other: &&str) -> bool {
628        self.triple == *other
629    }
630}
631
632// Targets are often used as directory names throughout bootstrap.
633// This impl makes it more ergonomics to use them as such.
634impl AsRef<Path> for TargetSelection {
635    fn as_ref(&self) -> &Path {
636        self.triple.as_ref()
637    }
638}
639
640/// Per-target configuration stored in the global configuration structure.
641#[derive(Debug, Default, Clone, PartialEq, Eq)]
642pub struct Target {
643    /// Some(path to llvm-config) if using an external LLVM.
644    pub llvm_config: Option<PathBuf>,
645    pub llvm_has_rust_patches: Option<bool>,
646    /// Some(path to FileCheck) if one was specified.
647    pub llvm_filecheck: Option<PathBuf>,
648    pub llvm_libunwind: Option<LlvmLibunwind>,
649    pub cc: Option<PathBuf>,
650    pub cxx: Option<PathBuf>,
651    pub ar: Option<PathBuf>,
652    pub ranlib: Option<PathBuf>,
653    pub default_linker: Option<PathBuf>,
654    pub linker: Option<PathBuf>,
655    pub split_debuginfo: Option<SplitDebuginfo>,
656    pub sanitizers: Option<bool>,
657    pub profiler: Option<StringOrBool>,
658    pub rpath: Option<bool>,
659    pub crt_static: Option<bool>,
660    pub musl_root: Option<PathBuf>,
661    pub musl_libdir: Option<PathBuf>,
662    pub wasi_root: Option<PathBuf>,
663    pub qemu_rootfs: Option<PathBuf>,
664    pub runner: Option<String>,
665    pub no_std: bool,
666    pub codegen_backends: Option<Vec<String>>,
667    pub optimized_compiler_builtins: Option<bool>,
668    pub jemalloc: Option<bool>,
669}
670
671impl Target {
672    pub fn from_triple(triple: &str) -> Self {
673        let mut target: Self = Default::default();
674        if triple.contains("-none") || triple.contains("nvptx") || triple.contains("switch") {
675            target.no_std = true;
676        }
677        if triple.contains("emscripten") {
678            target.runner = Some("node".into());
679        }
680        target
681    }
682}
683/// Structure of the `bootstrap.toml` file that configuration is read from.
684///
685/// This structure uses `Decodable` to automatically decode a TOML configuration
686/// file into this format, and then this is traversed and written into the above
687/// `Config` structure.
688#[derive(Deserialize, Default)]
689#[serde(deny_unknown_fields, rename_all = "kebab-case")]
690pub(crate) struct TomlConfig {
691    #[serde(flatten)]
692    change_id: ChangeIdWrapper,
693    build: Option<Build>,
694    install: Option<Install>,
695    llvm: Option<Llvm>,
696    gcc: Option<Gcc>,
697    rust: Option<Rust>,
698    target: Option<HashMap<String, TomlTarget>>,
699    dist: Option<Dist>,
700    profile: Option<String>,
701}
702
703/// Since we use `#[serde(deny_unknown_fields)]` on `TomlConfig`, we need a wrapper type
704/// for the "change-id" field to parse it even if other fields are invalid. This ensures
705/// that if deserialization fails due to other fields, we can still provide the changelogs
706/// to allow developers to potentially find the reason for the failure in the logs..
707#[derive(Deserialize, Default)]
708pub(crate) struct ChangeIdWrapper {
709    #[serde(alias = "change-id")]
710    pub(crate) inner: Option<usize>,
711}
712
713/// Describes how to handle conflicts in merging two [`TomlConfig`]
714#[derive(Copy, Clone, Debug)]
715enum ReplaceOpt {
716    /// Silently ignore a duplicated value
717    IgnoreDuplicate,
718    /// Override the current value, even if it's `Some`
719    Override,
720    /// Exit with an error on duplicate values
721    ErrorOnDuplicate,
722}
723
724trait Merge {
725    fn merge(&mut self, other: Self, replace: ReplaceOpt);
726}
727
728impl Merge for TomlConfig {
729    fn merge(
730        &mut self,
731        TomlConfig { build, install, llvm, gcc, rust, dist, target, profile, change_id }: Self,
732        replace: ReplaceOpt,
733    ) {
734        fn do_merge<T: Merge>(x: &mut Option<T>, y: Option<T>, replace: ReplaceOpt) {
735            if let Some(new) = y {
736                if let Some(original) = x {
737                    original.merge(new, replace);
738                } else {
739                    *x = Some(new);
740                }
741            }
742        }
743
744        self.change_id.inner.merge(change_id.inner, replace);
745        self.profile.merge(profile, replace);
746
747        do_merge(&mut self.build, build, replace);
748        do_merge(&mut self.install, install, replace);
749        do_merge(&mut self.llvm, llvm, replace);
750        do_merge(&mut self.gcc, gcc, replace);
751        do_merge(&mut self.rust, rust, replace);
752        do_merge(&mut self.dist, dist, replace);
753
754        match (self.target.as_mut(), target) {
755            (_, None) => {}
756            (None, Some(target)) => self.target = Some(target),
757            (Some(original_target), Some(new_target)) => {
758                for (triple, new) in new_target {
759                    if let Some(original) = original_target.get_mut(&triple) {
760                        original.merge(new, replace);
761                    } else {
762                        original_target.insert(triple, new);
763                    }
764                }
765            }
766        }
767    }
768}
769
770// We are using a decl macro instead of a derive proc macro here to reduce the compile time of bootstrap.
771macro_rules! define_config {
772    ($(#[$attr:meta])* struct $name:ident {
773        $($field:ident: Option<$field_ty:ty> = $field_key:literal,)*
774    }) => {
775        $(#[$attr])*
776        struct $name {
777            $($field: Option<$field_ty>,)*
778        }
779
780        impl Merge for $name {
781            fn merge(&mut self, other: Self, replace: ReplaceOpt) {
782                $(
783                    match replace {
784                        ReplaceOpt::IgnoreDuplicate => {
785                            if self.$field.is_none() {
786                                self.$field = other.$field;
787                            }
788                        },
789                        ReplaceOpt::Override => {
790                            if other.$field.is_some() {
791                                self.$field = other.$field;
792                            }
793                        }
794                        ReplaceOpt::ErrorOnDuplicate => {
795                            if other.$field.is_some() {
796                                if self.$field.is_some() {
797                                    if cfg!(test) {
798                                        panic!("overriding existing option")
799                                    } else {
800                                        eprintln!("overriding existing option: `{}`", stringify!($field));
801                                        exit!(2);
802                                    }
803                                } else {
804                                    self.$field = other.$field;
805                                }
806                            }
807                        }
808                    }
809                )*
810            }
811        }
812
813        // The following is a trimmed version of what serde_derive generates. All parts not relevant
814        // for toml deserialization have been removed. This reduces the binary size and improves
815        // compile time of bootstrap.
816        impl<'de> Deserialize<'de> for $name {
817            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
818            where
819                D: Deserializer<'de>,
820            {
821                struct Field;
822                impl<'de> serde::de::Visitor<'de> for Field {
823                    type Value = $name;
824                    fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
825                        f.write_str(concat!("struct ", stringify!($name)))
826                    }
827
828                    #[inline]
829                    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
830                    where
831                        A: serde::de::MapAccess<'de>,
832                    {
833                        $(let mut $field: Option<$field_ty> = None;)*
834                        while let Some(key) =
835                            match serde::de::MapAccess::next_key::<String>(&mut map) {
836                                Ok(val) => val,
837                                Err(err) => {
838                                    return Err(err);
839                                }
840                            }
841                        {
842                            match &*key {
843                                $($field_key => {
844                                    if $field.is_some() {
845                                        return Err(<A::Error as serde::de::Error>::duplicate_field(
846                                            $field_key,
847                                        ));
848                                    }
849                                    $field = match serde::de::MapAccess::next_value::<$field_ty>(
850                                        &mut map,
851                                    ) {
852                                        Ok(val) => Some(val),
853                                        Err(err) => {
854                                            return Err(err);
855                                        }
856                                    };
857                                })*
858                                key => {
859                                    return Err(serde::de::Error::unknown_field(key, FIELDS));
860                                }
861                            }
862                        }
863                        Ok($name { $($field),* })
864                    }
865                }
866                const FIELDS: &'static [&'static str] = &[
867                    $($field_key,)*
868                ];
869                Deserializer::deserialize_struct(
870                    deserializer,
871                    stringify!($name),
872                    FIELDS,
873                    Field,
874                )
875            }
876        }
877    }
878}
879
880impl<T> Merge for Option<T> {
881    fn merge(&mut self, other: Self, replace: ReplaceOpt) {
882        match replace {
883            ReplaceOpt::IgnoreDuplicate => {
884                if self.is_none() {
885                    *self = other;
886                }
887            }
888            ReplaceOpt::Override => {
889                if other.is_some() {
890                    *self = other;
891                }
892            }
893            ReplaceOpt::ErrorOnDuplicate => {
894                if other.is_some() {
895                    if self.is_some() {
896                        if cfg!(test) {
897                            panic!("overriding existing option")
898                        } else {
899                            eprintln!("overriding existing option");
900                            exit!(2);
901                        }
902                    } else {
903                        *self = other;
904                    }
905                }
906            }
907        }
908    }
909}
910
911define_config! {
912    /// TOML representation of various global build decisions.
913    #[derive(Default)]
914    struct Build {
915        build: Option<String> = "build",
916        description: Option<String> = "description",
917        host: Option<Vec<String>> = "host",
918        target: Option<Vec<String>> = "target",
919        build_dir: Option<String> = "build-dir",
920        cargo: Option<PathBuf> = "cargo",
921        rustc: Option<PathBuf> = "rustc",
922        rustfmt: Option<PathBuf> = "rustfmt",
923        cargo_clippy: Option<PathBuf> = "cargo-clippy",
924        docs: Option<bool> = "docs",
925        compiler_docs: Option<bool> = "compiler-docs",
926        library_docs_private_items: Option<bool> = "library-docs-private-items",
927        docs_minification: Option<bool> = "docs-minification",
928        submodules: Option<bool> = "submodules",
929        gdb: Option<String> = "gdb",
930        lldb: Option<String> = "lldb",
931        nodejs: Option<String> = "nodejs",
932        npm: Option<String> = "npm",
933        python: Option<String> = "python",
934        reuse: Option<String> = "reuse",
935        locked_deps: Option<bool> = "locked-deps",
936        vendor: Option<bool> = "vendor",
937        full_bootstrap: Option<bool> = "full-bootstrap",
938        bootstrap_cache_path: Option<PathBuf> = "bootstrap-cache-path",
939        extended: Option<bool> = "extended",
940        tools: Option<HashSet<String>> = "tools",
941        verbose: Option<usize> = "verbose",
942        sanitizers: Option<bool> = "sanitizers",
943        profiler: Option<bool> = "profiler",
944        cargo_native_static: Option<bool> = "cargo-native-static",
945        low_priority: Option<bool> = "low-priority",
946        configure_args: Option<Vec<String>> = "configure-args",
947        local_rebuild: Option<bool> = "local-rebuild",
948        print_step_timings: Option<bool> = "print-step-timings",
949        print_step_rusage: Option<bool> = "print-step-rusage",
950        check_stage: Option<u32> = "check-stage",
951        doc_stage: Option<u32> = "doc-stage",
952        build_stage: Option<u32> = "build-stage",
953        test_stage: Option<u32> = "test-stage",
954        install_stage: Option<u32> = "install-stage",
955        dist_stage: Option<u32> = "dist-stage",
956        bench_stage: Option<u32> = "bench-stage",
957        patch_binaries_for_nix: Option<bool> = "patch-binaries-for-nix",
958        // NOTE: only parsed by bootstrap.py, `--feature build-metrics` enables metrics unconditionally
959        metrics: Option<bool> = "metrics",
960        android_ndk: Option<PathBuf> = "android-ndk",
961        optimized_compiler_builtins: Option<bool> = "optimized-compiler-builtins",
962        jobs: Option<u32> = "jobs",
963        compiletest_diff_tool: Option<String> = "compiletest-diff-tool",
964        ccache: Option<StringOrBool> = "ccache",
965        exclude: Option<Vec<PathBuf>> = "exclude",
966    }
967}
968
969define_config! {
970    /// TOML representation of various global install decisions.
971    struct Install {
972        prefix: Option<String> = "prefix",
973        sysconfdir: Option<String> = "sysconfdir",
974        docdir: Option<String> = "docdir",
975        bindir: Option<String> = "bindir",
976        libdir: Option<String> = "libdir",
977        mandir: Option<String> = "mandir",
978        datadir: Option<String> = "datadir",
979    }
980}
981
982define_config! {
983    /// TOML representation of how the LLVM build is configured.
984    struct Llvm {
985        optimize: Option<bool> = "optimize",
986        thin_lto: Option<bool> = "thin-lto",
987        release_debuginfo: Option<bool> = "release-debuginfo",
988        assertions: Option<bool> = "assertions",
989        tests: Option<bool> = "tests",
990        enzyme: Option<bool> = "enzyme",
991        plugins: Option<bool> = "plugins",
992        // FIXME: Remove this field at Q2 2025, it has been replaced by build.ccache
993        ccache: Option<StringOrBool> = "ccache",
994        static_libstdcpp: Option<bool> = "static-libstdcpp",
995        libzstd: Option<bool> = "libzstd",
996        ninja: Option<bool> = "ninja",
997        targets: Option<String> = "targets",
998        experimental_targets: Option<String> = "experimental-targets",
999        link_jobs: Option<u32> = "link-jobs",
1000        link_shared: Option<bool> = "link-shared",
1001        version_suffix: Option<String> = "version-suffix",
1002        clang_cl: Option<String> = "clang-cl",
1003        cflags: Option<String> = "cflags",
1004        cxxflags: Option<String> = "cxxflags",
1005        ldflags: Option<String> = "ldflags",
1006        use_libcxx: Option<bool> = "use-libcxx",
1007        use_linker: Option<String> = "use-linker",
1008        allow_old_toolchain: Option<bool> = "allow-old-toolchain",
1009        offload: Option<bool> = "offload",
1010        polly: Option<bool> = "polly",
1011        clang: Option<bool> = "clang",
1012        enable_warnings: Option<bool> = "enable-warnings",
1013        download_ci_llvm: Option<StringOrBool> = "download-ci-llvm",
1014        build_config: Option<HashMap<String, String>> = "build-config",
1015    }
1016}
1017
1018define_config! {
1019    /// TOML representation of how the GCC build is configured.
1020    struct Gcc {
1021        download_ci_gcc: Option<bool> = "download-ci-gcc",
1022    }
1023}
1024
1025define_config! {
1026    struct Dist {
1027        sign_folder: Option<String> = "sign-folder",
1028        upload_addr: Option<String> = "upload-addr",
1029        src_tarball: Option<bool> = "src-tarball",
1030        compression_formats: Option<Vec<String>> = "compression-formats",
1031        compression_profile: Option<String> = "compression-profile",
1032        include_mingw_linker: Option<bool> = "include-mingw-linker",
1033        vendor: Option<bool> = "vendor",
1034    }
1035}
1036
1037#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
1038#[serde(untagged)]
1039pub enum StringOrBool {
1040    String(String),
1041    Bool(bool),
1042}
1043
1044impl Default for StringOrBool {
1045    fn default() -> StringOrBool {
1046        StringOrBool::Bool(false)
1047    }
1048}
1049
1050impl StringOrBool {
1051    fn is_string_or_true(&self) -> bool {
1052        matches!(self, Self::String(_) | Self::Bool(true))
1053    }
1054}
1055
1056#[derive(Clone, Debug, PartialEq, Eq)]
1057pub enum RustOptimize {
1058    String(String),
1059    Int(u8),
1060    Bool(bool),
1061}
1062
1063impl Default for RustOptimize {
1064    fn default() -> RustOptimize {
1065        RustOptimize::Bool(false)
1066    }
1067}
1068
1069impl<'de> Deserialize<'de> for RustOptimize {
1070    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1071    where
1072        D: Deserializer<'de>,
1073    {
1074        deserializer.deserialize_any(OptimizeVisitor)
1075    }
1076}
1077
1078struct OptimizeVisitor;
1079
1080impl serde::de::Visitor<'_> for OptimizeVisitor {
1081    type Value = RustOptimize;
1082
1083    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1084        formatter.write_str(r#"one of: 0, 1, 2, 3, "s", "z", true, false"#)
1085    }
1086
1087    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1088    where
1089        E: serde::de::Error,
1090    {
1091        if matches!(value, "s" | "z") {
1092            Ok(RustOptimize::String(value.to_string()))
1093        } else {
1094            Err(serde::de::Error::custom(format_optimize_error_msg(value)))
1095        }
1096    }
1097
1098    fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
1099    where
1100        E: serde::de::Error,
1101    {
1102        if matches!(value, 0..=3) {
1103            Ok(RustOptimize::Int(value as u8))
1104        } else {
1105            Err(serde::de::Error::custom(format_optimize_error_msg(value)))
1106        }
1107    }
1108
1109    fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
1110    where
1111        E: serde::de::Error,
1112    {
1113        Ok(RustOptimize::Bool(value))
1114    }
1115}
1116
1117fn format_optimize_error_msg(v: impl std::fmt::Display) -> String {
1118    format!(
1119        r#"unrecognized option for rust optimize: "{v}", expected one of 0, 1, 2, 3, "s", "z", true, false"#
1120    )
1121}
1122
1123impl RustOptimize {
1124    pub(crate) fn is_release(&self) -> bool {
1125        match &self {
1126            RustOptimize::Bool(true) | RustOptimize::String(_) => true,
1127            RustOptimize::Int(i) => *i > 0,
1128            RustOptimize::Bool(false) => false,
1129        }
1130    }
1131
1132    pub(crate) fn get_opt_level(&self) -> Option<String> {
1133        match &self {
1134            RustOptimize::String(s) => Some(s.clone()),
1135            RustOptimize::Int(i) => Some(i.to_string()),
1136            RustOptimize::Bool(_) => None,
1137        }
1138    }
1139}
1140
1141#[derive(Deserialize)]
1142#[serde(untagged)]
1143enum StringOrInt {
1144    String(String),
1145    Int(i64),
1146}
1147
1148impl<'de> Deserialize<'de> for LldMode {
1149    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1150    where
1151        D: Deserializer<'de>,
1152    {
1153        struct LldModeVisitor;
1154
1155        impl serde::de::Visitor<'_> for LldModeVisitor {
1156            type Value = LldMode;
1157
1158            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1159                formatter.write_str("one of true, 'self-contained' or 'external'")
1160            }
1161
1162            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1163            where
1164                E: serde::de::Error,
1165            {
1166                Ok(if v { LldMode::External } else { LldMode::Unused })
1167            }
1168
1169            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1170            where
1171                E: serde::de::Error,
1172            {
1173                match v {
1174                    "external" => Ok(LldMode::External),
1175                    "self-contained" => Ok(LldMode::SelfContained),
1176                    _ => Err(E::custom(format!("unknown mode {v}"))),
1177                }
1178            }
1179        }
1180
1181        deserializer.deserialize_any(LldModeVisitor)
1182    }
1183}
1184
1185define_config! {
1186    /// TOML representation of how the Rust build is configured.
1187    struct Rust {
1188        optimize: Option<RustOptimize> = "optimize",
1189        debug: Option<bool> = "debug",
1190        codegen_units: Option<u32> = "codegen-units",
1191        codegen_units_std: Option<u32> = "codegen-units-std",
1192        rustc_debug_assertions: Option<bool> = "debug-assertions",
1193        randomize_layout: Option<bool> = "randomize-layout",
1194        std_debug_assertions: Option<bool> = "debug-assertions-std",
1195        overflow_checks: Option<bool> = "overflow-checks",
1196        overflow_checks_std: Option<bool> = "overflow-checks-std",
1197        debug_logging: Option<bool> = "debug-logging",
1198        debuginfo_level: Option<DebuginfoLevel> = "debuginfo-level",
1199        debuginfo_level_rustc: Option<DebuginfoLevel> = "debuginfo-level-rustc",
1200        debuginfo_level_std: Option<DebuginfoLevel> = "debuginfo-level-std",
1201        debuginfo_level_tools: Option<DebuginfoLevel> = "debuginfo-level-tools",
1202        debuginfo_level_tests: Option<DebuginfoLevel> = "debuginfo-level-tests",
1203        backtrace: Option<bool> = "backtrace",
1204        incremental: Option<bool> = "incremental",
1205        default_linker: Option<String> = "default-linker",
1206        channel: Option<String> = "channel",
1207        // FIXME: Remove this field at Q2 2025, it has been replaced by build.description
1208        description: Option<String> = "description",
1209        musl_root: Option<String> = "musl-root",
1210        rpath: Option<bool> = "rpath",
1211        strip: Option<bool> = "strip",
1212        frame_pointers: Option<bool> = "frame-pointers",
1213        stack_protector: Option<String> = "stack-protector",
1214        verbose_tests: Option<bool> = "verbose-tests",
1215        optimize_tests: Option<bool> = "optimize-tests",
1216        codegen_tests: Option<bool> = "codegen-tests",
1217        omit_git_hash: Option<bool> = "omit-git-hash",
1218        dist_src: Option<bool> = "dist-src",
1219        save_toolstates: Option<String> = "save-toolstates",
1220        codegen_backends: Option<Vec<String>> = "codegen-backends",
1221        llvm_bitcode_linker: Option<bool> = "llvm-bitcode-linker",
1222        lld: Option<bool> = "lld",
1223        lld_mode: Option<LldMode> = "use-lld",
1224        llvm_tools: Option<bool> = "llvm-tools",
1225        deny_warnings: Option<bool> = "deny-warnings",
1226        backtrace_on_ice: Option<bool> = "backtrace-on-ice",
1227        verify_llvm_ir: Option<bool> = "verify-llvm-ir",
1228        thin_lto_import_instr_limit: Option<u32> = "thin-lto-import-instr-limit",
1229        remap_debuginfo: Option<bool> = "remap-debuginfo",
1230        jemalloc: Option<bool> = "jemalloc",
1231        test_compare_mode: Option<bool> = "test-compare-mode",
1232        llvm_libunwind: Option<String> = "llvm-libunwind",
1233        control_flow_guard: Option<bool> = "control-flow-guard",
1234        ehcont_guard: Option<bool> = "ehcont-guard",
1235        new_symbol_mangling: Option<bool> = "new-symbol-mangling",
1236        profile_generate: Option<String> = "profile-generate",
1237        profile_use: Option<String> = "profile-use",
1238        // ignored; this is set from an env var set by bootstrap.py
1239        download_rustc: Option<StringOrBool> = "download-rustc",
1240        lto: Option<String> = "lto",
1241        validate_mir_opts: Option<u32> = "validate-mir-opts",
1242        std_features: Option<BTreeSet<String>> = "std-features",
1243    }
1244}
1245
1246define_config! {
1247    /// TOML representation of how each build target is configured.
1248    struct TomlTarget {
1249        cc: Option<String> = "cc",
1250        cxx: Option<String> = "cxx",
1251        ar: Option<String> = "ar",
1252        ranlib: Option<String> = "ranlib",
1253        default_linker: Option<PathBuf> = "default-linker",
1254        linker: Option<String> = "linker",
1255        split_debuginfo: Option<String> = "split-debuginfo",
1256        llvm_config: Option<String> = "llvm-config",
1257        llvm_has_rust_patches: Option<bool> = "llvm-has-rust-patches",
1258        llvm_filecheck: Option<String> = "llvm-filecheck",
1259        llvm_libunwind: Option<String> = "llvm-libunwind",
1260        sanitizers: Option<bool> = "sanitizers",
1261        profiler: Option<StringOrBool> = "profiler",
1262        rpath: Option<bool> = "rpath",
1263        crt_static: Option<bool> = "crt-static",
1264        musl_root: Option<String> = "musl-root",
1265        musl_libdir: Option<String> = "musl-libdir",
1266        wasi_root: Option<String> = "wasi-root",
1267        qemu_rootfs: Option<String> = "qemu-rootfs",
1268        no_std: Option<bool> = "no-std",
1269        codegen_backends: Option<Vec<String>> = "codegen-backends",
1270        runner: Option<String> = "runner",
1271        optimized_compiler_builtins: Option<bool> = "optimized-compiler-builtins",
1272        jemalloc: Option<bool> = "jemalloc",
1273    }
1274}
1275
1276impl Config {
1277    #[cfg_attr(
1278        feature = "tracing",
1279        instrument(target = "CONFIG_HANDLING", level = "trace", name = "Config::default_opts")
1280    )]
1281    pub fn default_opts() -> Config {
1282        #[cfg(feature = "tracing")]
1283        span!(target: "CONFIG_HANDLING", tracing::Level::TRACE, "constructing default config");
1284
1285        Config {
1286            bypass_bootstrap_lock: false,
1287            llvm_optimize: true,
1288            ninja_in_file: true,
1289            llvm_static_stdcpp: false,
1290            llvm_libzstd: false,
1291            backtrace: true,
1292            rust_optimize: RustOptimize::Bool(true),
1293            rust_optimize_tests: true,
1294            rust_randomize_layout: false,
1295            submodules: None,
1296            docs: true,
1297            docs_minification: true,
1298            rust_rpath: true,
1299            rust_strip: false,
1300            channel: "dev".to_string(),
1301            codegen_tests: true,
1302            rust_dist_src: true,
1303            rust_codegen_backends: vec!["llvm".to_owned()],
1304            deny_warnings: true,
1305            bindir: "bin".into(),
1306            dist_include_mingw_linker: true,
1307            dist_compression_profile: "fast".into(),
1308
1309            stdout_is_tty: std::io::stdout().is_terminal(),
1310            stderr_is_tty: std::io::stderr().is_terminal(),
1311
1312            // set by build.rs
1313            build: TargetSelection::from_user(env!("BUILD_TRIPLE")),
1314
1315            src: {
1316                let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1317                // Undo `src/bootstrap`
1318                manifest_dir.parent().unwrap().parent().unwrap().to_owned()
1319            },
1320            out: PathBuf::from("build"),
1321
1322            // This is needed by codegen_ssa on macOS to ship `llvm-objcopy` aliased to
1323            // `rust-objcopy` to workaround bad `strip`s on macOS.
1324            llvm_tools_enabled: true,
1325
1326            ..Default::default()
1327        }
1328    }
1329
1330    pub(crate) fn get_builder_toml(&self, build_name: &str) -> Result<TomlConfig, toml::de::Error> {
1331        if self.dry_run() {
1332            return Ok(TomlConfig::default());
1333        }
1334
1335        let builder_config_path =
1336            self.out.join(self.build.triple).join(build_name).join(BUILDER_CONFIG_FILENAME);
1337        Self::get_toml(&builder_config_path)
1338    }
1339
1340    #[cfg(test)]
1341    pub(crate) fn get_toml(_: &Path) -> Result<TomlConfig, toml::de::Error> {
1342        Ok(TomlConfig::default())
1343    }
1344
1345    #[cfg(not(test))]
1346    pub(crate) fn get_toml(file: &Path) -> Result<TomlConfig, toml::de::Error> {
1347        let contents =
1348            t!(fs::read_to_string(file), format!("config file {} not found", file.display()));
1349        // Deserialize to Value and then TomlConfig to prevent the Deserialize impl of
1350        // TomlConfig and sub types to be monomorphized 5x by toml.
1351        toml::from_str(&contents)
1352            .and_then(|table: toml::Value| TomlConfig::deserialize(table))
1353            .inspect_err(|_| {
1354                if let Ok(Some(changes)) = toml::from_str(&contents)
1355                    .and_then(|table: toml::Value| ChangeIdWrapper::deserialize(table))
1356                    .map(|change_id| change_id.inner.map(crate::find_recent_config_change_ids))
1357                {
1358                    if !changes.is_empty() {
1359                        println!(
1360                            "WARNING: There have been changes to x.py since you last updated:\n{}",
1361                            crate::human_readable_changes(&changes)
1362                        );
1363                    }
1364                }
1365            })
1366    }
1367
1368    #[cfg_attr(
1369        feature = "tracing",
1370        instrument(target = "CONFIG_HANDLING", level = "trace", name = "Config::parse", skip_all)
1371    )]
1372    pub fn parse(flags: Flags) -> Config {
1373        Self::parse_inner(flags, Self::get_toml)
1374    }
1375
1376    #[cfg_attr(
1377        feature = "tracing",
1378        instrument(
1379            target = "CONFIG_HANDLING",
1380            level = "trace",
1381            name = "Config::parse_inner",
1382            skip_all
1383        )
1384    )]
1385    pub(crate) fn parse_inner(
1386        mut flags: Flags,
1387        get_toml: impl Fn(&Path) -> Result<TomlConfig, toml::de::Error>,
1388    ) -> Config {
1389        let mut config = Config::default_opts();
1390
1391        // Set flags.
1392        config.paths = std::mem::take(&mut flags.paths);
1393
1394        #[cfg(feature = "tracing")]
1395        span!(
1396            target: "CONFIG_HANDLING",
1397            tracing::Level::TRACE,
1398            "collecting paths and path exclusions",
1399            "flags.paths" = ?flags.paths,
1400            "flags.skip" = ?flags.skip,
1401            "flags.exclude" = ?flags.exclude
1402        );
1403
1404        #[cfg(feature = "tracing")]
1405        span!(
1406            target: "CONFIG_HANDLING",
1407            tracing::Level::TRACE,
1408            "normalizing and combining `flag.skip`/`flag.exclude` paths",
1409            "config.skip" = ?config.skip,
1410        );
1411
1412        config.include_default_paths = flags.include_default_paths;
1413        config.rustc_error_format = flags.rustc_error_format;
1414        config.json_output = flags.json_output;
1415        config.on_fail = flags.on_fail;
1416        config.cmd = flags.cmd;
1417        config.incremental = flags.incremental;
1418        config.dry_run = if flags.dry_run { DryRun::UserSelected } else { DryRun::Disabled };
1419        config.dump_bootstrap_shims = flags.dump_bootstrap_shims;
1420        config.keep_stage = flags.keep_stage;
1421        config.keep_stage_std = flags.keep_stage_std;
1422        config.color = flags.color;
1423        config.free_args = std::mem::take(&mut flags.free_args);
1424        config.llvm_profile_use = flags.llvm_profile_use;
1425        config.llvm_profile_generate = flags.llvm_profile_generate;
1426        config.enable_bolt_settings = flags.enable_bolt_settings;
1427        config.bypass_bootstrap_lock = flags.bypass_bootstrap_lock;
1428        config.is_running_on_ci = flags.ci.unwrap_or(CiEnv::is_ci());
1429
1430        // Infer the rest of the configuration.
1431
1432        if let Some(src) = flags.src {
1433            config.src = src
1434        } else {
1435            // Infer the source directory. This is non-trivial because we want to support a downloaded bootstrap binary,
1436            // running on a completely different machine from where it was compiled.
1437            let mut cmd = helpers::git(None);
1438            // NOTE: we cannot support running from outside the repository because the only other path we have available
1439            // is set at compile time, which can be wrong if bootstrap was downloaded rather than compiled locally.
1440            // We still support running outside the repository if we find we aren't in a git directory.
1441
1442            // NOTE: We get a relative path from git to work around an issue on MSYS/mingw. If we used an absolute path,
1443            // and end up using MSYS's git rather than git-for-windows, we would get a unix-y MSYS path. But as bootstrap
1444            // has already been (kinda-cross-)compiled to Windows land, we require a normal Windows path.
1445            cmd.arg("rev-parse").arg("--show-cdup");
1446            // Discard stderr because we expect this to fail when building from a tarball.
1447            let output = cmd
1448                .as_command_mut()
1449                .stderr(std::process::Stdio::null())
1450                .output()
1451                .ok()
1452                .and_then(|output| if output.status.success() { Some(output) } else { None });
1453            if let Some(output) = output {
1454                let git_root_relative = String::from_utf8(output.stdout).unwrap();
1455                // We need to canonicalize this path to make sure it uses backslashes instead of forward slashes,
1456                // and to resolve any relative components.
1457                let git_root = env::current_dir()
1458                    .unwrap()
1459                    .join(PathBuf::from(git_root_relative.trim()))
1460                    .canonicalize()
1461                    .unwrap();
1462                let s = git_root.to_str().unwrap();
1463
1464                // Bootstrap is quite bad at handling /? in front of paths
1465                let git_root = match s.strip_prefix("\\\\?\\") {
1466                    Some(p) => PathBuf::from(p),
1467                    None => git_root,
1468                };
1469                // If this doesn't have at least `stage0`, we guessed wrong. This can happen when,
1470                // for example, the build directory is inside of another unrelated git directory.
1471                // In that case keep the original `CARGO_MANIFEST_DIR` handling.
1472                //
1473                // NOTE: this implies that downloadable bootstrap isn't supported when the build directory is outside
1474                // the source directory. We could fix that by setting a variable from all three of python, ./x, and x.ps1.
1475                if git_root.join("src").join("stage0").exists() {
1476                    config.src = git_root;
1477                }
1478            } else {
1479                // We're building from a tarball, not git sources.
1480                // We don't support pre-downloaded bootstrap in this case.
1481            }
1482        }
1483
1484        if cfg!(test) {
1485            // Use the build directory of the original x.py invocation, so that we can set `initial_rustc` properly.
1486            config.out = Path::new(
1487                &env::var_os("CARGO_TARGET_DIR").expect("cargo test directly is not supported"),
1488            )
1489            .parent()
1490            .unwrap()
1491            .to_path_buf();
1492        }
1493
1494        config.stage0_metadata = build_helper::stage0_parser::parse_stage0_file();
1495
1496        // Locate the configuration file using the following priority (first match wins):
1497        // 1. `--config <path>` (explicit flag)
1498        // 2. `RUST_BOOTSTRAP_CONFIG` environment variable
1499        // 3. `./bootstrap.toml` (local file)
1500        // 4. `<root>/bootstrap.toml`
1501        // 5. `./config.toml` (fallback for backward compatibility)
1502        // 6. `<root>/config.toml`
1503        let toml_path = flags
1504            .config
1505            .clone()
1506            .or_else(|| env::var_os("RUST_BOOTSTRAP_CONFIG").map(PathBuf::from));
1507        let using_default_path = toml_path.is_none();
1508        let mut toml_path = toml_path.unwrap_or_else(|| PathBuf::from("bootstrap.toml"));
1509
1510        if using_default_path && !toml_path.exists() {
1511            toml_path = config.src.join(PathBuf::from("bootstrap.toml"));
1512            if !toml_path.exists() {
1513                toml_path = PathBuf::from("config.toml");
1514                if !toml_path.exists() {
1515                    toml_path = config.src.join(PathBuf::from("config.toml"));
1516                }
1517            }
1518        }
1519
1520        let file_content = t!(fs::read_to_string(config.src.join("src/ci/channel")));
1521        let ci_channel = file_content.trim_end();
1522
1523        // Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path,
1524        // but not if `bootstrap.toml` hasn't been created.
1525        let mut toml = if !using_default_path || toml_path.exists() {
1526            config.config = Some(if cfg!(not(test)) {
1527                toml_path.canonicalize().unwrap()
1528            } else {
1529                toml_path.clone()
1530            });
1531            get_toml(&toml_path).unwrap_or_else(|e| {
1532                eprintln!("ERROR: Failed to parse '{}': {e}", toml_path.display());
1533                exit!(2);
1534            })
1535        } else {
1536            config.config = None;
1537            TomlConfig::default()
1538        };
1539
1540        if cfg!(test) {
1541            // When configuring bootstrap for tests, make sure to set the rustc and Cargo to the
1542            // same ones used to call the tests (if custom ones are not defined in the toml). If we
1543            // don't do that, bootstrap will use its own detection logic to find a suitable rustc
1544            // and Cargo, which doesn't work when the caller is specìfying a custom local rustc or
1545            // Cargo in their bootstrap.toml.
1546            let build = toml.build.get_or_insert_with(Default::default);
1547            build.rustc = build.rustc.take().or(std::env::var_os("RUSTC").map(|p| p.into()));
1548            build.cargo = build.cargo.take().or(std::env::var_os("CARGO").map(|p| p.into()));
1549        }
1550
1551        if GitInfo::new(false, &config.src).is_from_tarball() && toml.profile.is_none() {
1552            toml.profile = Some("dist".into());
1553        }
1554
1555        if let Some(include) = &toml.profile {
1556            // Allows creating alias for profile names, allowing
1557            // profiles to be renamed while maintaining back compatibility
1558            // Keep in sync with `profile_aliases` in bootstrap.py
1559            let profile_aliases = HashMap::from([("user", "dist")]);
1560            let include = match profile_aliases.get(include.as_str()) {
1561                Some(alias) => alias,
1562                None => include.as_str(),
1563            };
1564            let mut include_path = config.src.clone();
1565            include_path.push("src");
1566            include_path.push("bootstrap");
1567            include_path.push("defaults");
1568            include_path.push(format!("bootstrap.{include}.toml"));
1569            let included_toml = get_toml(&include_path).unwrap_or_else(|e| {
1570                eprintln!(
1571                    "ERROR: Failed to parse default config profile at '{}': {e}",
1572                    include_path.display()
1573                );
1574                exit!(2);
1575            });
1576            toml.merge(included_toml, ReplaceOpt::IgnoreDuplicate);
1577        }
1578
1579        let mut override_toml = TomlConfig::default();
1580        for option in flags.set.iter() {
1581            fn get_table(option: &str) -> Result<TomlConfig, toml::de::Error> {
1582                toml::from_str(option).and_then(|table: toml::Value| TomlConfig::deserialize(table))
1583            }
1584
1585            let mut err = match get_table(option) {
1586                Ok(v) => {
1587                    override_toml.merge(v, ReplaceOpt::ErrorOnDuplicate);
1588                    continue;
1589                }
1590                Err(e) => e,
1591            };
1592            // We want to be able to set string values without quotes,
1593            // like in `configure.py`. Try adding quotes around the right hand side
1594            if let Some((key, value)) = option.split_once('=') {
1595                if !value.contains('"') {
1596                    match get_table(&format!(r#"{key}="{value}""#)) {
1597                        Ok(v) => {
1598                            override_toml.merge(v, ReplaceOpt::ErrorOnDuplicate);
1599                            continue;
1600                        }
1601                        Err(e) => err = e,
1602                    }
1603                }
1604            }
1605            eprintln!("failed to parse override `{option}`: `{err}");
1606            exit!(2)
1607        }
1608        toml.merge(override_toml, ReplaceOpt::Override);
1609
1610        config.change_id = toml.change_id.inner;
1611
1612        let Build {
1613            mut description,
1614            build,
1615            host,
1616            target,
1617            build_dir,
1618            cargo,
1619            rustc,
1620            rustfmt,
1621            cargo_clippy,
1622            docs,
1623            compiler_docs,
1624            library_docs_private_items,
1625            docs_minification,
1626            submodules,
1627            gdb,
1628            lldb,
1629            nodejs,
1630            npm,
1631            python,
1632            reuse,
1633            locked_deps,
1634            vendor,
1635            full_bootstrap,
1636            bootstrap_cache_path,
1637            extended,
1638            tools,
1639            verbose,
1640            sanitizers,
1641            profiler,
1642            cargo_native_static,
1643            low_priority,
1644            configure_args,
1645            local_rebuild,
1646            print_step_timings,
1647            print_step_rusage,
1648            check_stage,
1649            doc_stage,
1650            build_stage,
1651            test_stage,
1652            install_stage,
1653            dist_stage,
1654            bench_stage,
1655            patch_binaries_for_nix,
1656            // This field is only used by bootstrap.py
1657            metrics: _,
1658            android_ndk,
1659            optimized_compiler_builtins,
1660            jobs,
1661            compiletest_diff_tool,
1662            mut ccache,
1663            exclude,
1664        } = toml.build.unwrap_or_default();
1665
1666        let mut paths: Vec<PathBuf> = flags.skip.into_iter().chain(flags.exclude).collect();
1667
1668        if let Some(exclude) = exclude {
1669            paths.extend(exclude);
1670        }
1671
1672        config.skip = paths
1673            .into_iter()
1674            .map(|p| {
1675                // Never return top-level path here as it would break `--skip`
1676                // logic on rustc's internal test framework which is utilized
1677                // by compiletest.
1678                if cfg!(windows) {
1679                    PathBuf::from(p.to_str().unwrap().replace('/', "\\"))
1680                } else {
1681                    p
1682                }
1683            })
1684            .collect();
1685
1686        config.jobs = Some(threads_from_config(flags.jobs.unwrap_or(jobs.unwrap_or(0))));
1687
1688        if let Some(file_build) = build {
1689            config.build = TargetSelection::from_user(&file_build);
1690        };
1691
1692        set(&mut config.out, flags.build_dir.or_else(|| build_dir.map(PathBuf::from)));
1693        // NOTE: Bootstrap spawns various commands with different working directories.
1694        // To avoid writing to random places on the file system, `config.out` needs to be an absolute path.
1695        if !config.out.is_absolute() {
1696            // `canonicalize` requires the path to already exist. Use our vendored copy of `absolute` instead.
1697            config.out = absolute(&config.out).expect("can't make empty path absolute");
1698        }
1699
1700        if cargo_clippy.is_some() && rustc.is_none() {
1701            println!(
1702                "WARNING: Using `build.cargo-clippy` without `build.rustc` usually fails due to toolchain conflict."
1703            );
1704        }
1705
1706        config.initial_rustc = if let Some(rustc) = rustc {
1707            if !flags.skip_stage0_validation {
1708                config.check_stage0_version(&rustc, "rustc");
1709            }
1710            rustc
1711        } else {
1712            config.download_beta_toolchain();
1713            config
1714                .out
1715                .join(config.build)
1716                .join("stage0")
1717                .join("bin")
1718                .join(exe("rustc", config.build))
1719        };
1720
1721        config.initial_sysroot = config.initial_rustc.ancestors().nth(2).unwrap().into();
1722
1723        config.initial_cargo_clippy = cargo_clippy;
1724
1725        config.initial_cargo = if let Some(cargo) = cargo {
1726            if !flags.skip_stage0_validation {
1727                config.check_stage0_version(&cargo, "cargo");
1728            }
1729            cargo
1730        } else {
1731            config.download_beta_toolchain();
1732            config.initial_sysroot.join("bin").join(exe("cargo", config.build))
1733        };
1734
1735        // NOTE: it's important this comes *after* we set `initial_rustc` just above.
1736        if config.dry_run() {
1737            let dir = config.out.join("tmp-dry-run");
1738            t!(fs::create_dir_all(&dir));
1739            config.out = dir;
1740        }
1741
1742        config.hosts = if let Some(TargetSelectionList(arg_host)) = flags.host {
1743            arg_host
1744        } else if let Some(file_host) = host {
1745            file_host.iter().map(|h| TargetSelection::from_user(h)).collect()
1746        } else {
1747            vec![config.build]
1748        };
1749        config.targets = if let Some(TargetSelectionList(arg_target)) = flags.target {
1750            arg_target
1751        } else if let Some(file_target) = target {
1752            file_target.iter().map(|h| TargetSelection::from_user(h)).collect()
1753        } else {
1754            // If target is *not* configured, then default to the host
1755            // toolchains.
1756            config.hosts.clone()
1757        };
1758
1759        config.nodejs = nodejs.map(PathBuf::from);
1760        config.npm = npm.map(PathBuf::from);
1761        config.gdb = gdb.map(PathBuf::from);
1762        config.lldb = lldb.map(PathBuf::from);
1763        config.python = python.map(PathBuf::from);
1764        config.reuse = reuse.map(PathBuf::from);
1765        config.submodules = submodules;
1766        config.android_ndk = android_ndk;
1767        config.bootstrap_cache_path = bootstrap_cache_path;
1768        set(&mut config.low_priority, low_priority);
1769        set(&mut config.compiler_docs, compiler_docs);
1770        set(&mut config.library_docs_private_items, library_docs_private_items);
1771        set(&mut config.docs_minification, docs_minification);
1772        set(&mut config.docs, docs);
1773        set(&mut config.locked_deps, locked_deps);
1774        set(&mut config.full_bootstrap, full_bootstrap);
1775        set(&mut config.extended, extended);
1776        config.tools = tools;
1777        set(&mut config.verbose, verbose);
1778        set(&mut config.sanitizers, sanitizers);
1779        set(&mut config.profiler, profiler);
1780        set(&mut config.cargo_native_static, cargo_native_static);
1781        set(&mut config.configure_args, configure_args);
1782        set(&mut config.local_rebuild, local_rebuild);
1783        set(&mut config.print_step_timings, print_step_timings);
1784        set(&mut config.print_step_rusage, print_step_rusage);
1785        config.patch_binaries_for_nix = patch_binaries_for_nix;
1786
1787        config.verbose = cmp::max(config.verbose, flags.verbose as usize);
1788
1789        // Verbose flag is a good default for `rust.verbose-tests`.
1790        config.verbose_tests = config.is_verbose();
1791
1792        if let Some(install) = toml.install {
1793            let Install { prefix, sysconfdir, docdir, bindir, libdir, mandir, datadir } = install;
1794            config.prefix = prefix.map(PathBuf::from);
1795            config.sysconfdir = sysconfdir.map(PathBuf::from);
1796            config.datadir = datadir.map(PathBuf::from);
1797            config.docdir = docdir.map(PathBuf::from);
1798            set(&mut config.bindir, bindir.map(PathBuf::from));
1799            config.libdir = libdir.map(PathBuf::from);
1800            config.mandir = mandir.map(PathBuf::from);
1801        }
1802
1803        config.llvm_assertions =
1804            toml.llvm.as_ref().is_some_and(|llvm| llvm.assertions.unwrap_or(false));
1805
1806        // Store off these values as options because if they're not provided
1807        // we'll infer default values for them later
1808        let mut llvm_tests = None;
1809        let mut llvm_enzyme = None;
1810        let mut llvm_offload = None;
1811        let mut llvm_plugins = None;
1812        let mut debug = None;
1813        let mut rustc_debug_assertions = None;
1814        let mut std_debug_assertions = None;
1815        let mut overflow_checks = None;
1816        let mut overflow_checks_std = None;
1817        let mut debug_logging = None;
1818        let mut debuginfo_level = None;
1819        let mut debuginfo_level_rustc = None;
1820        let mut debuginfo_level_std = None;
1821        let mut debuginfo_level_tools = None;
1822        let mut debuginfo_level_tests = None;
1823        let mut optimize = None;
1824        let mut lld_enabled = None;
1825        let mut std_features = None;
1826
1827        let is_user_configured_rust_channel =
1828            if let Some(channel) = toml.rust.as_ref().and_then(|r| r.channel.clone()) {
1829                if channel == "auto-detect" {
1830                    config.channel = ci_channel.into();
1831                } else {
1832                    config.channel = channel;
1833                }
1834                true
1835            } else {
1836                false
1837            };
1838
1839        let default = config.channel == "dev";
1840        config.omit_git_hash = toml.rust.as_ref().and_then(|r| r.omit_git_hash).unwrap_or(default);
1841
1842        config.rust_info = GitInfo::new(config.omit_git_hash, &config.src);
1843        config.cargo_info = GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/cargo"));
1844        config.rust_analyzer_info =
1845            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/rust-analyzer"));
1846        config.clippy_info =
1847            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/clippy"));
1848        config.miri_info = GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/miri"));
1849        config.rustfmt_info =
1850            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/rustfmt"));
1851        config.enzyme_info =
1852            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/enzyme"));
1853        config.in_tree_llvm_info = GitInfo::new(false, &config.src.join("src/llvm-project"));
1854        config.in_tree_gcc_info = GitInfo::new(false, &config.src.join("src/gcc"));
1855
1856        config.vendor = vendor.unwrap_or(
1857            config.rust_info.is_from_tarball()
1858                && config.src.join("vendor").exists()
1859                && config.src.join(".cargo/config.toml").exists(),
1860        );
1861
1862        if let Some(rust) = toml.rust {
1863            let Rust {
1864                optimize: optimize_toml,
1865                debug: debug_toml,
1866                codegen_units,
1867                codegen_units_std,
1868                rustc_debug_assertions: rustc_debug_assertions_toml,
1869                std_debug_assertions: std_debug_assertions_toml,
1870                overflow_checks: overflow_checks_toml,
1871                overflow_checks_std: overflow_checks_std_toml,
1872                debug_logging: debug_logging_toml,
1873                debuginfo_level: debuginfo_level_toml,
1874                debuginfo_level_rustc: debuginfo_level_rustc_toml,
1875                debuginfo_level_std: debuginfo_level_std_toml,
1876                debuginfo_level_tools: debuginfo_level_tools_toml,
1877                debuginfo_level_tests: debuginfo_level_tests_toml,
1878                backtrace,
1879                incremental,
1880                randomize_layout,
1881                default_linker,
1882                channel: _, // already handled above
1883                description: rust_description,
1884                musl_root,
1885                rpath,
1886                verbose_tests,
1887                optimize_tests,
1888                codegen_tests,
1889                omit_git_hash: _, // already handled above
1890                dist_src,
1891                save_toolstates,
1892                codegen_backends,
1893                lld: lld_enabled_toml,
1894                llvm_tools,
1895                llvm_bitcode_linker,
1896                deny_warnings,
1897                backtrace_on_ice,
1898                verify_llvm_ir,
1899                thin_lto_import_instr_limit,
1900                remap_debuginfo,
1901                jemalloc,
1902                test_compare_mode,
1903                llvm_libunwind,
1904                control_flow_guard,
1905                ehcont_guard,
1906                new_symbol_mangling,
1907                profile_generate,
1908                profile_use,
1909                download_rustc,
1910                lto,
1911                validate_mir_opts,
1912                frame_pointers,
1913                stack_protector,
1914                strip,
1915                lld_mode,
1916                std_features: std_features_toml,
1917            } = rust;
1918
1919            // FIXME(#133381): alt rustc builds currently do *not* have rustc debug assertions
1920            // enabled. We should not download a CI alt rustc if we need rustc to have debug
1921            // assertions (e.g. for crashes test suite). This can be changed once something like
1922            // [Enable debug assertions on alt
1923            // builds](https://github.com/rust-lang/rust/pull/131077) lands.
1924            //
1925            // Note that `rust.debug = true` currently implies `rust.debug-assertions = true`!
1926            //
1927            // This relies also on the fact that the global default for `download-rustc` will be
1928            // `false` if it's not explicitly set.
1929            let debug_assertions_requested = matches!(rustc_debug_assertions_toml, Some(true))
1930                || (matches!(debug_toml, Some(true))
1931                    && !matches!(rustc_debug_assertions_toml, Some(false)));
1932
1933            if debug_assertions_requested {
1934                if let Some(ref opt) = download_rustc {
1935                    if opt.is_string_or_true() {
1936                        eprintln!(
1937                            "WARN: currently no CI rustc builds have rustc debug assertions \
1938                            enabled. Please either set `rust.debug-assertions` to `false` if you \
1939                            want to use download CI rustc or set `rust.download-rustc` to `false`."
1940                        );
1941                    }
1942                }
1943            }
1944
1945            config.download_rustc_commit = config.download_ci_rustc_commit(
1946                download_rustc,
1947                debug_assertions_requested,
1948                config.llvm_assertions,
1949            );
1950
1951            debug = debug_toml;
1952            rustc_debug_assertions = rustc_debug_assertions_toml;
1953            std_debug_assertions = std_debug_assertions_toml;
1954            overflow_checks = overflow_checks_toml;
1955            overflow_checks_std = overflow_checks_std_toml;
1956            debug_logging = debug_logging_toml;
1957            debuginfo_level = debuginfo_level_toml;
1958            debuginfo_level_rustc = debuginfo_level_rustc_toml;
1959            debuginfo_level_std = debuginfo_level_std_toml;
1960            debuginfo_level_tools = debuginfo_level_tools_toml;
1961            debuginfo_level_tests = debuginfo_level_tests_toml;
1962            lld_enabled = lld_enabled_toml;
1963            std_features = std_features_toml;
1964
1965            optimize = optimize_toml;
1966            config.rust_new_symbol_mangling = new_symbol_mangling;
1967            set(&mut config.rust_optimize_tests, optimize_tests);
1968            set(&mut config.codegen_tests, codegen_tests);
1969            set(&mut config.rust_rpath, rpath);
1970            set(&mut config.rust_strip, strip);
1971            set(&mut config.rust_frame_pointers, frame_pointers);
1972            config.rust_stack_protector = stack_protector;
1973            set(&mut config.jemalloc, jemalloc);
1974            set(&mut config.test_compare_mode, test_compare_mode);
1975            set(&mut config.backtrace, backtrace);
1976            if rust_description.is_some() {
1977                eprintln!(
1978                    "Warning: rust.description is deprecated. Use build.description instead."
1979                );
1980            }
1981            description = description.or(rust_description);
1982            set(&mut config.rust_dist_src, dist_src);
1983            set(&mut config.verbose_tests, verbose_tests);
1984            // in the case "false" is set explicitly, do not overwrite the command line args
1985            if let Some(true) = incremental {
1986                config.incremental = true;
1987            }
1988            set(&mut config.lld_mode, lld_mode);
1989            set(&mut config.llvm_bitcode_linker_enabled, llvm_bitcode_linker);
1990
1991            config.rust_randomize_layout = randomize_layout.unwrap_or_default();
1992            config.llvm_tools_enabled = llvm_tools.unwrap_or(true);
1993
1994            config.llvm_enzyme =
1995                llvm_enzyme.unwrap_or(config.channel == "dev" || config.channel == "nightly");
1996            config.rustc_default_linker = default_linker;
1997            config.musl_root = musl_root.map(PathBuf::from);
1998            config.save_toolstates = save_toolstates.map(PathBuf::from);
1999            set(
2000                &mut config.deny_warnings,
2001                match flags.warnings {
2002                    Warnings::Deny => Some(true),
2003                    Warnings::Warn => Some(false),
2004                    Warnings::Default => deny_warnings,
2005                },
2006            );
2007            set(&mut config.backtrace_on_ice, backtrace_on_ice);
2008            set(&mut config.rust_verify_llvm_ir, verify_llvm_ir);
2009            config.rust_thin_lto_import_instr_limit = thin_lto_import_instr_limit;
2010            set(&mut config.rust_remap_debuginfo, remap_debuginfo);
2011            set(&mut config.control_flow_guard, control_flow_guard);
2012            set(&mut config.ehcont_guard, ehcont_guard);
2013            config.llvm_libunwind_default =
2014                llvm_libunwind.map(|v| v.parse().expect("failed to parse rust.llvm-libunwind"));
2015
2016            if let Some(ref backends) = codegen_backends {
2017                let available_backends = ["llvm", "cranelift", "gcc"];
2018
2019                config.rust_codegen_backends = backends.iter().map(|s| {
2020                    if let Some(backend) = s.strip_prefix(CODEGEN_BACKEND_PREFIX) {
2021                        if available_backends.contains(&backend) {
2022                            panic!("Invalid value '{s}' for 'rust.codegen-backends'. Instead, please use '{backend}'.");
2023                        } else {
2024                            println!("HELP: '{s}' for 'rust.codegen-backends' might fail. \
2025                                Codegen backends are mostly defined without the '{CODEGEN_BACKEND_PREFIX}' prefix. \
2026                                In this case, it would be referred to as '{backend}'.");
2027                        }
2028                    }
2029
2030                    s.clone()
2031                }).collect();
2032            }
2033
2034            config.rust_codegen_units = codegen_units.map(threads_from_config);
2035            config.rust_codegen_units_std = codegen_units_std.map(threads_from_config);
2036            config.rust_profile_use = flags.rust_profile_use.or(profile_use);
2037            config.rust_profile_generate = flags.rust_profile_generate.or(profile_generate);
2038            config.rust_lto =
2039                lto.as_deref().map(|value| RustcLto::from_str(value).unwrap()).unwrap_or_default();
2040            config.rust_validate_mir_opts = validate_mir_opts;
2041        } else {
2042            config.rust_profile_use = flags.rust_profile_use;
2043            config.rust_profile_generate = flags.rust_profile_generate;
2044        }
2045
2046        config.reproducible_artifacts = flags.reproducible_artifact;
2047        config.description = description;
2048
2049        // We need to override `rust.channel` if it's manually specified when using the CI rustc.
2050        // This is because if the compiler uses a different channel than the one specified in bootstrap.toml,
2051        // tests may fail due to using a different channel than the one used by the compiler during tests.
2052        if let Some(commit) = &config.download_rustc_commit {
2053            if is_user_configured_rust_channel {
2054                println!(
2055                    "WARNING: `rust.download-rustc` is enabled. The `rust.channel` option will be overridden by the CI rustc's channel."
2056                );
2057
2058                let channel = config
2059                    .read_file_by_commit(Path::new("src/ci/channel"), commit)
2060                    .trim()
2061                    .to_owned();
2062
2063                config.channel = channel;
2064            }
2065        } else if config.rust_info.is_from_tarball() && !is_user_configured_rust_channel {
2066            ci_channel.clone_into(&mut config.channel);
2067        }
2068
2069        if let Some(llvm) = toml.llvm {
2070            let Llvm {
2071                optimize: optimize_toml,
2072                thin_lto,
2073                release_debuginfo,
2074                assertions: _,
2075                tests,
2076                enzyme,
2077                plugins,
2078                ccache: llvm_ccache,
2079                static_libstdcpp,
2080                libzstd,
2081                ninja,
2082                targets,
2083                experimental_targets,
2084                link_jobs,
2085                link_shared,
2086                version_suffix,
2087                clang_cl,
2088                cflags,
2089                cxxflags,
2090                ldflags,
2091                use_libcxx,
2092                use_linker,
2093                allow_old_toolchain,
2094                offload,
2095                polly,
2096                clang,
2097                enable_warnings,
2098                download_ci_llvm,
2099                build_config,
2100            } = llvm;
2101            if llvm_ccache.is_some() {
2102                eprintln!("Warning: llvm.ccache is deprecated. Use build.ccache instead.");
2103            }
2104
2105            ccache = ccache.or(llvm_ccache);
2106            set(&mut config.ninja_in_file, ninja);
2107            llvm_tests = tests;
2108            llvm_enzyme = enzyme;
2109            llvm_offload = offload;
2110            llvm_plugins = plugins;
2111            set(&mut config.llvm_optimize, optimize_toml);
2112            set(&mut config.llvm_thin_lto, thin_lto);
2113            set(&mut config.llvm_release_debuginfo, release_debuginfo);
2114            set(&mut config.llvm_static_stdcpp, static_libstdcpp);
2115            set(&mut config.llvm_libzstd, libzstd);
2116            if let Some(v) = link_shared {
2117                config.llvm_link_shared.set(Some(v));
2118            }
2119            config.llvm_targets.clone_from(&targets);
2120            config.llvm_experimental_targets.clone_from(&experimental_targets);
2121            config.llvm_link_jobs = link_jobs;
2122            config.llvm_version_suffix.clone_from(&version_suffix);
2123            config.llvm_clang_cl.clone_from(&clang_cl);
2124
2125            config.llvm_cflags.clone_from(&cflags);
2126            config.llvm_cxxflags.clone_from(&cxxflags);
2127            config.llvm_ldflags.clone_from(&ldflags);
2128            set(&mut config.llvm_use_libcxx, use_libcxx);
2129            config.llvm_use_linker.clone_from(&use_linker);
2130            config.llvm_allow_old_toolchain = allow_old_toolchain.unwrap_or(false);
2131            config.llvm_offload = offload.unwrap_or(false);
2132            config.llvm_polly = polly.unwrap_or(false);
2133            config.llvm_clang = clang.unwrap_or(false);
2134            config.llvm_enable_warnings = enable_warnings.unwrap_or(false);
2135            config.llvm_build_config = build_config.clone().unwrap_or(Default::default());
2136
2137            config.llvm_from_ci =
2138                config.parse_download_ci_llvm(download_ci_llvm, config.llvm_assertions);
2139
2140            if config.llvm_from_ci {
2141                let warn = |option: &str| {
2142                    println!(
2143                        "WARNING: `{option}` will only be used on `compiler/rustc_llvm` build, not for the LLVM build."
2144                    );
2145                    println!(
2146                        "HELP: To use `{option}` for LLVM builds, set `download-ci-llvm` option to false."
2147                    );
2148                };
2149
2150                if static_libstdcpp.is_some() {
2151                    warn("static-libstdcpp");
2152                }
2153
2154                if link_shared.is_some() {
2155                    warn("link-shared");
2156                }
2157
2158                // FIXME(#129153): instead of all the ad-hoc `download-ci-llvm` checks that follow,
2159                // use the `builder-config` present in tarballs since #128822 to compare the local
2160                // config to the ones used to build the LLVM artifacts on CI, and only notify users
2161                // if they've chosen a different value.
2162
2163                if libzstd.is_some() {
2164                    println!(
2165                        "WARNING: when using `download-ci-llvm`, the local `llvm.libzstd` option, \
2166                        like almost all `llvm.*` options, will be ignored and set by the LLVM CI \
2167                        artifacts builder config."
2168                    );
2169                    println!(
2170                        "HELP: To use `llvm.libzstd` for LLVM/LLD builds, set `download-ci-llvm` option to false."
2171                    );
2172                }
2173            }
2174
2175            if !config.llvm_from_ci && config.llvm_thin_lto && link_shared.is_none() {
2176                // If we're building with ThinLTO on, by default we want to link
2177                // to LLVM shared, to avoid re-doing ThinLTO (which happens in
2178                // the link step) with each stage.
2179                config.llvm_link_shared.set(Some(true));
2180            }
2181        } else {
2182            config.llvm_from_ci = config.parse_download_ci_llvm(None, false);
2183        }
2184
2185        if let Some(gcc) = toml.gcc {
2186            config.gcc_ci_mode = match gcc.download_ci_gcc {
2187                Some(value) => match value {
2188                    true => GccCiMode::DownloadFromCi,
2189                    false => GccCiMode::BuildLocally,
2190                },
2191                None => GccCiMode::default(),
2192            };
2193        }
2194
2195        if let Some(t) = toml.target {
2196            for (triple, cfg) in t {
2197                let mut target = Target::from_triple(&triple);
2198
2199                if let Some(ref s) = cfg.llvm_config {
2200                    if config.download_rustc_commit.is_some() && triple == *config.build.triple {
2201                        panic!(
2202                            "setting llvm_config for the host is incompatible with download-rustc"
2203                        );
2204                    }
2205                    target.llvm_config = Some(config.src.join(s));
2206                }
2207                if let Some(patches) = cfg.llvm_has_rust_patches {
2208                    assert!(
2209                        config.submodules == Some(false) || cfg.llvm_config.is_some(),
2210                        "use of `llvm-has-rust-patches` is restricted to cases where either submodules are disabled or llvm-config been provided"
2211                    );
2212                    target.llvm_has_rust_patches = Some(patches);
2213                }
2214                if let Some(ref s) = cfg.llvm_filecheck {
2215                    target.llvm_filecheck = Some(config.src.join(s));
2216                }
2217                target.llvm_libunwind = cfg.llvm_libunwind.as_ref().map(|v| {
2218                    v.parse().unwrap_or_else(|_| {
2219                        panic!("failed to parse target.{triple}.llvm-libunwind")
2220                    })
2221                });
2222                if let Some(s) = cfg.no_std {
2223                    target.no_std = s;
2224                }
2225                target.cc = cfg.cc.map(PathBuf::from);
2226                target.cxx = cfg.cxx.map(PathBuf::from);
2227                target.ar = cfg.ar.map(PathBuf::from);
2228                target.ranlib = cfg.ranlib.map(PathBuf::from);
2229                target.linker = cfg.linker.map(PathBuf::from);
2230                target.crt_static = cfg.crt_static;
2231                target.musl_root = cfg.musl_root.map(PathBuf::from);
2232                target.musl_libdir = cfg.musl_libdir.map(PathBuf::from);
2233                target.wasi_root = cfg.wasi_root.map(PathBuf::from);
2234                target.qemu_rootfs = cfg.qemu_rootfs.map(PathBuf::from);
2235                target.runner = cfg.runner;
2236                target.sanitizers = cfg.sanitizers;
2237                target.profiler = cfg.profiler;
2238                target.rpath = cfg.rpath;
2239                target.optimized_compiler_builtins = cfg.optimized_compiler_builtins;
2240                target.jemalloc = cfg.jemalloc;
2241
2242                if let Some(ref backends) = cfg.codegen_backends {
2243                    let available_backends = ["llvm", "cranelift", "gcc"];
2244
2245                    target.codegen_backends = Some(backends.iter().map(|s| {
2246                        if let Some(backend) = s.strip_prefix(CODEGEN_BACKEND_PREFIX) {
2247                            if available_backends.contains(&backend) {
2248                                panic!("Invalid value '{s}' for 'target.{triple}.codegen-backends'. Instead, please use '{backend}'.");
2249                            } else {
2250                                println!("HELP: '{s}' for 'target.{triple}.codegen-backends' might fail. \
2251                                    Codegen backends are mostly defined without the '{CODEGEN_BACKEND_PREFIX}' prefix. \
2252                                    In this case, it would be referred to as '{backend}'.");
2253                            }
2254                        }
2255
2256                        s.clone()
2257                    }).collect());
2258                }
2259
2260                target.split_debuginfo = cfg.split_debuginfo.as_ref().map(|v| {
2261                    v.parse().unwrap_or_else(|_| {
2262                        panic!("invalid value for target.{triple}.split-debuginfo")
2263                    })
2264                });
2265
2266                config.target_config.insert(TargetSelection::from_user(&triple), target);
2267            }
2268        }
2269
2270        match ccache {
2271            Some(StringOrBool::String(ref s)) => config.ccache = Some(s.to_string()),
2272            Some(StringOrBool::Bool(true)) => {
2273                config.ccache = Some("ccache".to_string());
2274            }
2275            Some(StringOrBool::Bool(false)) | None => {}
2276        }
2277
2278        if config.llvm_from_ci {
2279            let triple = &config.build.triple;
2280            let ci_llvm_bin = config.ci_llvm_root().join("bin");
2281            let build_target = config
2282                .target_config
2283                .entry(config.build)
2284                .or_insert_with(|| Target::from_triple(triple));
2285
2286            check_ci_llvm!(build_target.llvm_config);
2287            check_ci_llvm!(build_target.llvm_filecheck);
2288            build_target.llvm_config = Some(ci_llvm_bin.join(exe("llvm-config", config.build)));
2289            build_target.llvm_filecheck = Some(ci_llvm_bin.join(exe("FileCheck", config.build)));
2290        }
2291
2292        if let Some(dist) = toml.dist {
2293            let Dist {
2294                sign_folder,
2295                upload_addr,
2296                src_tarball,
2297                compression_formats,
2298                compression_profile,
2299                include_mingw_linker,
2300                vendor,
2301            } = dist;
2302            config.dist_sign_folder = sign_folder.map(PathBuf::from);
2303            config.dist_upload_addr = upload_addr;
2304            config.dist_compression_formats = compression_formats;
2305            set(&mut config.dist_compression_profile, compression_profile);
2306            set(&mut config.rust_dist_src, src_tarball);
2307            set(&mut config.dist_include_mingw_linker, include_mingw_linker);
2308            config.dist_vendor = vendor.unwrap_or_else(|| {
2309                // If we're building from git or tarball sources, enable it by default.
2310                config.rust_info.is_managed_git_subrepository()
2311                    || config.rust_info.is_from_tarball()
2312            });
2313        }
2314
2315        if let Some(r) = rustfmt {
2316            *config.initial_rustfmt.borrow_mut() = if r.exists() {
2317                RustfmtState::SystemToolchain(r)
2318            } else {
2319                RustfmtState::Unavailable
2320            };
2321        }
2322
2323        // Now that we've reached the end of our configuration, infer the
2324        // default values for all options that we haven't otherwise stored yet.
2325
2326        config.llvm_tests = llvm_tests.unwrap_or(false);
2327        config.llvm_enzyme = llvm_enzyme.unwrap_or(false);
2328        config.llvm_offload = llvm_offload.unwrap_or(false);
2329        config.llvm_plugins = llvm_plugins.unwrap_or(false);
2330        config.rust_optimize = optimize.unwrap_or(RustOptimize::Bool(true));
2331
2332        // We make `x86_64-unknown-linux-gnu` use the self-contained linker by default, so we will
2333        // build our internal lld and use it as the default linker, by setting the `rust.lld` config
2334        // to true by default:
2335        // - on the `x86_64-unknown-linux-gnu` target
2336        // - on the `dev` and `nightly` channels
2337        // - when building our in-tree llvm (i.e. the target has not set an `llvm-config`), so that
2338        //   we're also able to build the corresponding lld
2339        // - or when using an external llvm that's downloaded from CI, which also contains our prebuilt
2340        //   lld
2341        // - otherwise, we'd be using an external llvm, and lld would not necessarily available and
2342        //   thus, disabled
2343        // - similarly, lld will not be built nor used by default when explicitly asked not to, e.g.
2344        //   when the config sets `rust.lld = false`
2345        if config.build.triple == "x86_64-unknown-linux-gnu"
2346            && config.hosts == [config.build]
2347            && (config.channel == "dev" || config.channel == "nightly")
2348        {
2349            let no_llvm_config = config
2350                .target_config
2351                .get(&config.build)
2352                .is_some_and(|target_config| target_config.llvm_config.is_none());
2353            let enable_lld = config.llvm_from_ci || no_llvm_config;
2354            // Prefer the config setting in case an explicit opt-out is needed.
2355            config.lld_enabled = lld_enabled.unwrap_or(enable_lld);
2356        } else {
2357            set(&mut config.lld_enabled, lld_enabled);
2358        }
2359
2360        if matches!(config.lld_mode, LldMode::SelfContained)
2361            && !config.lld_enabled
2362            && flags.stage.unwrap_or(0) > 0
2363        {
2364            panic!(
2365                "Trying to use self-contained lld as a linker, but LLD is not being added to the sysroot. Enable it with rust.lld = true."
2366            );
2367        }
2368
2369        let default_std_features = BTreeSet::from([String::from("panic-unwind")]);
2370        config.rust_std_features = std_features.unwrap_or(default_std_features);
2371
2372        let default = debug == Some(true);
2373        config.rustc_debug_assertions = rustc_debug_assertions.unwrap_or(default);
2374        config.std_debug_assertions = std_debug_assertions.unwrap_or(config.rustc_debug_assertions);
2375        config.rust_overflow_checks = overflow_checks.unwrap_or(default);
2376        config.rust_overflow_checks_std =
2377            overflow_checks_std.unwrap_or(config.rust_overflow_checks);
2378
2379        config.rust_debug_logging = debug_logging.unwrap_or(config.rustc_debug_assertions);
2380
2381        let with_defaults = |debuginfo_level_specific: Option<_>| {
2382            debuginfo_level_specific.or(debuginfo_level).unwrap_or(if debug == Some(true) {
2383                DebuginfoLevel::Limited
2384            } else {
2385                DebuginfoLevel::None
2386            })
2387        };
2388        config.rust_debuginfo_level_rustc = with_defaults(debuginfo_level_rustc);
2389        config.rust_debuginfo_level_std = with_defaults(debuginfo_level_std);
2390        config.rust_debuginfo_level_tools = with_defaults(debuginfo_level_tools);
2391        config.rust_debuginfo_level_tests = debuginfo_level_tests.unwrap_or(DebuginfoLevel::None);
2392        config.optimized_compiler_builtins =
2393            optimized_compiler_builtins.unwrap_or(config.channel != "dev");
2394        config.compiletest_diff_tool = compiletest_diff_tool;
2395
2396        let download_rustc = config.download_rustc_commit.is_some();
2397        config.explicit_stage_from_cli = flags.stage.is_some();
2398        config.explicit_stage_from_config = test_stage.is_some()
2399            || build_stage.is_some()
2400            || doc_stage.is_some()
2401            || dist_stage.is_some()
2402            || install_stage.is_some()
2403            || check_stage.is_some()
2404            || bench_stage.is_some();
2405        // See https://github.com/rust-lang/compiler-team/issues/326
2406        config.stage = match config.cmd {
2407            Subcommand::Check { .. } => flags.stage.or(check_stage).unwrap_or(0),
2408            // `download-rustc` only has a speed-up for stage2 builds. Default to stage2 unless explicitly overridden.
2409            Subcommand::Doc { .. } => {
2410                flags.stage.or(doc_stage).unwrap_or(if download_rustc { 2 } else { 0 })
2411            }
2412            Subcommand::Build => {
2413                flags.stage.or(build_stage).unwrap_or(if download_rustc { 2 } else { 1 })
2414            }
2415            Subcommand::Test { .. } | Subcommand::Miri { .. } => {
2416                flags.stage.or(test_stage).unwrap_or(if download_rustc { 2 } else { 1 })
2417            }
2418            Subcommand::Bench { .. } => flags.stage.or(bench_stage).unwrap_or(2),
2419            Subcommand::Dist => flags.stage.or(dist_stage).unwrap_or(2),
2420            Subcommand::Install => flags.stage.or(install_stage).unwrap_or(2),
2421            Subcommand::Perf { .. } => flags.stage.unwrap_or(1),
2422            // These are all bootstrap tools, which don't depend on the compiler.
2423            // The stage we pass shouldn't matter, but use 0 just in case.
2424            Subcommand::Clean { .. }
2425            | Subcommand::Clippy { .. }
2426            | Subcommand::Fix
2427            | Subcommand::Run { .. }
2428            | Subcommand::Setup { .. }
2429            | Subcommand::Format { .. }
2430            | Subcommand::Suggest { .. }
2431            | Subcommand::Vendor { .. } => flags.stage.unwrap_or(0),
2432        };
2433
2434        // CI should always run stage 2 builds, unless it specifically states otherwise
2435        #[cfg(not(test))]
2436        if flags.stage.is_none() && config.is_running_on_ci {
2437            match config.cmd {
2438                Subcommand::Test { .. }
2439                | Subcommand::Miri { .. }
2440                | Subcommand::Doc { .. }
2441                | Subcommand::Build
2442                | Subcommand::Bench { .. }
2443                | Subcommand::Dist
2444                | Subcommand::Install => {
2445                    assert_eq!(
2446                        config.stage, 2,
2447                        "x.py should be run with `--stage 2` on CI, but was run with `--stage {}`",
2448                        config.stage,
2449                    );
2450                }
2451                Subcommand::Clean { .. }
2452                | Subcommand::Check { .. }
2453                | Subcommand::Clippy { .. }
2454                | Subcommand::Fix
2455                | Subcommand::Run { .. }
2456                | Subcommand::Setup { .. }
2457                | Subcommand::Format { .. }
2458                | Subcommand::Suggest { .. }
2459                | Subcommand::Vendor { .. }
2460                | Subcommand::Perf { .. } => {}
2461            }
2462        }
2463
2464        config
2465    }
2466
2467    pub fn dry_run(&self) -> bool {
2468        match self.dry_run {
2469            DryRun::Disabled => false,
2470            DryRun::SelfCheck | DryRun::UserSelected => true,
2471        }
2472    }
2473
2474    pub fn is_explicit_stage(&self) -> bool {
2475        self.explicit_stage_from_cli || self.explicit_stage_from_config
2476    }
2477
2478    /// Runs a command, printing out nice contextual information if it fails.
2479    /// Exits if the command failed to execute at all, otherwise returns its
2480    /// `status.success()`.
2481    #[deprecated = "use `Builder::try_run` instead where possible"]
2482    pub(crate) fn try_run(&self, cmd: &mut Command) -> Result<(), ()> {
2483        if self.dry_run() {
2484            return Ok(());
2485        }
2486        self.verbose(|| println!("running: {cmd:?}"));
2487        build_helper::util::try_run(cmd, self.is_verbose())
2488    }
2489
2490    pub(crate) fn test_args(&self) -> Vec<&str> {
2491        let mut test_args = match self.cmd {
2492            Subcommand::Test { ref test_args, .. }
2493            | Subcommand::Bench { ref test_args, .. }
2494            | Subcommand::Miri { ref test_args, .. } => {
2495                test_args.iter().flat_map(|s| s.split_whitespace()).collect()
2496            }
2497            _ => vec![],
2498        };
2499        test_args.extend(self.free_args.iter().map(|s| s.as_str()));
2500        test_args
2501    }
2502
2503    pub(crate) fn args(&self) -> Vec<&str> {
2504        let mut args = match self.cmd {
2505            Subcommand::Run { ref args, .. } => {
2506                args.iter().flat_map(|s| s.split_whitespace()).collect()
2507            }
2508            _ => vec![],
2509        };
2510        args.extend(self.free_args.iter().map(|s| s.as_str()));
2511        args
2512    }
2513
2514    /// Returns the content of the given file at a specific commit.
2515    pub(crate) fn read_file_by_commit(&self, file: &Path, commit: &str) -> String {
2516        assert!(
2517            self.rust_info.is_managed_git_subrepository(),
2518            "`Config::read_file_by_commit` is not supported in non-git sources."
2519        );
2520
2521        let mut git = helpers::git(Some(&self.src));
2522        git.arg("show").arg(format!("{commit}:{}", file.to_str().unwrap()));
2523        output(git.as_command_mut())
2524    }
2525
2526    /// Bootstrap embeds a version number into the name of shared libraries it uploads in CI.
2527    /// Return the version it would have used for the given commit.
2528    pub(crate) fn artifact_version_part(&self, commit: &str) -> String {
2529        let (channel, version) = if self.rust_info.is_managed_git_subrepository() {
2530            let channel =
2531                self.read_file_by_commit(Path::new("src/ci/channel"), commit).trim().to_owned();
2532            let version =
2533                self.read_file_by_commit(Path::new("src/version"), commit).trim().to_owned();
2534            (channel, version)
2535        } else {
2536            let channel = fs::read_to_string(self.src.join("src/ci/channel"));
2537            let version = fs::read_to_string(self.src.join("src/version"));
2538            match (channel, version) {
2539                (Ok(channel), Ok(version)) => {
2540                    (channel.trim().to_owned(), version.trim().to_owned())
2541                }
2542                (channel, version) => {
2543                    let src = self.src.display();
2544                    eprintln!("ERROR: failed to determine artifact channel and/or version");
2545                    eprintln!(
2546                        "HELP: consider using a git checkout or ensure these files are readable"
2547                    );
2548                    if let Err(channel) = channel {
2549                        eprintln!("reading {src}/src/ci/channel failed: {channel:?}");
2550                    }
2551                    if let Err(version) = version {
2552                        eprintln!("reading {src}/src/version failed: {version:?}");
2553                    }
2554                    panic!();
2555                }
2556            }
2557        };
2558
2559        match channel.as_str() {
2560            "stable" => version,
2561            "beta" => channel,
2562            "nightly" => channel,
2563            other => unreachable!("{:?} is not recognized as a valid channel", other),
2564        }
2565    }
2566
2567    /// Try to find the relative path of `bindir`, otherwise return it in full.
2568    pub fn bindir_relative(&self) -> &Path {
2569        let bindir = &self.bindir;
2570        if bindir.is_absolute() {
2571            // Try to make it relative to the prefix.
2572            if let Some(prefix) = &self.prefix {
2573                if let Ok(stripped) = bindir.strip_prefix(prefix) {
2574                    return stripped;
2575                }
2576            }
2577        }
2578        bindir
2579    }
2580
2581    /// Try to find the relative path of `libdir`.
2582    pub fn libdir_relative(&self) -> Option<&Path> {
2583        let libdir = self.libdir.as_ref()?;
2584        if libdir.is_relative() {
2585            Some(libdir)
2586        } else {
2587            // Try to make it relative to the prefix.
2588            libdir.strip_prefix(self.prefix.as_ref()?).ok()
2589        }
2590    }
2591
2592    /// The absolute path to the downloaded LLVM artifacts.
2593    pub(crate) fn ci_llvm_root(&self) -> PathBuf {
2594        assert!(self.llvm_from_ci);
2595        self.out.join(self.build).join("ci-llvm")
2596    }
2597
2598    /// Directory where the extracted `rustc-dev` component is stored.
2599    pub(crate) fn ci_rustc_dir(&self) -> PathBuf {
2600        assert!(self.download_rustc());
2601        self.out.join(self.build).join("ci-rustc")
2602    }
2603
2604    /// Determine whether llvm should be linked dynamically.
2605    ///
2606    /// If `false`, llvm should be linked statically.
2607    /// This is computed on demand since LLVM might have to first be downloaded from CI.
2608    pub(crate) fn llvm_link_shared(&self) -> bool {
2609        let mut opt = self.llvm_link_shared.get();
2610        if opt.is_none() && self.dry_run() {
2611            // just assume static for now - dynamic linking isn't supported on all platforms
2612            return false;
2613        }
2614
2615        let llvm_link_shared = *opt.get_or_insert_with(|| {
2616            if self.llvm_from_ci {
2617                self.maybe_download_ci_llvm();
2618                let ci_llvm = self.ci_llvm_root();
2619                let link_type = t!(
2620                    std::fs::read_to_string(ci_llvm.join("link-type.txt")),
2621                    format!("CI llvm missing: {}", ci_llvm.display())
2622                );
2623                link_type == "dynamic"
2624            } else {
2625                // unclear how thought-through this default is, but it maintains compatibility with
2626                // previous behavior
2627                false
2628            }
2629        });
2630        self.llvm_link_shared.set(opt);
2631        llvm_link_shared
2632    }
2633
2634    /// Return whether we will use a downloaded, pre-compiled version of rustc, or just build from source.
2635    pub(crate) fn download_rustc(&self) -> bool {
2636        self.download_rustc_commit().is_some()
2637    }
2638
2639    pub(crate) fn download_rustc_commit(&self) -> Option<&str> {
2640        static DOWNLOAD_RUSTC: OnceLock<Option<String>> = OnceLock::new();
2641        if self.dry_run() && DOWNLOAD_RUSTC.get().is_none() {
2642            // avoid trying to actually download the commit
2643            return self.download_rustc_commit.as_deref();
2644        }
2645
2646        DOWNLOAD_RUSTC
2647            .get_or_init(|| match &self.download_rustc_commit {
2648                None => None,
2649                Some(commit) => {
2650                    self.download_ci_rustc(commit);
2651
2652                    // CI-rustc can't be used without CI-LLVM. If `self.llvm_from_ci` is false, it means the "if-unchanged"
2653                    // logic has detected some changes in the LLVM submodule (download-ci-llvm=false can't happen here as
2654                    // we don't allow it while parsing the configuration).
2655                    if !self.llvm_from_ci {
2656                        // This happens when LLVM submodule is updated in CI, we should disable ci-rustc without an error
2657                        // to not break CI. For non-CI environments, we should return an error.
2658                        if self.is_running_on_ci {
2659                            println!("WARNING: LLVM submodule has changes, `download-rustc` will be disabled.");
2660                            return None;
2661                        } else {
2662                            panic!("ERROR: LLVM submodule has changes, `download-rustc` can't be used.");
2663                        }
2664                    }
2665
2666                    if let Some(config_path) = &self.config {
2667                        let ci_config_toml = match self.get_builder_toml("ci-rustc") {
2668                            Ok(ci_config_toml) => ci_config_toml,
2669                            Err(e) if e.to_string().contains("unknown field") => {
2670                                println!("WARNING: CI rustc has some fields that are no longer supported in bootstrap; download-rustc will be disabled.");
2671                                println!("HELP: Consider rebasing to a newer commit if available.");
2672                                return None;
2673                            },
2674                            Err(e) => {
2675                                eprintln!("ERROR: Failed to parse CI rustc bootstrap.toml: {e}");
2676                                exit!(2);
2677                            },
2678                        };
2679
2680                        let current_config_toml = Self::get_toml(config_path).unwrap();
2681
2682                        // Check the config compatibility
2683                        // FIXME: this doesn't cover `--set` flags yet.
2684                        let res = check_incompatible_options_for_ci_rustc(
2685                            self.build,
2686                            current_config_toml,
2687                            ci_config_toml,
2688                        );
2689
2690                        // Primarily used by CI runners to avoid handling download-rustc incompatible
2691                        // options one by one on shell scripts.
2692                        let disable_ci_rustc_if_incompatible = env::var_os("DISABLE_CI_RUSTC_IF_INCOMPATIBLE")
2693                            .is_some_and(|s| s == "1" || s == "true");
2694
2695                        if disable_ci_rustc_if_incompatible && res.is_err() {
2696                            println!("WARNING: download-rustc is disabled with `DISABLE_CI_RUSTC_IF_INCOMPATIBLE` env.");
2697                            return None;
2698                        }
2699
2700                        res.unwrap();
2701                    }
2702
2703                    Some(commit.clone())
2704                }
2705            })
2706            .as_deref()
2707    }
2708
2709    pub(crate) fn initial_rustfmt(&self) -> Option<PathBuf> {
2710        match &mut *self.initial_rustfmt.borrow_mut() {
2711            RustfmtState::SystemToolchain(p) | RustfmtState::Downloaded(p) => Some(p.clone()),
2712            RustfmtState::Unavailable => None,
2713            r @ RustfmtState::LazyEvaluated => {
2714                if self.dry_run() {
2715                    return Some(PathBuf::new());
2716                }
2717                let path = self.maybe_download_rustfmt();
2718                *r = if let Some(p) = &path {
2719                    RustfmtState::Downloaded(p.clone())
2720                } else {
2721                    RustfmtState::Unavailable
2722                };
2723                path
2724            }
2725        }
2726    }
2727
2728    /// Runs a function if verbosity is greater than 0
2729    pub fn verbose(&self, f: impl Fn()) {
2730        if self.is_verbose() {
2731            f()
2732        }
2733    }
2734
2735    pub fn sanitizers_enabled(&self, target: TargetSelection) -> bool {
2736        self.target_config.get(&target).and_then(|t| t.sanitizers).unwrap_or(self.sanitizers)
2737    }
2738
2739    pub fn needs_sanitizer_runtime_built(&self, target: TargetSelection) -> bool {
2740        // MSVC uses the Microsoft-provided sanitizer runtime, but all other runtimes we build.
2741        !target.is_msvc() && self.sanitizers_enabled(target)
2742    }
2743
2744    pub fn any_sanitizers_to_build(&self) -> bool {
2745        self.target_config
2746            .iter()
2747            .any(|(ts, t)| !ts.is_msvc() && t.sanitizers.unwrap_or(self.sanitizers))
2748    }
2749
2750    pub fn profiler_path(&self, target: TargetSelection) -> Option<&str> {
2751        match self.target_config.get(&target)?.profiler.as_ref()? {
2752            StringOrBool::String(s) => Some(s),
2753            StringOrBool::Bool(_) => None,
2754        }
2755    }
2756
2757    pub fn profiler_enabled(&self, target: TargetSelection) -> bool {
2758        self.target_config
2759            .get(&target)
2760            .and_then(|t| t.profiler.as_ref())
2761            .map(StringOrBool::is_string_or_true)
2762            .unwrap_or(self.profiler)
2763    }
2764
2765    pub fn any_profiler_enabled(&self) -> bool {
2766        self.target_config.values().any(|t| matches!(&t.profiler, Some(p) if p.is_string_or_true()))
2767            || self.profiler
2768    }
2769
2770    pub fn rpath_enabled(&self, target: TargetSelection) -> bool {
2771        self.target_config.get(&target).and_then(|t| t.rpath).unwrap_or(self.rust_rpath)
2772    }
2773
2774    pub fn optimized_compiler_builtins(&self, target: TargetSelection) -> bool {
2775        self.target_config
2776            .get(&target)
2777            .and_then(|t| t.optimized_compiler_builtins)
2778            .unwrap_or(self.optimized_compiler_builtins)
2779    }
2780
2781    pub fn llvm_enabled(&self, target: TargetSelection) -> bool {
2782        self.codegen_backends(target).contains(&"llvm".to_owned())
2783    }
2784
2785    pub fn llvm_libunwind(&self, target: TargetSelection) -> LlvmLibunwind {
2786        self.target_config
2787            .get(&target)
2788            .and_then(|t| t.llvm_libunwind)
2789            .or(self.llvm_libunwind_default)
2790            .unwrap_or(if target.contains("fuchsia") {
2791                LlvmLibunwind::InTree
2792            } else {
2793                LlvmLibunwind::No
2794            })
2795    }
2796
2797    pub fn split_debuginfo(&self, target: TargetSelection) -> SplitDebuginfo {
2798        self.target_config
2799            .get(&target)
2800            .and_then(|t| t.split_debuginfo)
2801            .unwrap_or_else(|| SplitDebuginfo::default_for_platform(target))
2802    }
2803
2804    /// Returns whether or not submodules should be managed by bootstrap.
2805    pub fn submodules(&self) -> bool {
2806        // If not specified in config, the default is to only manage
2807        // submodules if we're currently inside a git repository.
2808        self.submodules.unwrap_or(self.rust_info.is_managed_git_subrepository())
2809    }
2810
2811    pub fn codegen_backends(&self, target: TargetSelection) -> &[String] {
2812        self.target_config
2813            .get(&target)
2814            .and_then(|cfg| cfg.codegen_backends.as_deref())
2815            .unwrap_or(&self.rust_codegen_backends)
2816    }
2817
2818    pub fn jemalloc(&self, target: TargetSelection) -> bool {
2819        self.target_config.get(&target).and_then(|cfg| cfg.jemalloc).unwrap_or(self.jemalloc)
2820    }
2821
2822    pub fn default_codegen_backend(&self, target: TargetSelection) -> Option<String> {
2823        self.codegen_backends(target).first().cloned()
2824    }
2825
2826    pub fn git_config(&self) -> GitConfig<'_> {
2827        GitConfig {
2828            git_repository: &self.stage0_metadata.config.git_repository,
2829            nightly_branch: &self.stage0_metadata.config.nightly_branch,
2830            git_merge_commit_email: &self.stage0_metadata.config.git_merge_commit_email,
2831        }
2832    }
2833
2834    /// Given a path to the directory of a submodule, update it.
2835    ///
2836    /// `relative_path` should be relative to the root of the git repository, not an absolute path.
2837    ///
2838    /// This *does not* update the submodule if `bootstrap.toml` explicitly says
2839    /// not to, or if we're not in a git repository (like a plain source
2840    /// tarball). Typically [`crate::Build::require_submodule`] should be
2841    /// used instead to provide a nice error to the user if the submodule is
2842    /// missing.
2843    #[cfg_attr(
2844        feature = "tracing",
2845        instrument(
2846            level = "trace",
2847            name = "Config::update_submodule",
2848            skip_all,
2849            fields(relative_path = ?relative_path),
2850        ),
2851    )]
2852    pub(crate) fn update_submodule(&self, relative_path: &str) {
2853        if self.rust_info.is_from_tarball() || !self.submodules() {
2854            return;
2855        }
2856
2857        let absolute_path = self.src.join(relative_path);
2858
2859        // NOTE: The check for the empty directory is here because when running x.py the first time,
2860        // the submodule won't be checked out. Check it out now so we can build it.
2861        if !GitInfo::new(false, &absolute_path).is_managed_git_subrepository()
2862            && !helpers::dir_is_empty(&absolute_path)
2863        {
2864            return;
2865        }
2866
2867        // Submodule updating actually happens during in the dry run mode. We need to make sure that
2868        // all the git commands below are actually executed, because some follow-up code
2869        // in bootstrap might depend on the submodules being checked out. Furthermore, not all
2870        // the command executions below work with an empty output (produced during dry run).
2871        // Therefore, all commands below are marked with `run_always()`, so that they also run in
2872        // dry run mode.
2873        let submodule_git = || {
2874            let mut cmd = helpers::git(Some(&absolute_path));
2875            cmd.run_always();
2876            cmd
2877        };
2878
2879        // Determine commit checked out in submodule.
2880        let checked_out_hash = output(submodule_git().args(["rev-parse", "HEAD"]).as_command_mut());
2881        let checked_out_hash = checked_out_hash.trim_end();
2882        // Determine commit that the submodule *should* have.
2883        let recorded = output(
2884            helpers::git(Some(&self.src))
2885                .run_always()
2886                .args(["ls-tree", "HEAD"])
2887                .arg(relative_path)
2888                .as_command_mut(),
2889        );
2890
2891        let actual_hash = recorded
2892            .split_whitespace()
2893            .nth(2)
2894            .unwrap_or_else(|| panic!("unexpected output `{}`", recorded));
2895
2896        if actual_hash == checked_out_hash {
2897            // already checked out
2898            return;
2899        }
2900
2901        println!("Updating submodule {relative_path}");
2902        self.check_run(
2903            helpers::git(Some(&self.src))
2904                .run_always()
2905                .args(["submodule", "-q", "sync"])
2906                .arg(relative_path),
2907        );
2908
2909        // Try passing `--progress` to start, then run git again without if that fails.
2910        let update = |progress: bool| {
2911            // Git is buggy and will try to fetch submodules from the tracking branch for *this* repository,
2912            // even though that has no relation to the upstream for the submodule.
2913            let current_branch = output_result(
2914                helpers::git(Some(&self.src))
2915                    .allow_failure()
2916                    .run_always()
2917                    .args(["symbolic-ref", "--short", "HEAD"])
2918                    .as_command_mut(),
2919            )
2920            .map(|b| b.trim().to_owned());
2921
2922            let mut git = helpers::git(Some(&self.src)).allow_failure();
2923            git.run_always();
2924            if let Ok(branch) = current_branch {
2925                // If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name.
2926                // This syntax isn't accepted by `branch.{branch}`. Strip it.
2927                let branch = branch.strip_prefix("heads/").unwrap_or(&branch);
2928                git.arg("-c").arg(format!("branch.{branch}.remote=origin"));
2929            }
2930            git.args(["submodule", "update", "--init", "--recursive", "--depth=1"]);
2931            if progress {
2932                git.arg("--progress");
2933            }
2934            git.arg(relative_path);
2935            git
2936        };
2937        if !self.check_run(&mut update(true)) {
2938            self.check_run(&mut update(false));
2939        }
2940
2941        // Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error).
2942        // diff-index reports the modifications through the exit status
2943        let has_local_modifications = !self.check_run(submodule_git().allow_failure().args([
2944            "diff-index",
2945            "--quiet",
2946            "HEAD",
2947        ]));
2948        if has_local_modifications {
2949            self.check_run(submodule_git().args(["stash", "push"]));
2950        }
2951
2952        self.check_run(submodule_git().args(["reset", "-q", "--hard"]));
2953        self.check_run(submodule_git().args(["clean", "-qdfx"]));
2954
2955        if has_local_modifications {
2956            self.check_run(submodule_git().args(["stash", "pop"]));
2957        }
2958    }
2959
2960    #[cfg(test)]
2961    pub fn check_stage0_version(&self, _program_path: &Path, _component_name: &'static str) {}
2962
2963    /// check rustc/cargo version is same or lower with 1 apart from the building one
2964    #[cfg(not(test))]
2965    pub fn check_stage0_version(&self, program_path: &Path, component_name: &'static str) {
2966        use build_helper::util::fail;
2967
2968        if self.dry_run() {
2969            return;
2970        }
2971
2972        let stage0_output = output(Command::new(program_path).arg("--version"));
2973        let mut stage0_output = stage0_output.lines().next().unwrap().split(' ');
2974
2975        let stage0_name = stage0_output.next().unwrap();
2976        if stage0_name != component_name {
2977            fail(&format!(
2978                "Expected to find {component_name} at {} but it claims to be {stage0_name}",
2979                program_path.display()
2980            ));
2981        }
2982
2983        let stage0_version =
2984            semver::Version::parse(stage0_output.next().unwrap().split('-').next().unwrap().trim())
2985                .unwrap();
2986        let source_version = semver::Version::parse(
2987            fs::read_to_string(self.src.join("src/version")).unwrap().trim(),
2988        )
2989        .unwrap();
2990        if !(source_version == stage0_version
2991            || (source_version.major == stage0_version.major
2992                && (source_version.minor == stage0_version.minor
2993                    || source_version.minor == stage0_version.minor + 1)))
2994        {
2995            let prev_version = format!("{}.{}.x", source_version.major, source_version.minor - 1);
2996            fail(&format!(
2997                "Unexpected {component_name} version: {stage0_version}, we should use {prev_version}/{source_version} to build source with {source_version}"
2998            ));
2999        }
3000    }
3001
3002    /// Returns the commit to download, or `None` if we shouldn't download CI artifacts.
3003    fn download_ci_rustc_commit(
3004        &self,
3005        download_rustc: Option<StringOrBool>,
3006        debug_assertions_requested: bool,
3007        llvm_assertions: bool,
3008    ) -> Option<String> {
3009        if !is_download_ci_available(&self.build.triple, llvm_assertions) {
3010            return None;
3011        }
3012
3013        // If `download-rustc` is not set, default to rebuilding.
3014        let if_unchanged = match download_rustc {
3015            // Globally default `download-rustc` to `false`, because some contributors don't use
3016            // profiles for reasons such as:
3017            // - They need to seamlessly switch between compiler/library work.
3018            // - They don't want to use compiler profile because they need to override too many
3019            //   things and it's easier to not use a profile.
3020            None | Some(StringOrBool::Bool(false)) => return None,
3021            Some(StringOrBool::Bool(true)) => false,
3022            Some(StringOrBool::String(s)) if s == "if-unchanged" => {
3023                if !self.rust_info.is_managed_git_subrepository() {
3024                    println!(
3025                        "ERROR: `download-rustc=if-unchanged` is only compatible with Git managed sources."
3026                    );
3027                    crate::exit!(1);
3028                }
3029
3030                true
3031            }
3032            Some(StringOrBool::String(other)) => {
3033                panic!("unrecognized option for download-rustc: {other}")
3034            }
3035        };
3036
3037        // RUSTC_IF_UNCHANGED_ALLOWED_PATHS
3038        let mut allowed_paths = RUSTC_IF_UNCHANGED_ALLOWED_PATHS.to_vec();
3039
3040        // In CI, disable ci-rustc if there are changes in the library tree. But for non-CI, allow
3041        // these changes to speed up the build process for library developers. This provides consistent
3042        // functionality for library developers between `download-rustc=true` and `download-rustc="if-unchanged"`
3043        // options.
3044        //
3045        // If you update "library" logic here, update `builder::tests::ci_rustc_if_unchanged_logic` test
3046        // logic accordingly.
3047        if !self.is_running_on_ci {
3048            allowed_paths.push(":!library");
3049        }
3050
3051        let commit = if self.rust_info.is_managed_git_subrepository() {
3052            // Look for a version to compare to based on the current commit.
3053            // Only commits merged by bors will have CI artifacts.
3054            match self.last_modified_commit(&allowed_paths, "download-rustc", if_unchanged) {
3055                Some(commit) => commit,
3056                None => {
3057                    if if_unchanged {
3058                        return None;
3059                    }
3060                    println!("ERROR: could not find commit hash for downloading rustc");
3061                    println!("HELP: maybe your repository history is too shallow?");
3062                    println!(
3063                        "HELP: consider setting `rust.download-rustc=false` in bootstrap.toml"
3064                    );
3065                    println!("HELP: or fetch enough history to include one upstream commit");
3066                    crate::exit!(1);
3067                }
3068            }
3069        } else {
3070            channel::read_commit_info_file(&self.src)
3071                .map(|info| info.sha.trim().to_owned())
3072                .expect("git-commit-info is missing in the project root")
3073        };
3074
3075        if self.is_running_on_ci && {
3076            let head_sha =
3077                output(helpers::git(Some(&self.src)).arg("rev-parse").arg("HEAD").as_command_mut());
3078            let head_sha = head_sha.trim();
3079            commit == head_sha
3080        } {
3081            eprintln!("CI rustc commit matches with HEAD and we are in CI.");
3082            eprintln!(
3083                "`rustc.download-ci` functionality will be skipped as artifacts are not available."
3084            );
3085            return None;
3086        }
3087
3088        if debug_assertions_requested {
3089            eprintln!(
3090                "WARN: `rust.debug-assertions = true` will prevent downloading CI rustc as alt CI \
3091                rustc is not currently built with debug assertions."
3092            );
3093            return None;
3094        }
3095
3096        Some(commit)
3097    }
3098
3099    fn parse_download_ci_llvm(
3100        &self,
3101        download_ci_llvm: Option<StringOrBool>,
3102        asserts: bool,
3103    ) -> bool {
3104        // We don't ever want to use `true` on CI, as we should not
3105        // download upstream artifacts if there are any local modifications.
3106        let default = if self.is_running_on_ci {
3107            StringOrBool::String("if-unchanged".to_string())
3108        } else {
3109            StringOrBool::Bool(true)
3110        };
3111        let download_ci_llvm = download_ci_llvm.unwrap_or(default);
3112
3113        let if_unchanged = || {
3114            if self.rust_info.is_from_tarball() {
3115                // Git is needed for running "if-unchanged" logic.
3116                println!("ERROR: 'if-unchanged' is only compatible with Git managed sources.");
3117                crate::exit!(1);
3118            }
3119
3120            // Fetching the LLVM submodule is unnecessary for self-tests.
3121            #[cfg(not(test))]
3122            self.update_submodule("src/llvm-project");
3123
3124            // Check for untracked changes in `src/llvm-project` and other important places.
3125            let has_changes = self
3126                .last_modified_commit(LLVM_INVALIDATION_PATHS, "download-ci-llvm", true)
3127                .is_none();
3128
3129            // Return false if there are untracked changes, otherwise check if CI LLVM is available.
3130            if has_changes { false } else { llvm::is_ci_llvm_available_for_target(self, asserts) }
3131        };
3132
3133        match download_ci_llvm {
3134            StringOrBool::Bool(b) => {
3135                if !b && self.download_rustc_commit.is_some() {
3136                    panic!(
3137                        "`llvm.download-ci-llvm` cannot be set to `false` if `rust.download-rustc` is set to `true` or `if-unchanged`."
3138                    );
3139                }
3140
3141                if b && self.is_running_on_ci {
3142                    // On CI, we must always rebuild LLVM if there were any modifications to it
3143                    panic!(
3144                        "`llvm.download-ci-llvm` cannot be set to `true` on CI. Use `if-unchanged` instead."
3145                    );
3146                }
3147
3148                // If download-ci-llvm=true we also want to check that CI llvm is available
3149                b && llvm::is_ci_llvm_available_for_target(self, asserts)
3150            }
3151            StringOrBool::String(s) if s == "if-unchanged" => if_unchanged(),
3152            StringOrBool::String(other) => {
3153                panic!("unrecognized option for download-ci-llvm: {:?}", other)
3154            }
3155        }
3156    }
3157
3158    /// Returns the last commit in which any of `modified_paths` were changed,
3159    /// or `None` if there are untracked changes in the working directory and `if_unchanged` is true.
3160    pub fn last_modified_commit(
3161        &self,
3162        modified_paths: &[&str],
3163        option_name: &str,
3164        if_unchanged: bool,
3165    ) -> Option<String> {
3166        assert!(
3167            self.rust_info.is_managed_git_subrepository(),
3168            "Can't run `Config::last_modified_commit` on a non-git source."
3169        );
3170
3171        // Look for a version to compare to based on the current commit.
3172        // Only commits merged by bors will have CI artifacts.
3173        let commit = get_closest_merge_commit(Some(&self.src), &self.git_config(), &[]).unwrap();
3174        if commit.is_empty() {
3175            println!("error: could not find commit hash for downloading components from CI");
3176            println!("help: maybe your repository history is too shallow?");
3177            println!("help: consider disabling `{option_name}`");
3178            println!("help: or fetch enough history to include one upstream commit");
3179            crate::exit!(1);
3180        }
3181
3182        // Warn if there were changes to the compiler or standard library since the ancestor commit.
3183        let mut git = helpers::git(Some(&self.src));
3184        git.args(["diff-index", "--quiet", &commit, "--"]).args(modified_paths);
3185
3186        let has_changes = !t!(git.as_command_mut().status()).success();
3187        if has_changes {
3188            if if_unchanged {
3189                if self.is_verbose() {
3190                    println!(
3191                        "warning: saw changes to one of {modified_paths:?} since {commit}; \
3192                            ignoring `{option_name}`"
3193                    );
3194                }
3195                return None;
3196            }
3197            println!(
3198                "warning: `{option_name}` is enabled, but there are changes to one of {modified_paths:?}"
3199            );
3200        }
3201
3202        Some(commit.to_string())
3203    }
3204}
3205
3206/// Compares the current `Llvm` options against those in the CI LLVM builder and detects any incompatible options.
3207/// It does this by destructuring the `Llvm` instance to make sure every `Llvm` field is covered and not missing.
3208#[cfg(not(test))]
3209pub(crate) fn check_incompatible_options_for_ci_llvm(
3210    current_config_toml: TomlConfig,
3211    ci_config_toml: TomlConfig,
3212) -> Result<(), String> {
3213    macro_rules! err {
3214        ($current:expr, $expected:expr) => {
3215            if let Some(current) = &$current {
3216                if Some(current) != $expected.as_ref() {
3217                    return Err(format!(
3218                        "ERROR: Setting `llvm.{}` is incompatible with `llvm.download-ci-llvm`. \
3219                        Current value: {:?}, Expected value(s): {}{:?}",
3220                        stringify!($expected).replace("_", "-"),
3221                        $current,
3222                        if $expected.is_some() { "None/" } else { "" },
3223                        $expected,
3224                    ));
3225                };
3226            };
3227        };
3228    }
3229
3230    macro_rules! warn {
3231        ($current:expr, $expected:expr) => {
3232            if let Some(current) = &$current {
3233                if Some(current) != $expected.as_ref() {
3234                    println!(
3235                        "WARNING: `llvm.{}` has no effect with `llvm.download-ci-llvm`. \
3236                        Current value: {:?}, Expected value(s): {}{:?}",
3237                        stringify!($expected).replace("_", "-"),
3238                        $current,
3239                        if $expected.is_some() { "None/" } else { "" },
3240                        $expected,
3241                    );
3242                };
3243            };
3244        };
3245    }
3246
3247    let (Some(current_llvm_config), Some(ci_llvm_config)) =
3248        (current_config_toml.llvm, ci_config_toml.llvm)
3249    else {
3250        return Ok(());
3251    };
3252
3253    let Llvm {
3254        optimize,
3255        thin_lto,
3256        release_debuginfo,
3257        assertions: _,
3258        tests: _,
3259        plugins,
3260        ccache: _,
3261        static_libstdcpp: _,
3262        libzstd,
3263        ninja: _,
3264        targets,
3265        experimental_targets,
3266        link_jobs: _,
3267        link_shared: _,
3268        version_suffix,
3269        clang_cl,
3270        cflags,
3271        cxxflags,
3272        ldflags,
3273        use_libcxx,
3274        use_linker,
3275        allow_old_toolchain,
3276        offload,
3277        polly,
3278        clang,
3279        enable_warnings,
3280        download_ci_llvm: _,
3281        build_config,
3282        enzyme,
3283    } = ci_llvm_config;
3284
3285    err!(current_llvm_config.optimize, optimize);
3286    err!(current_llvm_config.thin_lto, thin_lto);
3287    err!(current_llvm_config.release_debuginfo, release_debuginfo);
3288    err!(current_llvm_config.libzstd, libzstd);
3289    err!(current_llvm_config.targets, targets);
3290    err!(current_llvm_config.experimental_targets, experimental_targets);
3291    err!(current_llvm_config.clang_cl, clang_cl);
3292    err!(current_llvm_config.version_suffix, version_suffix);
3293    err!(current_llvm_config.cflags, cflags);
3294    err!(current_llvm_config.cxxflags, cxxflags);
3295    err!(current_llvm_config.ldflags, ldflags);
3296    err!(current_llvm_config.use_libcxx, use_libcxx);
3297    err!(current_llvm_config.use_linker, use_linker);
3298    err!(current_llvm_config.allow_old_toolchain, allow_old_toolchain);
3299    err!(current_llvm_config.offload, offload);
3300    err!(current_llvm_config.polly, polly);
3301    err!(current_llvm_config.clang, clang);
3302    err!(current_llvm_config.build_config, build_config);
3303    err!(current_llvm_config.plugins, plugins);
3304    err!(current_llvm_config.enzyme, enzyme);
3305
3306    warn!(current_llvm_config.enable_warnings, enable_warnings);
3307
3308    Ok(())
3309}
3310
3311/// Compares the current Rust options against those in the CI rustc builder and detects any incompatible options.
3312/// It does this by destructuring the `Rust` instance to make sure every `Rust` field is covered and not missing.
3313fn check_incompatible_options_for_ci_rustc(
3314    host: TargetSelection,
3315    current_config_toml: TomlConfig,
3316    ci_config_toml: TomlConfig,
3317) -> Result<(), String> {
3318    macro_rules! err {
3319        ($current:expr, $expected:expr, $config_section:expr) => {
3320            if let Some(current) = &$current {
3321                if Some(current) != $expected.as_ref() {
3322                    return Err(format!(
3323                        "ERROR: Setting `{}` is incompatible with `rust.download-rustc`. \
3324                        Current value: {:?}, Expected value(s): {}{:?}",
3325                        format!("{}.{}", $config_section, stringify!($expected).replace("_", "-")),
3326                        $current,
3327                        if $expected.is_some() { "None/" } else { "" },
3328                        $expected,
3329                    ));
3330                };
3331            };
3332        };
3333    }
3334
3335    macro_rules! warn {
3336        ($current:expr, $expected:expr, $config_section:expr) => {
3337            if let Some(current) = &$current {
3338                if Some(current) != $expected.as_ref() {
3339                    println!(
3340                        "WARNING: `{}` has no effect with `rust.download-rustc`. \
3341                        Current value: {:?}, Expected value(s): {}{:?}",
3342                        format!("{}.{}", $config_section, stringify!($expected).replace("_", "-")),
3343                        $current,
3344                        if $expected.is_some() { "None/" } else { "" },
3345                        $expected,
3346                    );
3347                };
3348            };
3349        };
3350    }
3351
3352    let current_profiler = current_config_toml.build.as_ref().and_then(|b| b.profiler);
3353    let profiler = ci_config_toml.build.as_ref().and_then(|b| b.profiler);
3354    err!(current_profiler, profiler, "build");
3355
3356    let current_optimized_compiler_builtins =
3357        current_config_toml.build.as_ref().and_then(|b| b.optimized_compiler_builtins);
3358    let optimized_compiler_builtins =
3359        ci_config_toml.build.as_ref().and_then(|b| b.optimized_compiler_builtins);
3360    err!(current_optimized_compiler_builtins, optimized_compiler_builtins, "build");
3361
3362    // We always build the in-tree compiler on cross targets, so we only care
3363    // about the host target here.
3364    let host_str = host.to_string();
3365    if let Some(current_cfg) = current_config_toml.target.as_ref().and_then(|c| c.get(&host_str)) {
3366        if current_cfg.profiler.is_some() {
3367            let ci_target_toml = ci_config_toml.target.as_ref().and_then(|c| c.get(&host_str));
3368            let ci_cfg = ci_target_toml.ok_or(format!(
3369                "Target specific config for '{host_str}' is not present for CI-rustc"
3370            ))?;
3371
3372            let profiler = &ci_cfg.profiler;
3373            err!(current_cfg.profiler, profiler, "build");
3374
3375            let optimized_compiler_builtins = &ci_cfg.optimized_compiler_builtins;
3376            err!(current_cfg.optimized_compiler_builtins, optimized_compiler_builtins, "build");
3377        }
3378    }
3379
3380    let (Some(current_rust_config), Some(ci_rust_config)) =
3381        (current_config_toml.rust, ci_config_toml.rust)
3382    else {
3383        return Ok(());
3384    };
3385
3386    let Rust {
3387        // Following options are the CI rustc incompatible ones.
3388        optimize,
3389        randomize_layout,
3390        debug_logging,
3391        debuginfo_level_rustc,
3392        llvm_tools,
3393        llvm_bitcode_linker,
3394        lto,
3395        stack_protector,
3396        strip,
3397        lld_mode,
3398        jemalloc,
3399        rpath,
3400        channel,
3401        description,
3402        incremental,
3403        default_linker,
3404        std_features,
3405
3406        // Rest of the options can simply be ignored.
3407        debug: _,
3408        codegen_units: _,
3409        codegen_units_std: _,
3410        rustc_debug_assertions: _,
3411        std_debug_assertions: _,
3412        overflow_checks: _,
3413        overflow_checks_std: _,
3414        debuginfo_level: _,
3415        debuginfo_level_std: _,
3416        debuginfo_level_tools: _,
3417        debuginfo_level_tests: _,
3418        backtrace: _,
3419        musl_root: _,
3420        verbose_tests: _,
3421        optimize_tests: _,
3422        codegen_tests: _,
3423        omit_git_hash: _,
3424        dist_src: _,
3425        save_toolstates: _,
3426        codegen_backends: _,
3427        lld: _,
3428        deny_warnings: _,
3429        backtrace_on_ice: _,
3430        verify_llvm_ir: _,
3431        thin_lto_import_instr_limit: _,
3432        remap_debuginfo: _,
3433        test_compare_mode: _,
3434        llvm_libunwind: _,
3435        control_flow_guard: _,
3436        ehcont_guard: _,
3437        new_symbol_mangling: _,
3438        profile_generate: _,
3439        profile_use: _,
3440        download_rustc: _,
3441        validate_mir_opts: _,
3442        frame_pointers: _,
3443    } = ci_rust_config;
3444
3445    // There are two kinds of checks for CI rustc incompatible options:
3446    //    1. Checking an option that may change the compiler behaviour/output.
3447    //    2. Checking an option that have no effect on the compiler behaviour/output.
3448    //
3449    // If the option belongs to the first category, we call `err` macro for a hard error;
3450    // otherwise, we just print a warning with `warn` macro.
3451
3452    err!(current_rust_config.optimize, optimize, "rust");
3453    err!(current_rust_config.randomize_layout, randomize_layout, "rust");
3454    err!(current_rust_config.debug_logging, debug_logging, "rust");
3455    err!(current_rust_config.debuginfo_level_rustc, debuginfo_level_rustc, "rust");
3456    err!(current_rust_config.rpath, rpath, "rust");
3457    err!(current_rust_config.strip, strip, "rust");
3458    err!(current_rust_config.lld_mode, lld_mode, "rust");
3459    err!(current_rust_config.llvm_tools, llvm_tools, "rust");
3460    err!(current_rust_config.llvm_bitcode_linker, llvm_bitcode_linker, "rust");
3461    err!(current_rust_config.jemalloc, jemalloc, "rust");
3462    err!(current_rust_config.default_linker, default_linker, "rust");
3463    err!(current_rust_config.stack_protector, stack_protector, "rust");
3464    err!(current_rust_config.lto, lto, "rust");
3465    err!(current_rust_config.std_features, std_features, "rust");
3466
3467    warn!(current_rust_config.channel, channel, "rust");
3468    warn!(current_rust_config.description, description, "rust");
3469    warn!(current_rust_config.incremental, incremental, "rust");
3470
3471    Ok(())
3472}
3473
3474fn set<T>(field: &mut T, val: Option<T>) {
3475    if let Some(v) = val {
3476        *field = v;
3477    }
3478}
3479
3480fn threads_from_config(v: u32) -> u32 {
3481    match v {
3482        0 => std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) as u32,
3483        n => n,
3484    }
3485}