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