bootstrap/core/config/
config.rs

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