1use 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#[rustfmt::skip] pub 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#[derive(Default, Clone)]
84pub struct Config {
85 pub change_id: Option<ChangeId>,
86 pub bypass_bootstrap_lock: bool,
87 pub ccache: Option<String>,
88 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 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 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 pub config: Option<PathBuf>,
133 pub jobs: Option<u32>,
134 pub cmd: Subcommand,
135 pub incremental: bool,
136 pub dump_bootstrap_shims: bool,
137 pub free_args: Vec<String>,
140
141 pub download_rustc_commit: Option<String>,
143
144 pub deny_warnings: bool,
145 pub backtrace_on_ice: bool,
146
147 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 pub gcc_ci_mode: GccCiMode,
184
185 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 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 pub backtrace: bool, 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 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 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 pub paths: Vec<PathBuf>,
294
295 pub compiletest_diff_tool: Option<String>,
297
298 pub compiletest_use_stage0_libtest: bool,
300
301 pub is_running_on_ci: bool,
302
303 pub path_modification_cache: Arc<Mutex<HashMap<Vec<&'static str>, PathFreshness>>>,
305
306 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 host_target: get_host_target(),
352
353 src: {
354 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
355 manifest_dir.parent().unwrap().parent().unwrap().to_owned()
357 },
358 out: PathBuf::from("build"),
359
360 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 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 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 if let Some(src) = flags_src {
489 config.src = src
490 } else {
491 let mut cmd = helpers::git(None);
494 cmd.arg("rev-parse").arg("--show-cdup");
502 let output = cmd.allow_failure().run_capture_stdout(&config);
504 if output.is_success() {
505 let git_root_relative = output.stdout();
506 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 let git_root = match s.strip_prefix("\\\\?\\") {
517 Some(p) => PathBuf::from(p),
518 None => git_root,
519 };
520 if git_root.join("src").join("stage0").exists() {
527 config.src = git_root;
528 }
529 } else {
530 }
533 }
534
535 if cfg!(test) {
536 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 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 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 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 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 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 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 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 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 if !config.out.is_absolute() {
783 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 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 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 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 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 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 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 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 #[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 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 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 pub fn bindir_relative(&self) -> &Path {
1179 let bindir = &self.bindir;
1180 if bindir.is_absolute() {
1181 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 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 libdir.strip_prefix(self.prefix.as_ref()?).ok()
1199 }
1200 }
1201
1202 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 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 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 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 false
1238 }
1239 });
1240 self.llvm_link_shared.set(opt);
1241 llvm_link_shared
1242 }
1243
1244 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 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 if !self.llvm_from_ci {
1266 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 let res = check_incompatible_options_for_ci_rustc(
1295 self.host_target,
1296 current_config_toml,
1297 ci_config_toml,
1298 );
1299
1300 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 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 pub fn submodules(&self) -> bool {
1337 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 #[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 if !absolute_path.exists() {
1378 t!(fs::create_dir_all(&absolute_path));
1379 }
1380
1381 if !self.git_info(false, &absolute_path).is_managed_git_subrepository()
1384 && !helpers::dir_is_empty(&absolute_path)
1385 {
1386 return;
1387 }
1388
1389 let submodule_git = || {
1396 let mut cmd = helpers::git(Some(&absolute_path));
1397 cmd.run_in_dry_run();
1398 cmd
1399 };
1400
1401 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 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 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 let update = |progress: bool| {
1434 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 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 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 #[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 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 let if_unchanged = match download_rustc {
1535 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 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 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 println!("ERROR: 'if-unchanged' is only compatible with Git managed sources.");
1621 crate::exit!(1);
1622 }
1623
1624 #[cfg(not(test))]
1626 self.update_submodule("src/llvm-project");
1627
1628 let has_changes = self.has_changes_from_upstream(LLVM_INVALIDATION_PATHS);
1630
1631 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 panic!(
1646 "`llvm.download-ci-llvm` cannot be set to `true` on CI. Use `if-unchanged` instead."
1647 );
1648 }
1649
1650 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 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 pub fn check_path_modifications(&self, paths: &[&'static str]) -> PathFreshness {
1670 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 !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 pub fn is_host_target(&self, target: TargetSelection) -> bool {
1765 self.host_target == target
1766 }
1767
1768 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 Some(Target { llvm_config: None, .. }) => false,
1780 None => false,
1781 }
1782 }
1783
1784 pub fn is_rust_llvm(&self, target: TargetSelection) -> bool {
1788 match self.target_config.get(&target) {
1789 Some(Target { llvm_has_rust_patches: Some(patched), .. }) => *patched,
1793 _ => !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}