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