bootstrap/core/config/
config.rs

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