bootstrap/core/config/
config.rs

1//! This module defines the central `Config` struct, which aggregates all components
2//! of the bootstrap configuration into a single unit.
3//!
4//! It serves as the primary public interface for accessing the bootstrap configuration.
5//! The module coordinates the overall configuration parsing process using logic from `parsing.rs`
6//! and provides top-level methods such as `Config::parse()` for initialization, as well as
7//! utility methods for querying and manipulating the complete configuration state.
8//!
9//! Additionally, this module contains the core logic for parsing, validating, and inferring
10//! the final `Config` from various raw inputs.
11//!
12//! It manages the process of reading command-line arguments, environment variables,
13//! and the `bootstrap.toml` file—merging them, applying defaults, and performing
14//! cross-component validation. The main `parse_inner` function and its supporting
15//! helpers reside here, transforming raw `Toml` data into the structured `Config` type.
16
17use std::cell::Cell;
18use std::collections::{BTreeSet, HashMap, HashSet};
19use std::io::IsTerminal;
20use std::path::{Path, PathBuf, absolute};
21use std::str::FromStr;
22use std::sync::{Arc, Mutex};
23use std::{cmp, env, fs};
24
25use build_helper::ci::CiEnv;
26use build_helper::exit;
27use build_helper::git::{GitConfig, PathFreshness, check_path_modifications, output_result};
28use serde::Deserialize;
29#[cfg(feature = "tracing")]
30use tracing::{instrument, span};
31#[cfg(feature = "tracing")]
32use tracing::{instrument, span};
33
34use crate::core::build_steps::llvm;
35use crate::core::build_steps::llvm::LLVM_INVALIDATION_PATHS;
36pub use crate::core::config::flags::Subcommand;
37use crate::core::config::flags::{Color, Flags};
38use crate::core::config::target_selection::TargetSelectionList;
39use crate::core::config::toml::TomlConfig;
40use crate::core::config::toml::build::Build;
41use crate::core::config::toml::change_id::ChangeId;
42use crate::core::config::toml::rust::{
43    LldMode, RustOptimize, check_incompatible_options_for_ci_rustc,
44};
45use crate::core::config::toml::target::Target;
46use crate::core::config::{
47    DebuginfoLevel, DryRun, GccCiMode, LlvmLibunwind, Merge, ReplaceOpt, RustcLto, SplitDebuginfo,
48    StringOrBool, set, threads_from_config,
49};
50use crate::core::download::is_download_ci_available;
51use crate::utils::channel;
52use crate::utils::helpers::exe;
53use crate::{Command, GitInfo, OnceLock, TargetSelection, check_ci_llvm, helpers, output, t};
54
55/// Each path in this list is considered "allowed" in the `download-rustc="if-unchanged"` logic.
56/// This means they can be modified and changes to these paths should never trigger a compiler build
57/// when "if-unchanged" is set.
58///
59/// NOTE: Paths must have the ":!" prefix to tell git to ignore changes in those paths during
60/// the diff check.
61///
62/// WARNING: Be cautious when adding paths to this list. If a path that influences the compiler build
63/// is added here, it will cause bootstrap to skip necessary rebuilds, which may lead to risky results.
64/// For example, "src/bootstrap" should never be included in this list as it plays a crucial role in the
65/// final output/compiler, which can be significantly affected by changes made to the bootstrap sources.
66#[rustfmt::skip] // We don't want rustfmt to oneline this list
67pub const RUSTC_IF_UNCHANGED_ALLOWED_PATHS: &[&str] = &[
68    ":!library",
69    ":!src/tools",
70    ":!src/librustdoc",
71    ":!src/rustdoc-json-types",
72    ":!tests",
73    ":!triagebot.toml",
74];
75
76/// Global configuration for the entire build and/or bootstrap.
77///
78/// This structure is parsed from `bootstrap.toml`, and some of the fields are inferred from `git` or build-time parameters.
79///
80/// Note that this structure is not decoded directly into, but rather it is
81/// filled out from the decoded forms of the structs below. For documentation
82/// on each field, see the corresponding fields in
83/// `bootstrap.example.toml`.
84#[derive(Default, Clone)]
85pub struct Config {
86    pub change_id: Option<ChangeId>,
87    pub bypass_bootstrap_lock: bool,
88    pub ccache: Option<String>,
89    /// Call Build::ninja() instead of this.
90    pub ninja_in_file: bool,
91    pub verbose: usize,
92    pub submodules: Option<bool>,
93    pub compiler_docs: bool,
94    pub library_docs_private_items: bool,
95    pub docs_minification: bool,
96    pub docs: bool,
97    pub locked_deps: bool,
98    pub vendor: bool,
99    pub target_config: HashMap<TargetSelection, Target>,
100    pub full_bootstrap: bool,
101    pub bootstrap_cache_path: Option<PathBuf>,
102    pub extended: bool,
103    pub tools: Option<HashSet<String>>,
104    pub sanitizers: bool,
105    pub profiler: bool,
106    pub omit_git_hash: bool,
107    pub skip: Vec<PathBuf>,
108    pub include_default_paths: bool,
109    pub rustc_error_format: Option<String>,
110    pub json_output: bool,
111    pub test_compare_mode: bool,
112    pub color: Color,
113    pub patch_binaries_for_nix: Option<bool>,
114    pub stage0_metadata: build_helper::stage0_parser::Stage0,
115    pub android_ndk: Option<PathBuf>,
116    /// Whether to use the `c` feature of the `compiler_builtins` crate.
117    pub optimized_compiler_builtins: bool,
118
119    pub stdout_is_tty: bool,
120    pub stderr_is_tty: bool,
121
122    pub on_fail: Option<String>,
123    pub explicit_stage_from_cli: bool,
124    pub explicit_stage_from_config: bool,
125    pub stage: u32,
126    pub keep_stage: Vec<u32>,
127    pub keep_stage_std: Vec<u32>,
128    pub src: PathBuf,
129    /// defaults to `bootstrap.toml`
130    pub config: Option<PathBuf>,
131    pub jobs: Option<u32>,
132    pub cmd: Subcommand,
133    pub incremental: bool,
134    pub dry_run: DryRun,
135    pub dump_bootstrap_shims: bool,
136    /// Arguments appearing after `--` to be forwarded to tools,
137    /// e.g. `--fix-broken` or test arguments.
138    pub free_args: Vec<String>,
139
140    /// `None` if we shouldn't download CI compiler artifacts, or the commit to download if we should.
141    pub download_rustc_commit: Option<String>,
142
143    pub deny_warnings: bool,
144    pub backtrace_on_ice: bool,
145
146    // llvm codegen options
147    pub llvm_assertions: bool,
148    pub llvm_tests: bool,
149    pub llvm_enzyme: bool,
150    pub llvm_offload: bool,
151    pub llvm_plugins: bool,
152    pub llvm_optimize: bool,
153    pub llvm_thin_lto: bool,
154    pub llvm_release_debuginfo: bool,
155    pub llvm_static_stdcpp: bool,
156    pub llvm_libzstd: bool,
157    pub llvm_link_shared: Cell<Option<bool>>,
158    pub llvm_clang_cl: Option<String>,
159    pub llvm_targets: Option<String>,
160    pub llvm_experimental_targets: Option<String>,
161    pub llvm_link_jobs: Option<u32>,
162    pub llvm_version_suffix: Option<String>,
163    pub llvm_use_linker: Option<String>,
164    pub llvm_allow_old_toolchain: bool,
165    pub llvm_polly: bool,
166    pub llvm_clang: bool,
167    pub llvm_enable_warnings: bool,
168    pub llvm_from_ci: bool,
169    pub llvm_build_config: HashMap<String, String>,
170
171    pub lld_mode: LldMode,
172    pub lld_enabled: bool,
173    pub llvm_tools_enabled: bool,
174    pub llvm_bitcode_linker_enabled: bool,
175
176    pub llvm_cflags: Option<String>,
177    pub llvm_cxxflags: Option<String>,
178    pub llvm_ldflags: Option<String>,
179    pub llvm_use_libcxx: bool,
180
181    // gcc codegen options
182    pub gcc_ci_mode: GccCiMode,
183
184    // rust codegen options
185    pub rust_optimize: RustOptimize,
186    pub rust_codegen_units: Option<u32>,
187    pub rust_codegen_units_std: Option<u32>,
188
189    pub rustc_debug_assertions: bool,
190    pub std_debug_assertions: bool,
191    pub tools_debug_assertions: bool,
192
193    pub rust_overflow_checks: bool,
194    pub rust_overflow_checks_std: bool,
195    pub rust_debug_logging: bool,
196    pub rust_debuginfo_level_rustc: DebuginfoLevel,
197    pub rust_debuginfo_level_std: DebuginfoLevel,
198    pub rust_debuginfo_level_tools: DebuginfoLevel,
199    pub rust_debuginfo_level_tests: DebuginfoLevel,
200    pub rust_rpath: bool,
201    pub rust_strip: bool,
202    pub rust_frame_pointers: bool,
203    pub rust_stack_protector: Option<String>,
204    pub rustc_default_linker: Option<String>,
205    pub rust_optimize_tests: bool,
206    pub rust_dist_src: bool,
207    pub rust_codegen_backends: Vec<String>,
208    pub rust_verify_llvm_ir: bool,
209    pub rust_thin_lto_import_instr_limit: Option<u32>,
210    pub rust_randomize_layout: bool,
211    pub rust_remap_debuginfo: bool,
212    pub rust_new_symbol_mangling: Option<bool>,
213    pub rust_profile_use: Option<String>,
214    pub rust_profile_generate: Option<String>,
215    pub rust_lto: RustcLto,
216    pub rust_validate_mir_opts: Option<u32>,
217    pub rust_std_features: BTreeSet<String>,
218    pub llvm_profile_use: Option<String>,
219    pub llvm_profile_generate: bool,
220    pub llvm_libunwind_default: Option<LlvmLibunwind>,
221    pub enable_bolt_settings: bool,
222
223    pub reproducible_artifacts: Vec<String>,
224
225    pub build: TargetSelection,
226    pub hosts: Vec<TargetSelection>,
227    pub targets: Vec<TargetSelection>,
228    pub local_rebuild: bool,
229    pub jemalloc: bool,
230    pub control_flow_guard: bool,
231    pub ehcont_guard: bool,
232
233    // dist misc
234    pub dist_sign_folder: Option<PathBuf>,
235    pub dist_upload_addr: Option<String>,
236    pub dist_compression_formats: Option<Vec<String>>,
237    pub dist_compression_profile: String,
238    pub dist_include_mingw_linker: bool,
239    pub dist_vendor: bool,
240
241    // libstd features
242    pub backtrace: bool, // support for RUST_BACKTRACE
243
244    // misc
245    pub low_priority: bool,
246    pub channel: String,
247    pub description: Option<String>,
248    pub verbose_tests: bool,
249    pub save_toolstates: Option<PathBuf>,
250    pub print_step_timings: bool,
251    pub print_step_rusage: bool,
252
253    // Fallback musl-root for all targets
254    pub musl_root: Option<PathBuf>,
255    pub prefix: Option<PathBuf>,
256    pub sysconfdir: Option<PathBuf>,
257    pub datadir: Option<PathBuf>,
258    pub docdir: Option<PathBuf>,
259    pub bindir: PathBuf,
260    pub libdir: Option<PathBuf>,
261    pub mandir: Option<PathBuf>,
262    pub codegen_tests: bool,
263    pub nodejs: Option<PathBuf>,
264    pub npm: Option<PathBuf>,
265    pub gdb: Option<PathBuf>,
266    pub lldb: Option<PathBuf>,
267    pub python: Option<PathBuf>,
268    pub reuse: Option<PathBuf>,
269    pub cargo_native_static: bool,
270    pub configure_args: Vec<String>,
271    pub out: PathBuf,
272    pub rust_info: channel::GitInfo,
273
274    pub cargo_info: channel::GitInfo,
275    pub rust_analyzer_info: channel::GitInfo,
276    pub clippy_info: channel::GitInfo,
277    pub miri_info: channel::GitInfo,
278    pub rustfmt_info: channel::GitInfo,
279    pub enzyme_info: channel::GitInfo,
280    pub in_tree_llvm_info: channel::GitInfo,
281    pub in_tree_gcc_info: channel::GitInfo,
282
283    // These are either the stage0 downloaded binaries or the locally installed ones.
284    pub initial_cargo: PathBuf,
285    pub initial_rustc: PathBuf,
286    pub initial_cargo_clippy: Option<PathBuf>,
287    pub initial_sysroot: PathBuf,
288    pub initial_rustfmt: Option<PathBuf>,
289
290    /// The paths to work with. For example: with `./x check foo bar` we get
291    /// `paths=["foo", "bar"]`.
292    pub paths: Vec<PathBuf>,
293
294    /// Command for visual diff display, e.g. `diff-tool --color=always`.
295    pub compiletest_diff_tool: Option<String>,
296
297    /// Whether to use the precompiled stage0 libtest with compiletest.
298    pub compiletest_use_stage0_libtest: bool,
299
300    pub is_running_on_ci: bool,
301
302    /// Cache for determining path modifications
303    pub path_modification_cache: Arc<Mutex<HashMap<Vec<&'static str>, PathFreshness>>>,
304
305    /// Skip checking the standard library if `rust.download-rustc` isn't available.
306    /// This is mostly for RA as building the stage1 compiler to check the library tree
307    /// on each code change might be too much for some computers.
308    pub skip_std_check_if_no_download_rustc: bool,
309}
310
311impl Config {
312    #[cfg_attr(
313        feature = "tracing",
314        instrument(target = "CONFIG_HANDLING", level = "trace", name = "Config::default_opts")
315    )]
316    pub fn default_opts() -> Config {
317        #[cfg(feature = "tracing")]
318        span!(target: "CONFIG_HANDLING", tracing::Level::TRACE, "constructing default config");
319
320        Config {
321            bypass_bootstrap_lock: false,
322            llvm_optimize: true,
323            ninja_in_file: true,
324            llvm_static_stdcpp: false,
325            llvm_libzstd: false,
326            backtrace: true,
327            rust_optimize: RustOptimize::Bool(true),
328            rust_optimize_tests: true,
329            rust_randomize_layout: false,
330            submodules: None,
331            docs: true,
332            docs_minification: true,
333            rust_rpath: true,
334            rust_strip: false,
335            channel: "dev".to_string(),
336            codegen_tests: true,
337            rust_dist_src: true,
338            rust_codegen_backends: vec!["llvm".to_owned()],
339            deny_warnings: true,
340            bindir: "bin".into(),
341            dist_include_mingw_linker: true,
342            dist_compression_profile: "fast".into(),
343
344            stdout_is_tty: std::io::stdout().is_terminal(),
345            stderr_is_tty: std::io::stderr().is_terminal(),
346
347            // set by build.rs
348            build: TargetSelection::from_user(env!("BUILD_TRIPLE")),
349
350            src: {
351                let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
352                // Undo `src/bootstrap`
353                manifest_dir.parent().unwrap().parent().unwrap().to_owned()
354            },
355            out: PathBuf::from("build"),
356
357            // This is needed by codegen_ssa on macOS to ship `llvm-objcopy` aliased to
358            // `rust-objcopy` to workaround bad `strip`s on macOS.
359            llvm_tools_enabled: true,
360
361            ..Default::default()
362        }
363    }
364
365    #[cfg_attr(
366        feature = "tracing",
367        instrument(target = "CONFIG_HANDLING", level = "trace", name = "Config::parse", skip_all)
368    )]
369    pub fn parse(flags: Flags) -> Config {
370        Self::parse_inner(flags, Self::get_toml)
371    }
372
373    #[cfg_attr(
374        feature = "tracing",
375        instrument(
376            target = "CONFIG_HANDLING",
377            level = "trace",
378            name = "Config::parse_inner",
379            skip_all
380        )
381    )]
382    pub(crate) fn parse_inner(
383        mut flags: Flags,
384        get_toml: impl Fn(&Path) -> Result<TomlConfig, toml::de::Error>,
385    ) -> Config {
386        let mut config = Config::default_opts();
387
388        // Set flags.
389        config.paths = std::mem::take(&mut flags.paths);
390
391        #[cfg(feature = "tracing")]
392        span!(
393            target: "CONFIG_HANDLING",
394            tracing::Level::TRACE,
395            "collecting paths and path exclusions",
396            "flags.paths" = ?flags.paths,
397            "flags.skip" = ?flags.skip,
398            "flags.exclude" = ?flags.exclude
399        );
400
401        #[cfg(feature = "tracing")]
402        span!(
403            target: "CONFIG_HANDLING",
404            tracing::Level::TRACE,
405            "normalizing and combining `flag.skip`/`flag.exclude` paths",
406            "config.skip" = ?config.skip,
407        );
408
409        config.include_default_paths = flags.include_default_paths;
410        config.rustc_error_format = flags.rustc_error_format;
411        config.json_output = flags.json_output;
412        config.on_fail = flags.on_fail;
413        config.cmd = flags.cmd;
414        config.incremental = flags.incremental;
415        config.dry_run = if flags.dry_run { DryRun::UserSelected } else { DryRun::Disabled };
416        config.dump_bootstrap_shims = flags.dump_bootstrap_shims;
417        config.keep_stage = flags.keep_stage;
418        config.keep_stage_std = flags.keep_stage_std;
419        config.color = flags.color;
420        config.free_args = std::mem::take(&mut flags.free_args);
421        config.llvm_profile_use = flags.llvm_profile_use;
422        config.llvm_profile_generate = flags.llvm_profile_generate;
423        config.enable_bolt_settings = flags.enable_bolt_settings;
424        config.bypass_bootstrap_lock = flags.bypass_bootstrap_lock;
425        config.is_running_on_ci = flags.ci.unwrap_or(CiEnv::is_ci());
426        config.skip_std_check_if_no_download_rustc = flags.skip_std_check_if_no_download_rustc;
427
428        // Infer the rest of the configuration.
429
430        if let Some(src) = flags.src {
431            config.src = src
432        } else {
433            // Infer the source directory. This is non-trivial because we want to support a downloaded bootstrap binary,
434            // running on a completely different machine from where it was compiled.
435            let mut cmd = helpers::git(None);
436            // NOTE: we cannot support running from outside the repository because the only other path we have available
437            // is set at compile time, which can be wrong if bootstrap was downloaded rather than compiled locally.
438            // We still support running outside the repository if we find we aren't in a git directory.
439
440            // NOTE: We get a relative path from git to work around an issue on MSYS/mingw. If we used an absolute path,
441            // and end up using MSYS's git rather than git-for-windows, we would get a unix-y MSYS path. But as bootstrap
442            // has already been (kinda-cross-)compiled to Windows land, we require a normal Windows path.
443            cmd.arg("rev-parse").arg("--show-cdup");
444            // Discard stderr because we expect this to fail when building from a tarball.
445            let output = cmd
446                .as_command_mut()
447                .stderr(std::process::Stdio::null())
448                .output()
449                .ok()
450                .and_then(|output| if output.status.success() { Some(output) } else { None });
451            if let Some(output) = output {
452                let git_root_relative = String::from_utf8(output.stdout).unwrap();
453                // We need to canonicalize this path to make sure it uses backslashes instead of forward slashes,
454                // and to resolve any relative components.
455                let git_root = env::current_dir()
456                    .unwrap()
457                    .join(PathBuf::from(git_root_relative.trim()))
458                    .canonicalize()
459                    .unwrap();
460                let s = git_root.to_str().unwrap();
461
462                // Bootstrap is quite bad at handling /? in front of paths
463                let git_root = match s.strip_prefix("\\\\?\\") {
464                    Some(p) => PathBuf::from(p),
465                    None => git_root,
466                };
467                // If this doesn't have at least `stage0`, we guessed wrong. This can happen when,
468                // for example, the build directory is inside of another unrelated git directory.
469                // In that case keep the original `CARGO_MANIFEST_DIR` handling.
470                //
471                // NOTE: this implies that downloadable bootstrap isn't supported when the build directory is outside
472                // the source directory. We could fix that by setting a variable from all three of python, ./x, and x.ps1.
473                if git_root.join("src").join("stage0").exists() {
474                    config.src = git_root;
475                }
476            } else {
477                // We're building from a tarball, not git sources.
478                // We don't support pre-downloaded bootstrap in this case.
479            }
480        }
481
482        if cfg!(test) {
483            // Use the build directory of the original x.py invocation, so that we can set `initial_rustc` properly.
484            config.out = Path::new(
485                &env::var_os("CARGO_TARGET_DIR").expect("cargo test directly is not supported"),
486            )
487            .parent()
488            .unwrap()
489            .to_path_buf();
490        }
491
492        config.stage0_metadata = build_helper::stage0_parser::parse_stage0_file();
493
494        // Locate the configuration file using the following priority (first match wins):
495        // 1. `--config <path>` (explicit flag)
496        // 2. `RUST_BOOTSTRAP_CONFIG` environment variable
497        // 3. `./bootstrap.toml` (local file)
498        // 4. `<root>/bootstrap.toml`
499        // 5. `./config.toml` (fallback for backward compatibility)
500        // 6. `<root>/config.toml`
501        let toml_path = flags
502            .config
503            .clone()
504            .or_else(|| env::var_os("RUST_BOOTSTRAP_CONFIG").map(PathBuf::from));
505        let using_default_path = toml_path.is_none();
506        let mut toml_path = toml_path.unwrap_or_else(|| PathBuf::from("bootstrap.toml"));
507
508        if using_default_path && !toml_path.exists() {
509            toml_path = config.src.join(PathBuf::from("bootstrap.toml"));
510            if !toml_path.exists() {
511                toml_path = PathBuf::from("config.toml");
512                if !toml_path.exists() {
513                    toml_path = config.src.join(PathBuf::from("config.toml"));
514                }
515            }
516        }
517
518        // Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path,
519        // but not if `bootstrap.toml` hasn't been created.
520        let mut toml = if !using_default_path || toml_path.exists() {
521            config.config = Some(if cfg!(not(test)) {
522                toml_path = toml_path.canonicalize().unwrap();
523                toml_path.clone()
524            } else {
525                toml_path.clone()
526            });
527            get_toml(&toml_path).unwrap_or_else(|e| {
528                eprintln!("ERROR: Failed to parse '{}': {e}", toml_path.display());
529                exit!(2);
530            })
531        } else {
532            config.config = None;
533            TomlConfig::default()
534        };
535
536        if cfg!(test) {
537            // When configuring bootstrap for tests, make sure to set the rustc and Cargo to the
538            // same ones used to call the tests (if custom ones are not defined in the toml). If we
539            // don't do that, bootstrap will use its own detection logic to find a suitable rustc
540            // and Cargo, which doesn't work when the caller is specìfying a custom local rustc or
541            // Cargo in their bootstrap.toml.
542            let build = toml.build.get_or_insert_with(Default::default);
543            build.rustc = build.rustc.take().or(std::env::var_os("RUSTC").map(|p| p.into()));
544            build.cargo = build.cargo.take().or(std::env::var_os("CARGO").map(|p| p.into()));
545        }
546
547        if GitInfo::new(false, &config.src).is_from_tarball() && toml.profile.is_none() {
548            toml.profile = Some("dist".into());
549        }
550
551        // Reverse the list to ensure the last added config extension remains the most dominant.
552        // For example, given ["a.toml", "b.toml"], "b.toml" should take precedence over "a.toml".
553        //
554        // This must be handled before applying the `profile` since `include`s should always take
555        // precedence over `profile`s.
556        for include_path in toml.include.clone().unwrap_or_default().iter().rev() {
557            let include_path = toml_path.parent().unwrap().join(include_path);
558
559            let included_toml = get_toml(&include_path).unwrap_or_else(|e| {
560                eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display());
561                exit!(2);
562            });
563            toml.merge(
564                Some(include_path),
565                &mut Default::default(),
566                included_toml,
567                ReplaceOpt::IgnoreDuplicate,
568            );
569        }
570
571        if let Some(include) = &toml.profile {
572            // Allows creating alias for profile names, allowing
573            // profiles to be renamed while maintaining back compatibility
574            // Keep in sync with `profile_aliases` in bootstrap.py
575            let profile_aliases = HashMap::from([("user", "dist")]);
576            let include = match profile_aliases.get(include.as_str()) {
577                Some(alias) => alias,
578                None => include.as_str(),
579            };
580            let mut include_path = config.src.clone();
581            include_path.push("src");
582            include_path.push("bootstrap");
583            include_path.push("defaults");
584            include_path.push(format!("bootstrap.{include}.toml"));
585            let included_toml = get_toml(&include_path).unwrap_or_else(|e| {
586                eprintln!(
587                    "ERROR: Failed to parse default config profile at '{}': {e}",
588                    include_path.display()
589                );
590                exit!(2);
591            });
592            toml.merge(
593                Some(include_path),
594                &mut Default::default(),
595                included_toml,
596                ReplaceOpt::IgnoreDuplicate,
597            );
598        }
599
600        let mut override_toml = TomlConfig::default();
601        for option in flags.set.iter() {
602            fn get_table(option: &str) -> Result<TomlConfig, toml::de::Error> {
603                toml::from_str(option).and_then(|table: toml::Value| TomlConfig::deserialize(table))
604            }
605
606            let mut err = match get_table(option) {
607                Ok(v) => {
608                    override_toml.merge(
609                        None,
610                        &mut Default::default(),
611                        v,
612                        ReplaceOpt::ErrorOnDuplicate,
613                    );
614                    continue;
615                }
616                Err(e) => e,
617            };
618            // We want to be able to set string values without quotes,
619            // like in `configure.py`. Try adding quotes around the right hand side
620            if let Some((key, value)) = option.split_once('=')
621                && !value.contains('"')
622            {
623                match get_table(&format!(r#"{key}="{value}""#)) {
624                    Ok(v) => {
625                        override_toml.merge(
626                            None,
627                            &mut Default::default(),
628                            v,
629                            ReplaceOpt::ErrorOnDuplicate,
630                        );
631                        continue;
632                    }
633                    Err(e) => err = e,
634                }
635            }
636            eprintln!("failed to parse override `{option}`: `{err}");
637            exit!(2)
638        }
639        toml.merge(None, &mut Default::default(), override_toml, ReplaceOpt::Override);
640
641        config.change_id = toml.change_id.inner;
642
643        let Build {
644            mut description,
645            build,
646            host,
647            target,
648            build_dir,
649            cargo,
650            rustc,
651            rustfmt,
652            cargo_clippy,
653            docs,
654            compiler_docs,
655            library_docs_private_items,
656            docs_minification,
657            submodules,
658            gdb,
659            lldb,
660            nodejs,
661            npm,
662            python,
663            reuse,
664            locked_deps,
665            vendor,
666            full_bootstrap,
667            bootstrap_cache_path,
668            extended,
669            tools,
670            verbose,
671            sanitizers,
672            profiler,
673            cargo_native_static,
674            low_priority,
675            configure_args,
676            local_rebuild,
677            print_step_timings,
678            print_step_rusage,
679            check_stage,
680            doc_stage,
681            build_stage,
682            test_stage,
683            install_stage,
684            dist_stage,
685            bench_stage,
686            patch_binaries_for_nix,
687            // This field is only used by bootstrap.py
688            metrics: _,
689            android_ndk,
690            optimized_compiler_builtins,
691            jobs,
692            compiletest_diff_tool,
693            compiletest_use_stage0_libtest,
694            mut ccache,
695            exclude,
696        } = toml.build.unwrap_or_default();
697
698        let mut paths: Vec<PathBuf> = flags.skip.into_iter().chain(flags.exclude).collect();
699
700        if let Some(exclude) = exclude {
701            paths.extend(exclude);
702        }
703
704        config.skip = paths
705            .into_iter()
706            .map(|p| {
707                // Never return top-level path here as it would break `--skip`
708                // logic on rustc's internal test framework which is utilized
709                // by compiletest.
710                if cfg!(windows) {
711                    PathBuf::from(p.to_str().unwrap().replace('/', "\\"))
712                } else {
713                    p
714                }
715            })
716            .collect();
717
718        config.jobs = Some(threads_from_config(flags.jobs.unwrap_or(jobs.unwrap_or(0))));
719
720        if let Some(file_build) = build {
721            config.build = TargetSelection::from_user(&file_build);
722        };
723
724        set(&mut config.out, flags.build_dir.or_else(|| build_dir.map(PathBuf::from)));
725        // NOTE: Bootstrap spawns various commands with different working directories.
726        // To avoid writing to random places on the file system, `config.out` needs to be an absolute path.
727        if !config.out.is_absolute() {
728            // `canonicalize` requires the path to already exist. Use our vendored copy of `absolute` instead.
729            config.out = absolute(&config.out).expect("can't make empty path absolute");
730        }
731
732        if cargo_clippy.is_some() && rustc.is_none() {
733            println!(
734                "WARNING: Using `build.cargo-clippy` without `build.rustc` usually fails due to toolchain conflict."
735            );
736        }
737
738        config.initial_rustc = if let Some(rustc) = rustc {
739            if !flags.skip_stage0_validation {
740                config.check_stage0_version(&rustc, "rustc");
741            }
742            rustc
743        } else {
744            config.download_beta_toolchain();
745            config
746                .out
747                .join(config.build)
748                .join("stage0")
749                .join("bin")
750                .join(exe("rustc", config.build))
751        };
752
753        config.initial_sysroot = t!(PathBuf::from_str(
754            output(Command::new(&config.initial_rustc).args(["--print", "sysroot"])).trim()
755        ));
756
757        config.initial_cargo_clippy = cargo_clippy;
758
759        config.initial_cargo = if let Some(cargo) = cargo {
760            if !flags.skip_stage0_validation {
761                config.check_stage0_version(&cargo, "cargo");
762            }
763            cargo
764        } else {
765            config.download_beta_toolchain();
766            config.initial_sysroot.join("bin").join(exe("cargo", config.build))
767        };
768
769        // NOTE: it's important this comes *after* we set `initial_rustc` just above.
770        if config.dry_run() {
771            let dir = config.out.join("tmp-dry-run");
772            t!(fs::create_dir_all(&dir));
773            config.out = dir;
774        }
775
776        config.hosts = if let Some(TargetSelectionList(arg_host)) = flags.host {
777            arg_host
778        } else if let Some(file_host) = host {
779            file_host.iter().map(|h| TargetSelection::from_user(h)).collect()
780        } else {
781            vec![config.build]
782        };
783        config.targets = if let Some(TargetSelectionList(arg_target)) = flags.target {
784            arg_target
785        } else if let Some(file_target) = target {
786            file_target.iter().map(|h| TargetSelection::from_user(h)).collect()
787        } else {
788            // If target is *not* configured, then default to the host
789            // toolchains.
790            config.hosts.clone()
791        };
792
793        config.nodejs = nodejs.map(PathBuf::from);
794        config.npm = npm.map(PathBuf::from);
795        config.gdb = gdb.map(PathBuf::from);
796        config.lldb = lldb.map(PathBuf::from);
797        config.python = python.map(PathBuf::from);
798        config.reuse = reuse.map(PathBuf::from);
799        config.submodules = submodules;
800        config.android_ndk = android_ndk;
801        config.bootstrap_cache_path = bootstrap_cache_path;
802        set(&mut config.low_priority, low_priority);
803        set(&mut config.compiler_docs, compiler_docs);
804        set(&mut config.library_docs_private_items, library_docs_private_items);
805        set(&mut config.docs_minification, docs_minification);
806        set(&mut config.docs, docs);
807        set(&mut config.locked_deps, locked_deps);
808        set(&mut config.full_bootstrap, full_bootstrap);
809        set(&mut config.extended, extended);
810        config.tools = tools;
811        set(&mut config.verbose, verbose);
812        set(&mut config.sanitizers, sanitizers);
813        set(&mut config.profiler, profiler);
814        set(&mut config.cargo_native_static, cargo_native_static);
815        set(&mut config.configure_args, configure_args);
816        set(&mut config.local_rebuild, local_rebuild);
817        set(&mut config.print_step_timings, print_step_timings);
818        set(&mut config.print_step_rusage, print_step_rusage);
819        config.patch_binaries_for_nix = patch_binaries_for_nix;
820
821        config.verbose = cmp::max(config.verbose, flags.verbose as usize);
822
823        // Verbose flag is a good default for `rust.verbose-tests`.
824        config.verbose_tests = config.is_verbose();
825
826        config.apply_install_config(toml.install);
827
828        config.llvm_assertions =
829            toml.llvm.as_ref().is_some_and(|llvm| llvm.assertions.unwrap_or(false));
830
831        let file_content = t!(fs::read_to_string(config.src.join("src/ci/channel")));
832        let ci_channel = file_content.trim_end();
833
834        let toml_channel = toml.rust.as_ref().and_then(|r| r.channel.clone());
835        let is_user_configured_rust_channel = match toml_channel {
836            Some(channel) if channel == "auto-detect" => {
837                config.channel = ci_channel.into();
838                true
839            }
840            Some(channel) => {
841                config.channel = channel;
842                true
843            }
844            None => false,
845        };
846
847        let default = config.channel == "dev";
848        config.omit_git_hash = toml.rust.as_ref().and_then(|r| r.omit_git_hash).unwrap_or(default);
849
850        config.rust_info = GitInfo::new(config.omit_git_hash, &config.src);
851        config.cargo_info = GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/cargo"));
852        config.rust_analyzer_info =
853            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/rust-analyzer"));
854        config.clippy_info =
855            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/clippy"));
856        config.miri_info = GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/miri"));
857        config.rustfmt_info =
858            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/rustfmt"));
859        config.enzyme_info =
860            GitInfo::new(config.omit_git_hash, &config.src.join("src/tools/enzyme"));
861        config.in_tree_llvm_info = GitInfo::new(false, &config.src.join("src/llvm-project"));
862        config.in_tree_gcc_info = GitInfo::new(false, &config.src.join("src/gcc"));
863
864        config.vendor = vendor.unwrap_or(
865            config.rust_info.is_from_tarball()
866                && config.src.join("vendor").exists()
867                && config.src.join(".cargo/config.toml").exists(),
868        );
869
870        if !is_user_configured_rust_channel && config.rust_info.is_from_tarball() {
871            config.channel = ci_channel.into();
872        }
873
874        config.rust_profile_use = flags.rust_profile_use;
875        config.rust_profile_generate = flags.rust_profile_generate;
876
877        config.apply_rust_config(toml.rust, flags.warnings, &mut description);
878
879        config.reproducible_artifacts = flags.reproducible_artifact;
880        config.description = description;
881
882        // We need to override `rust.channel` if it's manually specified when using the CI rustc.
883        // This is because if the compiler uses a different channel than the one specified in bootstrap.toml,
884        // tests may fail due to using a different channel than the one used by the compiler during tests.
885        if let Some(commit) = &config.download_rustc_commit
886            && is_user_configured_rust_channel
887        {
888            println!(
889                "WARNING: `rust.download-rustc` is enabled. The `rust.channel` option will be overridden by the CI rustc's channel."
890            );
891
892            let channel =
893                config.read_file_by_commit(Path::new("src/ci/channel"), commit).trim().to_owned();
894
895            config.channel = channel;
896        }
897
898        config.apply_llvm_config(toml.llvm, &mut ccache);
899
900        config.apply_gcc_config(toml.gcc);
901
902        config.apply_target_config(toml.target);
903
904        match ccache {
905            Some(StringOrBool::String(ref s)) => config.ccache = Some(s.to_string()),
906            Some(StringOrBool::Bool(true)) => {
907                config.ccache = Some("ccache".to_string());
908            }
909            Some(StringOrBool::Bool(false)) | None => {}
910        }
911
912        if config.llvm_from_ci {
913            let triple = &config.build.triple;
914            let ci_llvm_bin = config.ci_llvm_root().join("bin");
915            let build_target = config
916                .target_config
917                .entry(config.build)
918                .or_insert_with(|| Target::from_triple(triple));
919
920            check_ci_llvm!(build_target.llvm_config);
921            check_ci_llvm!(build_target.llvm_filecheck);
922            build_target.llvm_config = Some(ci_llvm_bin.join(exe("llvm-config", config.build)));
923            build_target.llvm_filecheck = Some(ci_llvm_bin.join(exe("FileCheck", config.build)));
924        }
925
926        config.apply_dist_config(toml.dist);
927
928        config.initial_rustfmt =
929            if let Some(r) = rustfmt { Some(r) } else { config.maybe_download_rustfmt() };
930
931        if matches!(config.lld_mode, LldMode::SelfContained)
932            && !config.lld_enabled
933            && flags.stage.unwrap_or(0) > 0
934        {
935            panic!(
936                "Trying to use self-contained lld as a linker, but LLD is not being added to the sysroot. Enable it with rust.lld = true."
937            );
938        }
939
940        if config.lld_enabled && config.is_system_llvm(config.build) {
941            eprintln!(
942                "Warning: LLD is enabled when using external llvm-config. LLD will not be built and copied to the sysroot."
943            );
944        }
945
946        config.optimized_compiler_builtins =
947            optimized_compiler_builtins.unwrap_or(config.channel != "dev");
948        config.compiletest_diff_tool = compiletest_diff_tool;
949        config.compiletest_use_stage0_libtest = compiletest_use_stage0_libtest.unwrap_or(true);
950
951        let download_rustc = config.download_rustc_commit.is_some();
952        config.explicit_stage_from_cli = flags.stage.is_some();
953        config.explicit_stage_from_config = test_stage.is_some()
954            || build_stage.is_some()
955            || doc_stage.is_some()
956            || dist_stage.is_some()
957            || install_stage.is_some()
958            || check_stage.is_some()
959            || bench_stage.is_some();
960        // See https://github.com/rust-lang/compiler-team/issues/326
961        config.stage = match config.cmd {
962            Subcommand::Check { .. } => flags.stage.or(check_stage).unwrap_or(0),
963            Subcommand::Clippy { .. } | Subcommand::Fix => flags.stage.or(check_stage).unwrap_or(1),
964            // `download-rustc` only has a speed-up for stage2 builds. Default to stage2 unless explicitly overridden.
965            Subcommand::Doc { .. } => {
966                flags.stage.or(doc_stage).unwrap_or(if download_rustc { 2 } else { 1 })
967            }
968            Subcommand::Build => {
969                flags.stage.or(build_stage).unwrap_or(if download_rustc { 2 } else { 1 })
970            }
971            Subcommand::Test { .. } | Subcommand::Miri { .. } => {
972                flags.stage.or(test_stage).unwrap_or(if download_rustc { 2 } else { 1 })
973            }
974            Subcommand::Bench { .. } => flags.stage.or(bench_stage).unwrap_or(2),
975            Subcommand::Dist => flags.stage.or(dist_stage).unwrap_or(2),
976            Subcommand::Install => flags.stage.or(install_stage).unwrap_or(2),
977            Subcommand::Perf { .. } => flags.stage.unwrap_or(1),
978            // These are all bootstrap tools, which don't depend on the compiler.
979            // The stage we pass shouldn't matter, but use 0 just in case.
980            Subcommand::Clean { .. }
981            | Subcommand::Run { .. }
982            | Subcommand::Setup { .. }
983            | Subcommand::Format { .. }
984            | Subcommand::Suggest { .. }
985            | Subcommand::Vendor { .. } => flags.stage.unwrap_or(0),
986        };
987
988        // CI should always run stage 2 builds, unless it specifically states otherwise
989        #[cfg(not(test))]
990        if flags.stage.is_none() && config.is_running_on_ci {
991            match config.cmd {
992                Subcommand::Test { .. }
993                | Subcommand::Miri { .. }
994                | Subcommand::Doc { .. }
995                | Subcommand::Build
996                | Subcommand::Bench { .. }
997                | Subcommand::Dist
998                | Subcommand::Install => {
999                    assert_eq!(
1000                        config.stage, 2,
1001                        "x.py should be run with `--stage 2` on CI, but was run with `--stage {}`",
1002                        config.stage,
1003                    );
1004                }
1005                Subcommand::Clean { .. }
1006                | Subcommand::Check { .. }
1007                | Subcommand::Clippy { .. }
1008                | Subcommand::Fix
1009                | Subcommand::Run { .. }
1010                | Subcommand::Setup { .. }
1011                | Subcommand::Format { .. }
1012                | Subcommand::Suggest { .. }
1013                | Subcommand::Vendor { .. }
1014                | Subcommand::Perf { .. } => {}
1015            }
1016        }
1017
1018        config
1019    }
1020
1021    pub fn dry_run(&self) -> bool {
1022        match self.dry_run {
1023            DryRun::Disabled => false,
1024            DryRun::SelfCheck | DryRun::UserSelected => true,
1025        }
1026    }
1027
1028    pub fn is_explicit_stage(&self) -> bool {
1029        self.explicit_stage_from_cli || self.explicit_stage_from_config
1030    }
1031
1032    /// Runs a command, printing out nice contextual information if it fails.
1033    /// Exits if the command failed to execute at all, otherwise returns its
1034    /// `status.success()`.
1035    #[deprecated = "use `Builder::try_run` instead where possible"]
1036    pub(crate) fn try_run(&self, cmd: &mut Command) -> Result<(), ()> {
1037        if self.dry_run() {
1038            return Ok(());
1039        }
1040        self.verbose(|| println!("running: {cmd:?}"));
1041        build_helper::util::try_run(cmd, self.is_verbose())
1042    }
1043
1044    pub(crate) fn test_args(&self) -> Vec<&str> {
1045        let mut test_args = match self.cmd {
1046            Subcommand::Test { ref test_args, .. }
1047            | Subcommand::Bench { ref test_args, .. }
1048            | Subcommand::Miri { ref test_args, .. } => {
1049                test_args.iter().flat_map(|s| s.split_whitespace()).collect()
1050            }
1051            _ => vec![],
1052        };
1053        test_args.extend(self.free_args.iter().map(|s| s.as_str()));
1054        test_args
1055    }
1056
1057    pub(crate) fn args(&self) -> Vec<&str> {
1058        let mut args = match self.cmd {
1059            Subcommand::Run { ref args, .. } => {
1060                args.iter().flat_map(|s| s.split_whitespace()).collect()
1061            }
1062            _ => vec![],
1063        };
1064        args.extend(self.free_args.iter().map(|s| s.as_str()));
1065        args
1066    }
1067
1068    /// Returns the content of the given file at a specific commit.
1069    pub(crate) fn read_file_by_commit(&self, file: &Path, commit: &str) -> String {
1070        assert!(
1071            self.rust_info.is_managed_git_subrepository(),
1072            "`Config::read_file_by_commit` is not supported in non-git sources."
1073        );
1074
1075        let mut git = helpers::git(Some(&self.src));
1076        git.arg("show").arg(format!("{commit}:{}", file.to_str().unwrap()));
1077        output(git.as_command_mut())
1078    }
1079
1080    /// Bootstrap embeds a version number into the name of shared libraries it uploads in CI.
1081    /// Return the version it would have used for the given commit.
1082    pub(crate) fn artifact_version_part(&self, commit: &str) -> String {
1083        let (channel, version) = if self.rust_info.is_managed_git_subrepository() {
1084            let channel =
1085                self.read_file_by_commit(Path::new("src/ci/channel"), commit).trim().to_owned();
1086            let version =
1087                self.read_file_by_commit(Path::new("src/version"), commit).trim().to_owned();
1088            (channel, version)
1089        } else {
1090            let channel = fs::read_to_string(self.src.join("src/ci/channel"));
1091            let version = fs::read_to_string(self.src.join("src/version"));
1092            match (channel, version) {
1093                (Ok(channel), Ok(version)) => {
1094                    (channel.trim().to_owned(), version.trim().to_owned())
1095                }
1096                (channel, version) => {
1097                    let src = self.src.display();
1098                    eprintln!("ERROR: failed to determine artifact channel and/or version");
1099                    eprintln!(
1100                        "HELP: consider using a git checkout or ensure these files are readable"
1101                    );
1102                    if let Err(channel) = channel {
1103                        eprintln!("reading {src}/src/ci/channel failed: {channel:?}");
1104                    }
1105                    if let Err(version) = version {
1106                        eprintln!("reading {src}/src/version failed: {version:?}");
1107                    }
1108                    panic!();
1109                }
1110            }
1111        };
1112
1113        match channel.as_str() {
1114            "stable" => version,
1115            "beta" => channel,
1116            "nightly" => channel,
1117            other => unreachable!("{:?} is not recognized as a valid channel", other),
1118        }
1119    }
1120
1121    /// Try to find the relative path of `bindir`, otherwise return it in full.
1122    pub fn bindir_relative(&self) -> &Path {
1123        let bindir = &self.bindir;
1124        if bindir.is_absolute() {
1125            // Try to make it relative to the prefix.
1126            if let Some(prefix) = &self.prefix
1127                && let Ok(stripped) = bindir.strip_prefix(prefix)
1128            {
1129                return stripped;
1130            }
1131        }
1132        bindir
1133    }
1134
1135    /// Try to find the relative path of `libdir`.
1136    pub fn libdir_relative(&self) -> Option<&Path> {
1137        let libdir = self.libdir.as_ref()?;
1138        if libdir.is_relative() {
1139            Some(libdir)
1140        } else {
1141            // Try to make it relative to the prefix.
1142            libdir.strip_prefix(self.prefix.as_ref()?).ok()
1143        }
1144    }
1145
1146    /// The absolute path to the downloaded LLVM artifacts.
1147    pub(crate) fn ci_llvm_root(&self) -> PathBuf {
1148        assert!(self.llvm_from_ci);
1149        self.out.join(self.build).join("ci-llvm")
1150    }
1151
1152    /// Directory where the extracted `rustc-dev` component is stored.
1153    pub(crate) fn ci_rustc_dir(&self) -> PathBuf {
1154        assert!(self.download_rustc());
1155        self.out.join(self.build).join("ci-rustc")
1156    }
1157
1158    /// Determine whether llvm should be linked dynamically.
1159    ///
1160    /// If `false`, llvm should be linked statically.
1161    /// This is computed on demand since LLVM might have to first be downloaded from CI.
1162    pub(crate) fn llvm_link_shared(&self) -> bool {
1163        let mut opt = self.llvm_link_shared.get();
1164        if opt.is_none() && self.dry_run() {
1165            // just assume static for now - dynamic linking isn't supported on all platforms
1166            return false;
1167        }
1168
1169        let llvm_link_shared = *opt.get_or_insert_with(|| {
1170            if self.llvm_from_ci {
1171                self.maybe_download_ci_llvm();
1172                let ci_llvm = self.ci_llvm_root();
1173                let link_type = t!(
1174                    std::fs::read_to_string(ci_llvm.join("link-type.txt")),
1175                    format!("CI llvm missing: {}", ci_llvm.display())
1176                );
1177                link_type == "dynamic"
1178            } else {
1179                // unclear how thought-through this default is, but it maintains compatibility with
1180                // previous behavior
1181                false
1182            }
1183        });
1184        self.llvm_link_shared.set(opt);
1185        llvm_link_shared
1186    }
1187
1188    /// Return whether we will use a downloaded, pre-compiled version of rustc, or just build from source.
1189    pub(crate) fn download_rustc(&self) -> bool {
1190        self.download_rustc_commit().is_some()
1191    }
1192
1193    pub(crate) fn download_rustc_commit(&self) -> Option<&str> {
1194        static DOWNLOAD_RUSTC: OnceLock<Option<String>> = OnceLock::new();
1195        if self.dry_run() && DOWNLOAD_RUSTC.get().is_none() {
1196            // avoid trying to actually download the commit
1197            return self.download_rustc_commit.as_deref();
1198        }
1199
1200        DOWNLOAD_RUSTC
1201            .get_or_init(|| match &self.download_rustc_commit {
1202                None => None,
1203                Some(commit) => {
1204                    self.download_ci_rustc(commit);
1205
1206                    // CI-rustc can't be used without CI-LLVM. If `self.llvm_from_ci` is false, it means the "if-unchanged"
1207                    // logic has detected some changes in the LLVM submodule (download-ci-llvm=false can't happen here as
1208                    // we don't allow it while parsing the configuration).
1209                    if !self.llvm_from_ci {
1210                        // This happens when LLVM submodule is updated in CI, we should disable ci-rustc without an error
1211                        // to not break CI. For non-CI environments, we should return an error.
1212                        if self.is_running_on_ci {
1213                            println!("WARNING: LLVM submodule has changes, `download-rustc` will be disabled.");
1214                            return None;
1215                        } else {
1216                            panic!("ERROR: LLVM submodule has changes, `download-rustc` can't be used.");
1217                        }
1218                    }
1219
1220                    if let Some(config_path) = &self.config {
1221                        let ci_config_toml = match self.get_builder_toml("ci-rustc") {
1222                            Ok(ci_config_toml) => ci_config_toml,
1223                            Err(e) if e.to_string().contains("unknown field") => {
1224                                println!("WARNING: CI rustc has some fields that are no longer supported in bootstrap; download-rustc will be disabled.");
1225                                println!("HELP: Consider rebasing to a newer commit if available.");
1226                                return None;
1227                            },
1228                            Err(e) => {
1229                                eprintln!("ERROR: Failed to parse CI rustc bootstrap.toml: {e}");
1230                                exit!(2);
1231                            },
1232                        };
1233
1234                        let current_config_toml = Self::get_toml(config_path).unwrap();
1235
1236                        // Check the config compatibility
1237                        // FIXME: this doesn't cover `--set` flags yet.
1238                        let res = check_incompatible_options_for_ci_rustc(
1239                            self.build,
1240                            current_config_toml,
1241                            ci_config_toml,
1242                        );
1243
1244                        // Primarily used by CI runners to avoid handling download-rustc incompatible
1245                        // options one by one on shell scripts.
1246                        let disable_ci_rustc_if_incompatible = env::var_os("DISABLE_CI_RUSTC_IF_INCOMPATIBLE")
1247                            .is_some_and(|s| s == "1" || s == "true");
1248
1249                        if disable_ci_rustc_if_incompatible && res.is_err() {
1250                            println!("WARNING: download-rustc is disabled with `DISABLE_CI_RUSTC_IF_INCOMPATIBLE` env.");
1251                            return None;
1252                        }
1253
1254                        res.unwrap();
1255                    }
1256
1257                    Some(commit.clone())
1258                }
1259            })
1260            .as_deref()
1261    }
1262
1263    /// Runs a function if verbosity is greater than 0
1264    pub fn verbose(&self, f: impl Fn()) {
1265        if self.is_verbose() {
1266            f()
1267        }
1268    }
1269
1270    pub fn any_sanitizers_to_build(&self) -> bool {
1271        self.target_config
1272            .iter()
1273            .any(|(ts, t)| !ts.is_msvc() && t.sanitizers.unwrap_or(self.sanitizers))
1274    }
1275
1276    pub fn any_profiler_enabled(&self) -> bool {
1277        self.target_config.values().any(|t| matches!(&t.profiler, Some(p) if p.is_string_or_true()))
1278            || self.profiler
1279    }
1280
1281    /// Returns whether or not submodules should be managed by bootstrap.
1282    pub fn submodules(&self) -> bool {
1283        // If not specified in config, the default is to only manage
1284        // submodules if we're currently inside a git repository.
1285        self.submodules.unwrap_or(self.rust_info.is_managed_git_subrepository())
1286    }
1287
1288    pub fn git_config(&self) -> GitConfig<'_> {
1289        GitConfig {
1290            nightly_branch: &self.stage0_metadata.config.nightly_branch,
1291            git_merge_commit_email: &self.stage0_metadata.config.git_merge_commit_email,
1292        }
1293    }
1294
1295    /// Given a path to the directory of a submodule, update it.
1296    ///
1297    /// `relative_path` should be relative to the root of the git repository, not an absolute path.
1298    ///
1299    /// This *does not* update the submodule if `bootstrap.toml` explicitly says
1300    /// not to, or if we're not in a git repository (like a plain source
1301    /// tarball). Typically [`crate::Build::require_submodule`] should be
1302    /// used instead to provide a nice error to the user if the submodule is
1303    /// missing.
1304    #[cfg_attr(
1305        feature = "tracing",
1306        instrument(
1307            level = "trace",
1308            name = "Config::update_submodule",
1309            skip_all,
1310            fields(relative_path = ?relative_path),
1311        ),
1312    )]
1313    pub(crate) fn update_submodule(&self, relative_path: &str) {
1314        if self.rust_info.is_from_tarball() || !self.submodules() {
1315            return;
1316        }
1317
1318        let absolute_path = self.src.join(relative_path);
1319
1320        // NOTE: This check is required because `jj git clone` doesn't create directories for
1321        // submodules, they are completely ignored. The code below assumes this directory exists,
1322        // so create it here.
1323        if !absolute_path.exists() {
1324            t!(fs::create_dir_all(&absolute_path));
1325        }
1326
1327        // NOTE: The check for the empty directory is here because when running x.py the first time,
1328        // the submodule won't be checked out. Check it out now so we can build it.
1329        if !GitInfo::new(false, &absolute_path).is_managed_git_subrepository()
1330            && !helpers::dir_is_empty(&absolute_path)
1331        {
1332            return;
1333        }
1334
1335        // Submodule updating actually happens during in the dry run mode. We need to make sure that
1336        // all the git commands below are actually executed, because some follow-up code
1337        // in bootstrap might depend on the submodules being checked out. Furthermore, not all
1338        // the command executions below work with an empty output (produced during dry run).
1339        // Therefore, all commands below are marked with `run_always()`, so that they also run in
1340        // dry run mode.
1341        let submodule_git = || {
1342            let mut cmd = helpers::git(Some(&absolute_path));
1343            cmd.run_always();
1344            cmd
1345        };
1346
1347        // Determine commit checked out in submodule.
1348        let checked_out_hash = output(submodule_git().args(["rev-parse", "HEAD"]).as_command_mut());
1349        let checked_out_hash = checked_out_hash.trim_end();
1350        // Determine commit that the submodule *should* have.
1351        let recorded = output(
1352            helpers::git(Some(&self.src))
1353                .run_always()
1354                .args(["ls-tree", "HEAD"])
1355                .arg(relative_path)
1356                .as_command_mut(),
1357        );
1358
1359        let actual_hash = recorded
1360            .split_whitespace()
1361            .nth(2)
1362            .unwrap_or_else(|| panic!("unexpected output `{recorded}`"));
1363
1364        if actual_hash == checked_out_hash {
1365            // already checked out
1366            return;
1367        }
1368
1369        println!("Updating submodule {relative_path}");
1370        self.check_run(
1371            helpers::git(Some(&self.src))
1372                .run_always()
1373                .args(["submodule", "-q", "sync"])
1374                .arg(relative_path),
1375        );
1376
1377        // Try passing `--progress` to start, then run git again without if that fails.
1378        let update = |progress: bool| {
1379            // Git is buggy and will try to fetch submodules from the tracking branch for *this* repository,
1380            // even though that has no relation to the upstream for the submodule.
1381            let current_branch = output_result(
1382                helpers::git(Some(&self.src))
1383                    .allow_failure()
1384                    .run_always()
1385                    .args(["symbolic-ref", "--short", "HEAD"])
1386                    .as_command_mut(),
1387            )
1388            .map(|b| b.trim().to_owned());
1389
1390            let mut git = helpers::git(Some(&self.src)).allow_failure();
1391            git.run_always();
1392            if let Ok(branch) = current_branch {
1393                // If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name.
1394                // This syntax isn't accepted by `branch.{branch}`. Strip it.
1395                let branch = branch.strip_prefix("heads/").unwrap_or(&branch);
1396                git.arg("-c").arg(format!("branch.{branch}.remote=origin"));
1397            }
1398            git.args(["submodule", "update", "--init", "--recursive", "--depth=1"]);
1399            if progress {
1400                git.arg("--progress");
1401            }
1402            git.arg(relative_path);
1403            git
1404        };
1405        if !self.check_run(&mut update(true)) {
1406            self.check_run(&mut update(false));
1407        }
1408
1409        // Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error).
1410        // diff-index reports the modifications through the exit status
1411        let has_local_modifications = !self.check_run(submodule_git().allow_failure().args([
1412            "diff-index",
1413            "--quiet",
1414            "HEAD",
1415        ]));
1416        if has_local_modifications {
1417            self.check_run(submodule_git().args(["stash", "push"]));
1418        }
1419
1420        self.check_run(submodule_git().args(["reset", "-q", "--hard"]));
1421        self.check_run(submodule_git().args(["clean", "-qdfx"]));
1422
1423        if has_local_modifications {
1424            self.check_run(submodule_git().args(["stash", "pop"]));
1425        }
1426    }
1427
1428    #[cfg(test)]
1429    pub fn check_stage0_version(&self, _program_path: &Path, _component_name: &'static str) {}
1430
1431    /// check rustc/cargo version is same or lower with 1 apart from the building one
1432    #[cfg(not(test))]
1433    pub fn check_stage0_version(&self, program_path: &Path, component_name: &'static str) {
1434        use build_helper::util::fail;
1435
1436        if self.dry_run() {
1437            return;
1438        }
1439
1440        let stage0_output = output(Command::new(program_path).arg("--version"));
1441        let mut stage0_output = stage0_output.lines().next().unwrap().split(' ');
1442
1443        let stage0_name = stage0_output.next().unwrap();
1444        if stage0_name != component_name {
1445            fail(&format!(
1446                "Expected to find {component_name} at {} but it claims to be {stage0_name}",
1447                program_path.display()
1448            ));
1449        }
1450
1451        let stage0_version =
1452            semver::Version::parse(stage0_output.next().unwrap().split('-').next().unwrap().trim())
1453                .unwrap();
1454        let source_version = semver::Version::parse(
1455            fs::read_to_string(self.src.join("src/version")).unwrap().trim(),
1456        )
1457        .unwrap();
1458        if !(source_version == stage0_version
1459            || (source_version.major == stage0_version.major
1460                && (source_version.minor == stage0_version.minor
1461                    || source_version.minor == stage0_version.minor + 1)))
1462        {
1463            let prev_version = format!("{}.{}.x", source_version.major, source_version.minor - 1);
1464            fail(&format!(
1465                "Unexpected {component_name} version: {stage0_version}, we should use {prev_version}/{source_version} to build source with {source_version}"
1466            ));
1467        }
1468    }
1469
1470    /// Returns the commit to download, or `None` if we shouldn't download CI artifacts.
1471    pub fn download_ci_rustc_commit(
1472        &self,
1473        download_rustc: Option<StringOrBool>,
1474        debug_assertions_requested: bool,
1475        llvm_assertions: bool,
1476    ) -> Option<String> {
1477        if !is_download_ci_available(&self.build.triple, llvm_assertions) {
1478            return None;
1479        }
1480
1481        // If `download-rustc` is not set, default to rebuilding.
1482        let if_unchanged = match download_rustc {
1483            // Globally default `download-rustc` to `false`, because some contributors don't use
1484            // profiles for reasons such as:
1485            // - They need to seamlessly switch between compiler/library work.
1486            // - They don't want to use compiler profile because they need to override too many
1487            //   things and it's easier to not use a profile.
1488            None | Some(StringOrBool::Bool(false)) => return None,
1489            Some(StringOrBool::Bool(true)) => false,
1490            Some(StringOrBool::String(s)) if s == "if-unchanged" => {
1491                if !self.rust_info.is_managed_git_subrepository() {
1492                    println!(
1493                        "ERROR: `download-rustc=if-unchanged` is only compatible with Git managed sources."
1494                    );
1495                    crate::exit!(1);
1496                }
1497
1498                true
1499            }
1500            Some(StringOrBool::String(other)) => {
1501                panic!("unrecognized option for download-rustc: {other}")
1502            }
1503        };
1504
1505        let commit = if self.rust_info.is_managed_git_subrepository() {
1506            // Look for a version to compare to based on the current commit.
1507            // Only commits merged by bors will have CI artifacts.
1508            let freshness = self.check_path_modifications(RUSTC_IF_UNCHANGED_ALLOWED_PATHS);
1509            self.verbose(|| {
1510                eprintln!("rustc freshness: {freshness:?}");
1511            });
1512            match freshness {
1513                PathFreshness::LastModifiedUpstream { upstream } => upstream,
1514                PathFreshness::HasLocalModifications { upstream } => {
1515                    if if_unchanged {
1516                        return None;
1517                    }
1518
1519                    if self.is_running_on_ci {
1520                        eprintln!("CI rustc commit matches with HEAD and we are in CI.");
1521                        eprintln!(
1522                            "`rustc.download-ci` functionality will be skipped as artifacts are not available."
1523                        );
1524                        return None;
1525                    }
1526
1527                    upstream
1528                }
1529                PathFreshness::MissingUpstream => {
1530                    eprintln!("No upstream commit found");
1531                    return None;
1532                }
1533            }
1534        } else {
1535            channel::read_commit_info_file(&self.src)
1536                .map(|info| info.sha.trim().to_owned())
1537                .expect("git-commit-info is missing in the project root")
1538        };
1539
1540        if debug_assertions_requested {
1541            eprintln!(
1542                "WARN: `rust.debug-assertions = true` will prevent downloading CI rustc as alt CI \
1543                rustc is not currently built with debug assertions."
1544            );
1545            return None;
1546        }
1547
1548        Some(commit)
1549    }
1550
1551    pub fn parse_download_ci_llvm(
1552        &self,
1553        download_ci_llvm: Option<StringOrBool>,
1554        asserts: bool,
1555    ) -> bool {
1556        // We don't ever want to use `true` on CI, as we should not
1557        // download upstream artifacts if there are any local modifications.
1558        let default = if self.is_running_on_ci {
1559            StringOrBool::String("if-unchanged".to_string())
1560        } else {
1561            StringOrBool::Bool(true)
1562        };
1563        let download_ci_llvm = download_ci_llvm.unwrap_or(default);
1564
1565        let if_unchanged = || {
1566            if self.rust_info.is_from_tarball() {
1567                // Git is needed for running "if-unchanged" logic.
1568                println!("ERROR: 'if-unchanged' is only compatible with Git managed sources.");
1569                crate::exit!(1);
1570            }
1571
1572            // Fetching the LLVM submodule is unnecessary for self-tests.
1573            #[cfg(not(test))]
1574            self.update_submodule("src/llvm-project");
1575
1576            // Check for untracked changes in `src/llvm-project` and other important places.
1577            let has_changes = self.has_changes_from_upstream(LLVM_INVALIDATION_PATHS);
1578
1579            // Return false if there are untracked changes, otherwise check if CI LLVM is available.
1580            if has_changes { false } else { llvm::is_ci_llvm_available_for_target(self, asserts) }
1581        };
1582
1583        match download_ci_llvm {
1584            StringOrBool::Bool(b) => {
1585                if !b && self.download_rustc_commit.is_some() {
1586                    panic!(
1587                        "`llvm.download-ci-llvm` cannot be set to `false` if `rust.download-rustc` is set to `true` or `if-unchanged`."
1588                    );
1589                }
1590
1591                if b && self.is_running_on_ci {
1592                    // On CI, we must always rebuild LLVM if there were any modifications to it
1593                    panic!(
1594                        "`llvm.download-ci-llvm` cannot be set to `true` on CI. Use `if-unchanged` instead."
1595                    );
1596                }
1597
1598                // If download-ci-llvm=true we also want to check that CI llvm is available
1599                b && llvm::is_ci_llvm_available_for_target(self, asserts)
1600            }
1601            StringOrBool::String(s) if s == "if-unchanged" => if_unchanged(),
1602            StringOrBool::String(other) => {
1603                panic!("unrecognized option for download-ci-llvm: {other:?}")
1604            }
1605        }
1606    }
1607
1608    /// Returns true if any of the `paths` have been modified locally.
1609    pub fn has_changes_from_upstream(&self, paths: &[&'static str]) -> bool {
1610        match self.check_path_modifications(paths) {
1611            PathFreshness::LastModifiedUpstream { .. } => false,
1612            PathFreshness::HasLocalModifications { .. } | PathFreshness::MissingUpstream => true,
1613        }
1614    }
1615
1616    /// Checks whether any of the given paths have been modified w.r.t. upstream.
1617    pub fn check_path_modifications(&self, paths: &[&'static str]) -> PathFreshness {
1618        // Checking path modifications through git can be relatively expensive (>100ms).
1619        // We do not assume that the sources would change during bootstrap's execution,
1620        // so we can cache the results here.
1621        // Note that we do not use a static variable for the cache, because it would cause problems
1622        // in tests that create separate `Config` instsances.
1623        self.path_modification_cache
1624            .lock()
1625            .unwrap()
1626            .entry(paths.to_vec())
1627            .or_insert_with(|| {
1628                check_path_modifications(&self.src, &self.git_config(), paths, CiEnv::current())
1629                    .unwrap()
1630            })
1631            .clone()
1632    }
1633
1634    pub fn ci_env(&self) -> CiEnv {
1635        if self.is_running_on_ci { CiEnv::GitHubActions } else { CiEnv::None }
1636    }
1637
1638    pub fn sanitizers_enabled(&self, target: TargetSelection) -> bool {
1639        self.target_config.get(&target).and_then(|t| t.sanitizers).unwrap_or(self.sanitizers)
1640    }
1641
1642    pub fn needs_sanitizer_runtime_built(&self, target: TargetSelection) -> bool {
1643        // MSVC uses the Microsoft-provided sanitizer runtime, but all other runtimes we build.
1644        !target.is_msvc() && self.sanitizers_enabled(target)
1645    }
1646
1647    pub fn profiler_path(&self, target: TargetSelection) -> Option<&str> {
1648        match self.target_config.get(&target)?.profiler.as_ref()? {
1649            StringOrBool::String(s) => Some(s),
1650            StringOrBool::Bool(_) => None,
1651        }
1652    }
1653
1654    pub fn profiler_enabled(&self, target: TargetSelection) -> bool {
1655        self.target_config
1656            .get(&target)
1657            .and_then(|t| t.profiler.as_ref())
1658            .map(StringOrBool::is_string_or_true)
1659            .unwrap_or(self.profiler)
1660    }
1661
1662    pub fn codegen_backends(&self, target: TargetSelection) -> &[String] {
1663        self.target_config
1664            .get(&target)
1665            .and_then(|cfg| cfg.codegen_backends.as_deref())
1666            .unwrap_or(&self.rust_codegen_backends)
1667    }
1668
1669    pub fn jemalloc(&self, target: TargetSelection) -> bool {
1670        self.target_config.get(&target).and_then(|cfg| cfg.jemalloc).unwrap_or(self.jemalloc)
1671    }
1672
1673    pub fn default_codegen_backend(&self, target: TargetSelection) -> Option<String> {
1674        self.codegen_backends(target).first().cloned()
1675    }
1676
1677    pub fn rpath_enabled(&self, target: TargetSelection) -> bool {
1678        self.target_config.get(&target).and_then(|t| t.rpath).unwrap_or(self.rust_rpath)
1679    }
1680
1681    pub fn optimized_compiler_builtins(&self, target: TargetSelection) -> bool {
1682        self.target_config
1683            .get(&target)
1684            .and_then(|t| t.optimized_compiler_builtins)
1685            .unwrap_or(self.optimized_compiler_builtins)
1686    }
1687
1688    pub fn llvm_enabled(&self, target: TargetSelection) -> bool {
1689        self.codegen_backends(target).contains(&"llvm".to_owned())
1690    }
1691
1692    pub fn llvm_libunwind(&self, target: TargetSelection) -> LlvmLibunwind {
1693        self.target_config
1694            .get(&target)
1695            .and_then(|t| t.llvm_libunwind)
1696            .or(self.llvm_libunwind_default)
1697            .unwrap_or(if target.contains("fuchsia") {
1698                LlvmLibunwind::InTree
1699            } else {
1700                LlvmLibunwind::No
1701            })
1702    }
1703
1704    pub fn split_debuginfo(&self, target: TargetSelection) -> SplitDebuginfo {
1705        self.target_config
1706            .get(&target)
1707            .and_then(|t| t.split_debuginfo)
1708            .unwrap_or_else(|| SplitDebuginfo::default_for_platform(target))
1709    }
1710
1711    /// Checks if the given target is the same as the host target.
1712    pub fn is_host_target(&self, target: TargetSelection) -> bool {
1713        self.build == target
1714    }
1715
1716    /// Returns `true` if this is an external version of LLVM not managed by bootstrap.
1717    /// In particular, we expect llvm sources to be available when this is false.
1718    ///
1719    /// NOTE: this is not the same as `!is_rust_llvm` when `llvm_has_patches` is set.
1720    pub fn is_system_llvm(&self, target: TargetSelection) -> bool {
1721        match self.target_config.get(&target) {
1722            Some(Target { llvm_config: Some(_), .. }) => {
1723                let ci_llvm = self.llvm_from_ci && self.is_host_target(target);
1724                !ci_llvm
1725            }
1726            // We're building from the in-tree src/llvm-project sources.
1727            Some(Target { llvm_config: None, .. }) => false,
1728            None => false,
1729        }
1730    }
1731
1732    /// Returns `true` if this is our custom, patched, version of LLVM.
1733    ///
1734    /// This does not necessarily imply that we're managing the `llvm-project` submodule.
1735    pub fn is_rust_llvm(&self, target: TargetSelection) -> bool {
1736        match self.target_config.get(&target) {
1737            // We're using a user-controlled version of LLVM. The user has explicitly told us whether the version has our patches.
1738            // (They might be wrong, but that's not a supported use-case.)
1739            // In particular, this tries to support `submodules = false` and `patches = false`, for using a newer version of LLVM that's not through `rust-lang/llvm-project`.
1740            Some(Target { llvm_has_rust_patches: Some(patched), .. }) => *patched,
1741            // The user hasn't promised the patches match.
1742            // This only has our patches if it's downloaded from CI or built from source.
1743            _ => !self.is_system_llvm(target),
1744        }
1745    }
1746}