bootstrap/core/config/
config.rs

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