bootstrap/core/config/
config.rs

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