bootstrap/core/config/
config.rs

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