1use std::cell::Cell;
2use std::collections::{BTreeSet, HashMap, HashSet};
3use std::fmt::Display;
4use std::path::{Path, PathBuf};
5use std::sync::OnceLock;
6use std::time::{Instant, SystemTime};
7use std::{env, fs, io, str};
8
9use build_helper::ci::gha;
10use termcolor::{ColorChoice, StandardStream, WriteColor};
11#[cfg(feature = "tracing")]
12use tracing::{instrument, span};
13
14use crate::core::build_steps::format::InternalRustfmt;
15use crate::core::build_steps::test::TestTarget;
16use crate::core::build_steps::vendor::VENDOR_DIR;
17use crate::core::builder::{Builder, Kind};
18use crate::core::compiler::Compiler;
19use crate::core::config::flags::{self, Subcommand};
20use crate::core::config::{BootstrapOverrideLld, Config, DryRun, LlvmLibunwind, TargetSelection};
21use crate::core::metadata::Crate;
22#[cfg(feature = "tracing")]
23use crate::trace_io;
24use crate::utils::build_stamp::BuildStamp;
25use crate::utils::channel::GitInfo;
26use crate::utils::exec::{BootstrapCommand, ExecutionContext, command};
27use crate::utils::helpers::{
28 self, dir_is_empty, exe, is_symlink_dir, libdir, set_file_times, split_debuginfo, symlink_dir,
29 t,
30};
31use crate::{debug, trace};
32
33pub(crate) enum GitRepo {
34 Rustc,
35 Llvm,
36}
37
38pub(crate) struct Session {
44 pub(crate) config: Config,
46
47 pub(crate) version: String,
49
50 pub(crate) src: PathBuf,
52 pub(crate) out: PathBuf,
53 pub(crate) bootstrap_out: PathBuf,
54 pub(crate) cargo_info: GitInfo,
55 pub(crate) rust_analyzer_info: GitInfo,
56 pub(crate) clippy_info: GitInfo,
57 pub(crate) miri_info: GitInfo,
58 pub(crate) rustfmt_info: GitInfo,
59 pub(crate) enzyme_info: GitInfo,
60 pub(crate) in_tree_llvm_info: GitInfo,
61 pub(crate) in_tree_gcc_info: GitInfo,
62 pub(crate) local_rebuild: bool,
63 pub(crate) fail_fast: bool,
64 pub(crate) test_target: TestTarget,
65 pub(crate) verbosity: usize,
66
67 pub(crate) host_target: TargetSelection,
69 pub(crate) hosts: Vec<TargetSelection>,
71 pub(crate) targets: Vec<TargetSelection>,
73
74 pub(crate) initial_rustc: PathBuf,
75 pub(crate) initial_rustdoc: PathBuf,
76 pub(crate) initial_cargo: PathBuf,
77 pub(crate) initial_lld: PathBuf,
78 pub(crate) initial_relative_libdir: PathBuf,
79 pub(crate) initial_sysroot: PathBuf,
80
81 pub(crate) cc: HashMap<TargetSelection, cc::Tool>,
84 pub(crate) cxx: HashMap<TargetSelection, cc::Tool>,
85 pub(crate) ar: HashMap<TargetSelection, PathBuf>,
86 pub(crate) ranlib: HashMap<TargetSelection, PathBuf>,
87 pub(crate) wasi_sdk_path: Option<PathBuf>,
88
89 pub(crate) crates: HashMap<String, Crate>,
92 pub(crate) crate_paths: HashMap<PathBuf, String>,
93 pub(crate) is_sudo: bool,
94 pub(crate) prerelease_version: Cell<Option<u32>>,
95
96 #[cfg(feature = "build-metrics")]
97 pub(crate) metrics: crate::utils::metrics::BuildMetrics,
98
99 #[cfg(feature = "tracing")]
100 pub(crate) step_graph: std::cell::RefCell<crate::utils::step_graph::StepGraph>,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
105pub(crate) enum DependencyType {
106 Host,
108 Target,
110 TargetSelfContained,
112}
113
114#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
119pub(crate) enum Mode {
120 Std,
122
123 Rustc,
125
126 Codegen,
128
129 ToolBootstrap,
141
142 ToolTarget,
153
154 ToolStd,
158
159 ToolRustcPrivate,
165}
166
167impl Mode {
168 pub(crate) fn must_support_dlopen(&self) -> bool {
169 match self {
170 Mode::Std | Mode::Codegen => true,
171 Mode::ToolBootstrap
172 | Mode::ToolRustcPrivate
173 | Mode::ToolStd
174 | Mode::ToolTarget
175 | Mode::Rustc => false,
176 }
177 }
178}
179
180pub(crate) enum RemapScheme {
184 Compiler,
186 NonCompiler,
188}
189
190#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
191pub(crate) enum CLang {
192 C,
193 Cxx,
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub(crate) enum FileType {
198 Executable,
200 NativeLibrary,
202 Script,
204 Regular,
206}
207
208impl FileType {
209 pub(crate) fn perms(self) -> u32 {
211 match self {
212 FileType::Executable | FileType::Script => 0o755,
213 FileType::Regular | FileType::NativeLibrary => 0o644,
214 }
215 }
216
217 pub(crate) fn could_have_split_debuginfo(self) -> bool {
218 match self {
219 FileType::Executable | FileType::NativeLibrary => true,
220 FileType::Script | FileType::Regular => false,
221 }
222 }
223}
224
225macro_rules! forward {
226 ($( $fn:ident( $($param:ident: $ty:ty),* ) $( -> $ret:ty)? ),+ $(,)? ) => {
227 impl Session {
228 $(
229 pub(crate) fn $fn(&self, $($param: $ty),* ) $( -> $ret)? {
230 self.config.$fn( $($param),* )
231 }
232 )+
233 }
234 }
235}
236
237forward! {
238 do_if_verbose(f: impl Fn()),
239 is_verbose() -> bool,
240 create(path: &Path, s: &str),
241 remove(f: &Path),
242 tempdir() -> PathBuf,
243 download_rustc() -> bool,
244}
245
246pub(crate) struct TargetAndStage {
249 target: TargetSelection,
250 stage: u32,
251}
252
253impl From<(TargetSelection, u32)> for TargetAndStage {
254 fn from((target, stage): (TargetSelection, u32)) -> Self {
255 Self { target, stage }
256 }
257}
258
259impl From<Compiler> for TargetAndStage {
260 fn from(compiler: Compiler) -> Self {
261 Self { target: compiler.host, stage: compiler.stage }
262 }
263}
264
265impl Session {
266 pub(crate) fn new(mut config: Config) -> Session {
271 let src = config.src.clone();
272 let out = config.out.clone();
273
274 #[cfg(unix)]
275 let is_sudo = match env::var_os("SUDO_USER") {
278 Some(_sudo_user) => {
279 let uid = unsafe { libc::getuid() };
284 uid == 0
285 }
286 None => false,
287 };
288 #[cfg(not(unix))]
289 let is_sudo = false;
290
291 let rust_info = config.rust_info.clone();
292 let cargo_info = config.cargo_info.clone();
293 let rust_analyzer_info = config.rust_analyzer_info.clone();
294 let clippy_info = config.clippy_info.clone();
295 let miri_info = config.miri_info.clone();
296 let rustfmt_info = config.rustfmt_info.clone();
297 let enzyme_info = config.enzyme_info.clone();
298 let in_tree_llvm_info = config.in_tree_llvm_info.clone();
299 let in_tree_gcc_info = config.in_tree_gcc_info.clone();
300
301 let initial_target_libdir = command(&config.initial_rustc)
302 .run_in_dry_run()
303 .args(["--print", "target-libdir"])
304 .run_capture_stdout(&config)
305 .stdout()
306 .trim()
307 .to_owned();
308
309 let initial_target_dir = Path::new(&initial_target_libdir)
310 .parent()
311 .unwrap_or_else(|| panic!("{initial_target_libdir} has no parent"));
312
313 let initial_lld = initial_target_dir.join("bin").join("rust-lld");
314
315 let initial_relative_libdir = if cfg!(test) {
316 PathBuf::default()
318 } else {
319 let ancestor = initial_target_dir.ancestors().nth(2).unwrap_or_else(|| {
320 panic!("Not enough ancestors for {}", initial_target_dir.display())
321 });
322
323 ancestor
324 .strip_prefix(&config.initial_sysroot)
325 .unwrap_or_else(|_| {
326 panic!(
327 "Couldn’t resolve the initial relative libdir from {}",
328 initial_target_dir.display()
329 )
330 })
331 .to_path_buf()
332 };
333
334 let version = std::fs::read_to_string(src.join("src").join("version"))
335 .expect("failed to read src/version");
336 let version = version.trim();
337
338 let mut bootstrap_out = std::env::current_exe()
339 .expect("could not determine path to running process")
340 .parent()
341 .unwrap()
342 .to_path_buf();
343 if bootstrap_out.ends_with("deps") {
346 bootstrap_out.pop();
347 }
348 if !bootstrap_out.join(exe("rustc", config.host_target)).exists() && !cfg!(test) {
349 panic!(
351 "`rustc` not found in {}, run `cargo build --bins` before `cargo run`",
352 bootstrap_out.display()
353 )
354 }
355
356 if rust_info.is_from_tarball() && config.description.is_none() {
357 config.description = Some("built from a source tarball".to_owned());
358 }
359
360 let mut sess = Session {
361 initial_lld,
362 initial_relative_libdir,
363 initial_rustc: config.initial_rustc.clone(),
364 initial_rustdoc: config.initial_rustdoc.clone(),
365 initial_cargo: config.initial_cargo.clone(),
366 initial_sysroot: config.initial_sysroot.clone(),
367 local_rebuild: config.local_rebuild,
368 fail_fast: config.cmd.fail_fast(),
369 test_target: config.cmd.test_target(),
370 verbosity: config.exec_ctx.verbosity as usize,
371
372 host_target: config.host_target,
373 hosts: config.hosts.clone(),
374 targets: config.targets.clone(),
375
376 config,
377 version: version.to_string(),
378 src,
379 out,
380 bootstrap_out,
381
382 cargo_info,
383 rust_analyzer_info,
384 clippy_info,
385 miri_info,
386 rustfmt_info,
387 enzyme_info,
388 in_tree_llvm_info,
389 in_tree_gcc_info,
390 cc: HashMap::new(),
391 cxx: HashMap::new(),
392 ar: HashMap::new(),
393 ranlib: HashMap::new(),
394 wasi_sdk_path: env::var_os("WASI_SDK_PATH").map(PathBuf::from),
395 crates: HashMap::new(),
396 crate_paths: HashMap::new(),
397 is_sudo,
398 prerelease_version: Cell::new(None),
399
400 #[cfg(feature = "build-metrics")]
401 metrics: crate::utils::metrics::BuildMetrics::init(),
402
403 #[cfg(feature = "tracing")]
404 step_graph: std::cell::RefCell::new(crate::utils::step_graph::StepGraph::default()),
405 };
406
407 let local_version_verbose = command(&sess.initial_rustc)
410 .run_in_dry_run()
411 .args(["--version", "--verbose"])
412 .run_capture_stdout(&sess)
413 .stdout();
414 let local_release = local_version_verbose
415 .lines()
416 .filter_map(|x| x.strip_prefix("release:"))
417 .next()
418 .unwrap()
419 .trim();
420 if local_release.split('.').take(2).eq(version.split('.').take(2)) {
421 sess.do_if_verbose(|| println!("auto-detected local-rebuild {local_release}"));
422 sess.local_rebuild = true;
423 }
424
425 sess.do_if_verbose(|| println!("finding compilers"));
426 crate::utils::cc_detect::fill_compilers(&mut sess);
427 if !matches!(sess.config.cmd, Subcommand::Setup { .. }) {
433 sess.do_if_verbose(|| println!("running sanity check"));
434 crate::core::sanity::check(&mut sess);
435
436 let rust_submodules = ["library/backtrace"];
439 for s in rust_submodules {
440 sess.require_submodule(
441 s,
442 Some(
443 "The submodule is required for the standard library \
444 and the main Cargo workspace.",
445 ),
446 );
447 }
448 sess.update_existing_submodules();
450
451 sess.do_if_verbose(|| println!("learning about cargo"));
452 crate::core::metadata::build(&mut sess);
453 }
454
455 let build_triple = sess.out.join(sess.host_target);
457 t!(fs::create_dir_all(&build_triple));
458 let host = sess.out.join("host");
459 if host.is_symlink() {
460 #[cfg(windows)]
463 t!(fs::remove_dir(&host));
464 #[cfg(not(windows))]
465 t!(fs::remove_file(&host));
466 }
467 t!(
468 symlink_dir(&sess.config, &build_triple, &host),
469 format!("symlink_dir({} => {}) failed", host.display(), build_triple.display())
470 );
471
472 sess
473 }
474
475 #[cfg_attr(
484 feature = "tracing",
485 instrument(
486 level = "trace",
487 name = "Session::require_submodule",
488 skip_all,
489 fields(submodule = submodule),
490 )
491 )]
492 pub(crate) fn require_submodule(&self, submodule: &str, err_hint: Option<&str>) {
493 if self.rust_info().is_from_tarball() {
494 return;
495 }
496
497 if self.config.dry_run() {
498 return;
499 }
500
501 if cfg!(test) && !self.config.submodules() {
504 return;
505 }
506 self.config.update_submodule(submodule);
507 let absolute_path = self.config.src.join(submodule);
508 if !absolute_path.exists() || dir_is_empty(&absolute_path) {
509 let maybe_enable = if !self.config.submodules()
510 && self.config.rust_info.is_managed_git_subrepository()
511 {
512 "\nConsider setting `build.submodules = true` or manually initializing the submodules."
513 } else {
514 ""
515 };
516 let err_hint = err_hint.map_or_else(String::new, |e| format!("\n{e}"));
517 eprintln!(
518 "submodule {submodule} does not appear to be checked out, \
519 but it is required for this step{maybe_enable}{err_hint}"
520 );
521 helpers::exit_process(1);
522 }
523 }
524
525 pub(crate) fn update_existing_submodules(&self) {
528 if !self.config.submodules() {
531 return;
532 }
533 let output = helpers::git(Some(&self.src))
534 .args(["config", "--file"])
535 .arg(".gitmodules")
536 .args(["--get-regexp", "path"])
537 .run_capture(self)
538 .stdout();
539 std::thread::scope(|s| {
540 for line in output.lines() {
543 let submodule = line.split_once(' ').unwrap().1;
544 let config = self.config.clone();
545 s.spawn(move || {
546 Self::update_existing_submodule(&config, submodule);
547 });
548 }
549 });
550 }
551
552 pub(crate) fn update_existing_submodule(config: &Config, submodule: &str) {
554 if !config.submodules() {
556 return;
557 }
558
559 if config.git_info(false, Path::new(submodule)).is_managed_git_subrepository() {
560 config.update_submodule(submodule);
561 }
562 }
563
564 #[cfg_attr(feature = "tracing", instrument(level = "debug", name = "Session::build", skip_all))]
566 pub(crate) fn build(&mut self) {
567 trace!("setting up job management");
568 unsafe {
569 crate::utils::job::setup(self);
570 }
571
572 {
574 #[cfg(feature = "tracing")]
575 let _hardcoded_span =
576 span!(tracing::Level::DEBUG, "handling hardcoded subcommands (Format, Perf)")
577 .entered();
578
579 match &self.config.cmd {
580 Subcommand::Format { check, all } => {
581 let builder = Builder::new(self);
582 let rustfmt_path = builder.ensure(InternalRustfmt).unwrap_or_else(|| {
583 eprintln!("fmt error: `x fmt` is not supported on this channel");
584 helpers::exit_process(1);
585 });
586 return crate::core::build_steps::format::format(
587 &builder,
588 rustfmt_path,
589 *check,
590 *all,
591 &self.config.paths,
592 );
593 }
594 Subcommand::Perf(args) => {
595 return crate::core::build_steps::perf::perf(&Builder::new(self), args);
596 }
597 _cmd => {
598 debug!(cmd = ?_cmd, "not a hardcoded subcommand; returning to normal handling");
599 }
600 }
601
602 debug!("handling subcommand normally");
603 }
604
605 if !self.config.dry_run() {
606 #[cfg(feature = "tracing")]
607 let _real_run_span = span!(tracing::Level::DEBUG, "executing real run").entered();
608
609 {
612 #[cfg(feature = "tracing")]
613 let _sanity_check_span =
614 span!(tracing::Level::DEBUG, "(1) executing dry-run sanity-check").entered();
615 self.config.set_dry_run(DryRun::SelfCheck);
616 let builder = Builder::new(self);
617 builder.execute_cli();
618 }
619
620 {
622 #[cfg(feature = "tracing")]
623 let _actual_run_span =
624 span!(tracing::Level::DEBUG, "(2) executing actual run").entered();
625 self.config.set_dry_run(DryRun::Disabled);
626 let builder = Builder::new(self);
627 builder.execute_cli();
628 }
629 } else {
630 #[cfg(feature = "tracing")]
631 let _dry_run_span = span!(tracing::Level::DEBUG, "executing dry run").entered();
632
633 let builder = Builder::new(self);
634 builder.execute_cli();
635 }
636
637 #[cfg(feature = "tracing")]
638 debug!("checking for postponed test failures from `test --no-fail-fast`");
639
640 self.config.exec_ctx().report_failures_and_exit();
642
643 #[cfg(feature = "build-metrics")]
644 self.metrics.persist(self);
645 }
646
647 pub(crate) fn rust_info(&self) -> &GitInfo {
648 &self.config.rust_info
649 }
650
651 pub(crate) fn std_features(&self, target: TargetSelection) -> String {
654 let mut features: BTreeSet<&str> =
655 self.config.rust_std_features.iter().map(|s| s.as_str()).collect();
656
657 match self.config.llvm_libunwind(target) {
658 LlvmLibunwind::InTree => features.insert("llvm-libunwind"),
659 LlvmLibunwind::System => features.insert("system-llvm-libunwind"),
660 LlvmLibunwind::No => false,
661 };
662
663 if self.config.backtrace {
664 features.insert("backtrace");
665 }
666
667 if self.config.profiler_enabled(target) {
668 features.insert("profiler");
669 }
670
671 if target.contains("zkvm") {
673 features.insert("compiler-builtins-mem");
674 }
675
676 features.into_iter().collect::<Vec<_>>().join(" ")
677 }
678
679 pub(crate) fn rustc_features(
681 &self,
682 kind: Kind,
683 target: TargetSelection,
684 crates: &[String],
685 ) -> String {
686 let possible_features_by_crates: HashSet<_> = crates
687 .iter()
688 .flat_map(|krate| &self.crates[krate].features)
689 .map(std::ops::Deref::deref)
690 .collect();
691 let check = |feature: &str| -> bool {
692 crates.is_empty() || possible_features_by_crates.contains(feature)
693 };
694 let mut features = vec![];
695
696 if let Some(allocator_feature_name) = self.config.allocator(target).feature_name()
697 && check(allocator_feature_name)
698 {
699 features.push(allocator_feature_name);
700 }
701 if (self.config.llvm_enabled(target) || kind == Kind::Check) && check("llvm") {
702 features.push("llvm");
703 }
704 if self.config.llvm_offload {
705 features.push("llvm_offload");
706 }
707 if self.config.rust_randomize_layout && check("rustc_randomized_layouts") {
709 features.push("rustc_randomized_layouts");
710 }
711 if self.config.compile_time_deps && kind == Kind::Check {
712 features.push("check_only");
713 }
714
715 if crates.iter().any(|c| c == "rustc_transmute") {
716 features.push("rustc");
719 }
720
721 if !self.config.rust_debug_logging && check("max_level_info") {
727 features.push("max_level_info");
728 }
729
730 features.join(" ")
731 }
732
733 pub(crate) fn cargo_dir(&self, mode: Mode) -> &'static str {
736 match (mode, self.config.rust_optimize.is_release()) {
737 (Mode::Std, _) => "dist",
738 (_, true) => "release",
739 (_, false) => "debug",
740 }
741 }
742
743 pub(crate) fn tools_dir(&self, build_compiler: Compiler) -> PathBuf {
744 let out = self
745 .out
746 .join(build_compiler.host)
747 .join(format!("stage{}-tools-bin", build_compiler.stage + 1));
748 t!(fs::create_dir_all(&out));
749 out
750 }
751
752 pub(crate) fn stage_out(&self, build_compiler: Compiler, mode: Mode) -> PathBuf {
757 use std::fmt::Write;
758
759 fn bootstrap_tool() -> (Option<u32>, &'static str) {
760 (None, "bootstrap-tools")
761 }
762 fn staged_tool(build_compiler: Compiler) -> (Option<u32>, &'static str) {
763 (Some(build_compiler.stage + 1), "tools")
764 }
765
766 let (stage, suffix) = match mode {
767 Mode::Std => (Some(build_compiler.stage), "std"),
769 Mode::Rustc => (Some(build_compiler.stage + 1), "rustc"),
771 Mode::Codegen => (Some(build_compiler.stage + 1), "codegen"),
772 Mode::ToolBootstrap => bootstrap_tool(),
773 Mode::ToolStd | Mode::ToolRustcPrivate => (Some(build_compiler.stage + 1), "tools"),
774 Mode::ToolTarget => {
775 if build_compiler.stage == 0 {
778 bootstrap_tool()
779 } else {
780 staged_tool(build_compiler)
781 }
782 }
783 };
784 let path = self.out.join(build_compiler.host);
785 let mut dir_name = String::new();
786 if let Some(stage) = stage {
787 write!(dir_name, "stage{stage}-").unwrap();
788 }
789 dir_name.push_str(suffix);
790 path.join(dir_name)
791 }
792
793 pub(crate) fn cargo_out(
797 &self,
798 build_compiler: Compiler,
799 mode: Mode,
800 target: TargetSelection,
801 ) -> PathBuf {
802 self.stage_out(build_compiler, mode).join(target).join(self.cargo_dir(mode))
803 }
804
805 pub(crate) fn doc_out(&self, target: TargetSelection) -> PathBuf {
807 self.out.join(target).join("doc")
808 }
809
810 pub(crate) fn json_doc_out(&self, target: TargetSelection) -> PathBuf {
812 self.out.join(target).join("json-doc")
813 }
814
815 pub(crate) fn test_out(&self, target: TargetSelection) -> PathBuf {
816 self.out.join(target).join("test")
817 }
818
819 pub(crate) fn compiler_doc_out(&self, target: TargetSelection) -> PathBuf {
821 self.out.join(target).join("compiler-doc")
822 }
823
824 pub(crate) fn md_doc_out(&self, target: TargetSelection) -> PathBuf {
826 self.out.join(target).join("md-doc")
827 }
828
829 pub(crate) fn vendored_crates_path(&self) -> Option<PathBuf> {
831 if self.config.vendor { Some(self.src.join(VENDOR_DIR)) } else { None }
832 }
833
834 pub(crate) fn native_dir(&self, target: TargetSelection) -> PathBuf {
836 self.out.join(target).join("native")
837 }
838
839 pub(crate) fn test_helpers_out(&self, target: TargetSelection) -> PathBuf {
842 self.native_dir(target).join("rust-test-helpers")
843 }
844
845 pub(crate) fn add_rust_test_threads(&self, cmd: &mut BootstrapCommand) {
847 if env::var_os("RUST_TEST_THREADS").is_none() {
848 cmd.env("RUST_TEST_THREADS", self.jobs().to_string());
849 }
850 }
851
852 pub(crate) fn rustc_snapshot_libdir(&self) -> PathBuf {
854 self.rustc_snapshot_sysroot().join(libdir(self.config.host_target))
855 }
856
857 pub(crate) fn rustc_snapshot_sysroot(&self) -> &Path {
859 static SYSROOT_CACHE: OnceLock<PathBuf> = OnceLock::new();
860 SYSROOT_CACHE.get_or_init(|| {
861 command(&self.initial_rustc)
862 .run_in_dry_run()
863 .args(["--print", "sysroot"])
864 .run_capture_stdout(self)
865 .stdout()
866 .trim()
867 .to_owned()
868 .into()
869 })
870 }
871
872 pub(crate) fn info(&self, msg: &str) {
873 match self.config.get_dry_run() {
874 DryRun::SelfCheck => (),
875 DryRun::Disabled | DryRun::UserSelected => {
876 println!("{msg}");
877 }
878 }
879 }
880
881 #[must_use = "Groups should not be dropped until the Step finishes running"]
893 #[track_caller]
894 pub(crate) fn msg(
895 &self,
896 action: impl Into<Kind>,
897 what: impl Display,
898 mode: impl Into<Option<Mode>>,
899 target_and_stage: impl Into<TargetAndStage>,
900 target: impl Into<Option<TargetSelection>>,
901 ) -> Option<gha::Group> {
902 let target_and_stage = target_and_stage.into();
903 let action = action.into();
904 assert!(
905 action != Kind::Test,
906 "Please use `Session::msg_test` instead of `Session::msg(Kind::Test)`"
907 );
908
909 let actual_stage = match mode.into() {
910 Some(Mode::Std) => target_and_stage.stage,
912 Some(
914 Mode::Rustc
915 | Mode::Codegen
916 | Mode::ToolBootstrap
917 | Mode::ToolTarget
918 | Mode::ToolStd
919 | Mode::ToolRustcPrivate,
920 )
921 | None => target_and_stage.stage + 1,
922 };
923
924 let action = action.description();
925 let what = what.to_string();
926 let msg = |fmt| {
927 let space = if !what.is_empty() { " " } else { "" };
928 format!("{action} stage{actual_stage} {what}{space}{fmt}")
929 };
930 let msg = if let Some(target) = target.into() {
931 let build_stage = target_and_stage.stage;
932 let host = target_and_stage.target;
933 if host == target {
934 msg(format_args!("(stage{build_stage} -> stage{actual_stage}, {target})"))
935 } else {
936 msg(format_args!("(stage{build_stage}:{host} -> stage{actual_stage}:{target})"))
937 }
938 } else {
939 msg(format_args!(""))
940 };
941 self.group(&msg)
942 }
943
944 #[must_use = "Groups should not be dropped until the Step finishes running"]
950 #[track_caller]
951 pub(crate) fn msg_test(
952 &self,
953 what: impl Display,
954 target: TargetSelection,
955 stage: u32,
956 ) -> Option<gha::Group> {
957 let action = Kind::Test.description();
958 let msg = format!("{action} stage{stage} {what} ({target})");
959 self.group(&msg)
960 }
961
962 #[must_use = "Groups should not be dropped until the Step finishes running"]
966 #[track_caller]
967 pub(crate) fn msg_unstaged(
968 &self,
969 action: impl Into<Kind>,
970 what: impl Display,
971 target: TargetSelection,
972 ) -> Option<gha::Group> {
973 let action = action.into().description();
974 let msg = format!("{action} {what} for {target}");
975 self.group(&msg)
976 }
977
978 #[track_caller]
979 pub(crate) fn group(&self, msg: &str) -> Option<gha::Group> {
980 match self.config.get_dry_run() {
981 DryRun::SelfCheck => None,
982 DryRun::Disabled | DryRun::UserSelected => Some(gha::group(msg)),
983 }
984 }
985
986 pub(crate) fn jobs(&self) -> u32 {
989 self.config.jobs.unwrap_or_else(|| {
990 std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) as u32
991 })
992 }
993
994 pub(crate) fn debuginfo_map_to(
995 &self,
996 which: GitRepo,
997 remap_scheme: RemapScheme,
998 ) -> Option<String> {
999 if !self.config.rust_remap_debuginfo {
1000 return None;
1001 }
1002
1003 match which {
1004 GitRepo::Rustc => {
1005 let sha = self.rust_sha().unwrap_or(&self.version);
1006
1007 match remap_scheme {
1008 RemapScheme::Compiler => {
1009 Some(format!("/rustc-dev/{sha}"))
1018 }
1019 RemapScheme::NonCompiler => {
1020 Some(format!("/rustc/{sha}"))
1022 }
1023 }
1024 }
1025 GitRepo::Llvm => Some(String::from("/rustc/llvm")),
1026 }
1027 }
1028
1029 pub(crate) fn cc(&self, target: TargetSelection) -> PathBuf {
1031 if self.config.dry_run() {
1032 return PathBuf::new();
1033 }
1034 self.cc[&target].path().into()
1035 }
1036
1037 pub(crate) fn cc_tool(&self, target: TargetSelection) -> cc::Tool {
1039 self.cc[&target].clone()
1040 }
1041
1042 pub(crate) fn cxx_tool(&self, target: TargetSelection) -> cc::Tool {
1044 self.cxx[&target].clone()
1045 }
1046
1047 pub(crate) fn cc_handled_cflags(&self, target: TargetSelection, c: CLang) -> Vec<String> {
1050 if self.config.dry_run() {
1051 return Vec::new();
1052 }
1053 let base = match c {
1054 CLang::C => self.cc[&target].clone(),
1055 CLang::Cxx => self.cxx[&target].clone(),
1056 };
1057
1058 base.args()
1061 .iter()
1062 .map(|s| s.to_string_lossy().into_owned())
1063 .filter(|s| !s.starts_with("-O") && !s.starts_with("/O"))
1064 .collect::<Vec<String>>()
1065 }
1066
1067 pub(crate) fn cc_unhandled_cflags(
1069 &self,
1070 target: TargetSelection,
1071 which: GitRepo,
1072 c: CLang,
1073 ) -> Vec<String> {
1074 let mut base = Vec::new();
1075
1076 if matches!(c, CLang::Cxx) && target.contains("apple-darwin") {
1080 base.push("-stdlib=libc++".into());
1081 }
1082
1083 if &*target.triple == "i686-pc-windows-gnu" {
1087 base.push("-fno-omit-frame-pointer".into());
1088 }
1089
1090 if let Some(map_to) = self.debuginfo_map_to(which, RemapScheme::NonCompiler) {
1091 let map = format!("{}={}", self.src.display(), map_to);
1092 let cc = self.cc_tool(target);
1093 if cc.is_like_clang() || cc.is_like_gnu() {
1094 base.push(format!("-fdebug-prefix-map={map}"));
1095 } else if cc.is_like_clang_cl() {
1096 base.push("-Xclang".into());
1097 base.push(format!("-fdebug-prefix-map={map}"));
1098 }
1099 }
1100 base
1101 }
1102
1103 pub(crate) fn ar(&self, target: TargetSelection) -> Option<PathBuf> {
1105 if self.config.dry_run() {
1106 return None;
1107 }
1108 self.ar.get(&target).cloned()
1109 }
1110
1111 pub(crate) fn ranlib(&self, target: TargetSelection) -> Option<PathBuf> {
1113 if self.config.dry_run() {
1114 return None;
1115 }
1116 self.ranlib.get(&target).cloned()
1117 }
1118
1119 pub(crate) fn cxx(&self, target: TargetSelection) -> Result<PathBuf, String> {
1121 if self.config.dry_run() {
1122 return Ok(PathBuf::new());
1123 }
1124 match self.cxx.get(&target) {
1125 Some(p) => Ok(p.path().into()),
1126 None => Err(format!("target `{target}` is not configured as a host, only as a target")),
1127 }
1128 }
1129
1130 pub(crate) fn linker(&self, target: TargetSelection) -> Option<PathBuf> {
1132 if self.config.dry_run() {
1133 return Some(PathBuf::new());
1134 }
1135 if let Some(linker) = self.config.target_config.get(&target).and_then(|c| c.linker.clone())
1136 {
1137 Some(linker)
1138 } else if target.contains("vxworks") {
1139 Some(self.cxx[&target].path().into())
1142 } else if !self.config.is_host_target(target)
1143 && helpers::use_host_linker(target)
1144 && !target.is_msvc()
1145 {
1146 Some(self.cc(target))
1147 } else if self.config.bootstrap_override_lld.is_used()
1148 && self.is_lld_direct_linker(target)
1149 && self.host_target == target
1150 {
1151 match self.config.bootstrap_override_lld {
1152 BootstrapOverrideLld::SelfContained => Some(self.initial_lld.clone()),
1153 BootstrapOverrideLld::External => Some("lld".into()),
1154 BootstrapOverrideLld::None => None,
1155 }
1156 } else {
1157 None
1158 }
1159 }
1160
1161 pub(crate) fn is_lld_direct_linker(&self, target: TargetSelection) -> bool {
1164 target.is_msvc()
1165 }
1166
1167 pub(crate) fn crt_static(&self, target: TargetSelection) -> Option<bool> {
1169 if target.contains("pc-windows-msvc") {
1170 Some(true)
1171 } else {
1172 self.config.target_config.get(&target).and_then(|t| t.crt_static)
1173 }
1174 }
1175
1176 pub(crate) fn musl_root(&self, target: TargetSelection) -> Option<&Path> {
1181 let configured_root = self
1182 .config
1183 .target_config
1184 .get(&target)
1185 .and_then(|t| t.musl_root.as_ref())
1186 .or(self.config.musl_root.as_ref())
1187 .map(|p| &**p);
1188
1189 if self.config.is_host_target(target) && configured_root.is_none() {
1190 Some(Path::new("/usr"))
1191 } else {
1192 configured_root
1193 }
1194 }
1195
1196 pub(crate) fn musl_libdir(&self, target: TargetSelection) -> Option<PathBuf> {
1198 self.config
1199 .target_config
1200 .get(&target)
1201 .and_then(|t| t.musl_libdir.clone())
1202 .or_else(|| self.musl_root(target).map(|root| root.join("lib")))
1203 }
1204
1205 pub(crate) fn wasi_libdir(&self, target: TargetSelection) -> Option<PathBuf> {
1212 let configured =
1213 self.config.target_config.get(&target).and_then(|t| t.wasi_root.as_ref()).map(|p| &**p);
1214 if let Some(path) = configured {
1215 return Some(path.join("lib").join(target.to_string()));
1216 }
1217 let mut env_root = self.wasi_sdk_path.clone()?;
1218 env_root.push("share");
1219 env_root.push("wasi-sysroot");
1220 env_root.push("lib");
1221 env_root.push(target.to_string());
1222 Some(env_root)
1223 }
1224
1225 pub(crate) fn no_std(&self, target: TargetSelection) -> Option<bool> {
1227 self.config.target_config.get(&target).map(|t| t.no_std)
1228 }
1229
1230 pub(crate) fn remote_tested(&self, target: TargetSelection) -> bool {
1233 self.qemu_rootfs(target).is_some()
1234 || target.contains("android")
1235 || env::var_os("TEST_DEVICE_ADDR").is_some()
1236 }
1237
1238 pub(crate) fn runner(&self, target: TargetSelection) -> Option<String> {
1244 let configured_runner =
1245 self.config.target_config.get(&target).and_then(|t| t.runner.as_ref()).map(|p| &**p);
1246 if let Some(runner) = configured_runner {
1247 return Some(runner.to_owned());
1248 }
1249
1250 if target.starts_with("wasm") && target.contains("wasi") {
1251 self.default_wasi_runner(target)
1252 } else {
1253 None
1254 }
1255 }
1256
1257 fn default_wasi_runner(&self, target: TargetSelection) -> Option<String> {
1261 let mut finder = crate::core::sanity::Finder::new();
1262
1263 if let Some(path) = finder.maybe_have("wasmtime")
1267 && let Ok(mut path) = path.into_os_string().into_string()
1268 {
1269 path.push_str(" run -Wexceptions -C cache=n --dir .");
1270 path.push_str(" --env RUSTC_BOOTSTRAP");
1277
1278 if target.contains("wasip2") {
1279 path.push_str(" --wasi inherit-network --wasi allow-ip-name-lookup");
1280 }
1281
1282 return Some(path);
1283 }
1284
1285 None
1286 }
1287
1288 pub(crate) fn tool_enabled(&self, tool: &str) -> bool {
1293 if !self.config.extended {
1294 return false;
1295 }
1296 match &self.config.tools {
1297 Some(set) => set.contains(tool),
1298 None => true,
1299 }
1300 }
1301
1302 pub(crate) fn qemu_rootfs(&self, target: TargetSelection) -> Option<&Path> {
1308 self.config.target_config.get(&target).and_then(|t| t.qemu_rootfs.as_ref()).map(|p| &**p)
1309 }
1310
1311 pub(crate) fn extended_error_dir(&self) -> PathBuf {
1313 self.out.join("tmp/extended-error-metadata")
1314 }
1315
1316 pub(crate) fn force_use_stage1(&self, stage: u32, target: TargetSelection) -> bool {
1335 !self.config.full_bootstrap
1336 && !self.config.download_rustc()
1337 && stage >= 2
1338 && (self.hosts.contains(&target) || target == self.host_target)
1339 }
1340
1341 pub(crate) fn force_use_stage2(&self, stage: u32) -> bool {
1347 self.config.download_rustc() && stage >= 2
1348 }
1349
1350 pub(crate) fn release(&self, num: &str) -> String {
1356 match &self.config.channel[..] {
1357 "stable" => num.to_string(),
1358 "beta" => {
1359 if !self.config.omit_git_hash {
1360 format!("{}-beta.{}", num, self.beta_prerelease_version())
1361 } else {
1362 format!("{num}-beta")
1363 }
1364 }
1365 "nightly" => format!("{num}-nightly"),
1366 _ => format!("{num}-dev"),
1367 }
1368 }
1369
1370 fn beta_prerelease_version(&self) -> u32 {
1371 fn extract_beta_rev_from_file<P: AsRef<Path>>(version_file: P) -> Option<String> {
1372 let version = fs::read_to_string(version_file).ok()?;
1373
1374 helpers::extract_beta_rev(&version)
1375 }
1376
1377 if let Some(s) = self.prerelease_version.get() {
1378 return s;
1379 }
1380
1381 let count = extract_beta_rev_from_file(self.src.join("version")).unwrap_or_else(|| {
1385 helpers::git(Some(&self.src))
1389 .arg("rev-list")
1390 .arg("--count")
1391 .arg("--merges")
1392 .arg(format!(
1393 "refs/remotes/origin/{}..HEAD",
1394 self.config.stage0_metadata.config.nightly_branch
1395 ))
1396 .run_in_dry_run()
1397 .run_capture(self)
1398 .stdout()
1399 });
1400 let n = count.trim().parse().unwrap();
1401 self.prerelease_version.set(Some(n));
1402 n
1403 }
1404
1405 pub(crate) fn rust_release(&self) -> String {
1407 self.release(&self.version)
1408 }
1409
1410 pub(crate) fn rust_package_vers(&self) -> String {
1416 match &self.config.channel[..] {
1417 "stable" => self.version.to_string(),
1418 "beta" => "beta".to_string(),
1419 "nightly" => "nightly".to_string(),
1420 _ => format!("{}-dev", self.version),
1421 }
1422 }
1423
1424 pub(crate) fn rust_version(&self) -> String {
1430 let mut version = self.rust_info().version(self, &self.version);
1431 if let Some(ref s) = self.config.description
1432 && !s.is_empty()
1433 {
1434 version.push_str(" (");
1435 version.push_str(s);
1436 version.push(')');
1437 }
1438 version
1439 }
1440
1441 pub(crate) fn rust_sha(&self) -> Option<&str> {
1443 self.rust_info().sha()
1444 }
1445
1446 pub(crate) fn release_num(&self, package: &str) -> String {
1448 if self.config.dry_run() {
1449 return "0.0.0 (dry-run)".into();
1450 }
1451 let toml_file_name = self.src.join(format!("src/tools/{package}/Cargo.toml"));
1452 let toml = t!(fs::read_to_string(toml_file_name));
1453 for line in toml.lines() {
1454 if let Some(stripped) =
1455 line.strip_prefix("version = \"").and_then(|s| s.strip_suffix('"'))
1456 {
1457 return stripped.to_owned();
1458 }
1459 }
1460
1461 panic!("failed to find version in {package}'s Cargo.toml")
1462 }
1463
1464 pub(crate) fn unstable_features(&self) -> bool {
1467 !matches!(&self.config.channel[..], "stable" | "beta")
1468 }
1469
1470 pub(crate) fn in_tree_crates(
1474 &self,
1475 root: &str,
1476 target: Option<TargetSelection>,
1477 ) -> Vec<&Crate> {
1478 let mut ret = Vec::new();
1479 let mut list = vec![root.to_owned()];
1480 let mut visited = HashSet::new();
1481 while let Some(krate) = list.pop() {
1482 let krate = self
1483 .crates
1484 .get(&krate)
1485 .unwrap_or_else(|| panic!("metadata missing for {krate}: {:?}", self.crates));
1486 ret.push(krate);
1487 for dep in &krate.deps {
1488 if !self.crates.contains_key(dep) {
1489 continue;
1491 }
1492 if visited.insert(dep)
1498 && (dep != "profiler_builtins"
1499 || target
1500 .map(|t| self.config.profiler_enabled(t))
1501 .unwrap_or_else(|| self.config.any_profiler_enabled()))
1502 && (dep != "rustc_codegen_llvm"
1503 || self.config.hosts.iter().any(|host| self.config.llvm_enabled(*host)))
1504 {
1505 list.push(dep.clone());
1506 }
1507 }
1508 }
1509
1510 ret.sort_unstable_by(|a, b| Ord::cmp(&a.name, &b.name));
1512 ret
1513 }
1514
1515 pub(crate) fn read_stamp_file(&self, stamp: &BuildStamp) -> Vec<(PathBuf, DependencyType)> {
1516 if self.config.dry_run() {
1517 return Vec::new();
1518 }
1519
1520 if !stamp.path().exists() {
1521 eprintln!(
1522 "ERROR: Unable to find the stamp file {}, did you try to keep a nonexistent build stage?",
1523 stamp.path().display()
1524 );
1525 helpers::exit_process(1);
1526 }
1527
1528 let mut paths = Vec::new();
1529 let contents = t!(fs::read(stamp.path()), stamp.path());
1530 for part in contents.split(|b| *b == 0) {
1533 if part.is_empty() {
1534 continue;
1535 }
1536 let dependency_type = match part[0] as char {
1537 'h' => DependencyType::Host,
1538 's' => DependencyType::TargetSelfContained,
1539 't' => DependencyType::Target,
1540 _ => unreachable!(),
1541 };
1542 let path = PathBuf::from(t!(str::from_utf8(&part[1..])));
1543 paths.push((path, dependency_type));
1544 }
1545 paths
1546 }
1547
1548 #[track_caller]
1553 pub(crate) fn resolve_symlink_and_copy(&self, src: &Path, dst: &Path) {
1554 self.copy_link_internal(src, dst, true);
1555 }
1556
1557 #[track_caller]
1562 pub(crate) fn copy_link(&self, src: &Path, dst: &Path, file_type: FileType) {
1563 self.copy_link_internal(src, dst, false);
1564
1565 if file_type.could_have_split_debuginfo()
1566 && let Some(dbg_file) = split_debuginfo(src)
1567 {
1568 self.copy_link_internal(
1569 &dbg_file,
1570 &dst.with_extension(dbg_file.extension().unwrap()),
1571 false,
1572 );
1573 }
1574 }
1575
1576 #[track_caller]
1577 fn copy_link_internal(&self, src: &Path, dst: &Path, dereference_symlinks: bool) {
1578 if self.config.dry_run() {
1579 return;
1580 }
1581 if src == dst {
1582 return;
1583 }
1584
1585 #[cfg(feature = "tracing")]
1586 let _span = trace_io!("file-copy-link", ?src, ?dst);
1587
1588 if let Err(e) = fs::remove_file(dst)
1589 && cfg!(windows)
1590 && e.kind() != io::ErrorKind::NotFound
1591 {
1592 let now = t!(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH));
1595 let _ = fs::rename(dst, format!("{}-{}", dst.display(), now.as_nanos()));
1596 }
1597 let mut metadata = t!(src.symlink_metadata(), format!("src = {}", src.display()));
1598 let mut src = src.to_path_buf();
1599 if metadata.file_type().is_symlink() {
1600 if dereference_symlinks {
1601 src = t!(fs::canonicalize(src));
1602 metadata = t!(fs::metadata(&src), format!("target = {}", src.display()));
1603 } else {
1604 let link = t!(fs::read_link(src));
1605 if is_symlink_dir(&metadata) {
1606 t!(symlink_dir(&self.config, &link, dst));
1607 } else {
1608 t!(self.symlink_file(link, dst));
1609 }
1610 return;
1611 }
1612 }
1613 if let Ok(()) = fs::hard_link(&src, dst) {
1614 } else {
1617 if let Err(e) = fs::copy(&src, dst) {
1618 panic!("failed to copy `{}` to `{}`: {}", src.display(), dst.display(), e)
1619 }
1620 t!(fs::set_permissions(dst, metadata.permissions()));
1621
1622 let file_times = fs::FileTimes::new()
1625 .set_accessed(t!(metadata.accessed()))
1626 .set_modified(t!(metadata.modified()));
1627 t!(set_file_times(dst, file_times));
1628 }
1629 }
1630
1631 #[track_caller]
1635 pub(crate) fn cp_link_r(&self, src: &Path, dst: &Path) {
1636 if self.config.dry_run() {
1637 return;
1638 }
1639 for f in self.read_dir(src) {
1640 let path = f.path();
1641 let name = path.file_name().unwrap();
1642 let dst = dst.join(name);
1643 if t!(f.file_type()).is_dir() {
1644 t!(fs::create_dir_all(&dst));
1645 self.cp_link_r(&path, &dst);
1646 } else {
1647 self.copy_link(&path, &dst, FileType::Regular);
1648 }
1649 }
1650 }
1651
1652 #[track_caller]
1658 pub(crate) fn cp_link_filtered(&self, src: &Path, dst: &Path, filter: &dyn Fn(&Path) -> bool) {
1659 self.cp_link_filtered_recurse(src, dst, Path::new(""), filter)
1661 }
1662
1663 #[track_caller]
1665 fn cp_link_filtered_recurse(
1666 &self,
1667 src: &Path,
1668 dst: &Path,
1669 relative: &Path,
1670 filter: &dyn Fn(&Path) -> bool,
1671 ) {
1672 for f in self.read_dir(src) {
1673 let path = f.path();
1674 let name = path.file_name().unwrap();
1675 let dst = dst.join(name);
1676 let relative = relative.join(name);
1677 if filter(&relative) {
1679 if t!(f.file_type()).is_dir() {
1680 let _ = fs::remove_dir_all(&dst);
1681 self.create_dir(&dst);
1682 self.cp_link_filtered_recurse(&path, &dst, &relative, filter);
1683 } else {
1684 self.copy_link(&path, &dst, FileType::Regular);
1685 }
1686 }
1687 }
1688 }
1689
1690 pub(crate) fn copy_link_to_folder(&self, src: &Path, dest_folder: &Path) {
1691 let file_name = src.file_name().unwrap();
1692 let dest = dest_folder.join(file_name);
1693 self.copy_link(src, &dest, FileType::Regular);
1694 }
1695
1696 pub(crate) fn install(&self, src: &Path, dstdir: &Path, file_type: FileType) {
1697 if self.config.dry_run() {
1698 return;
1699 }
1700 let dst = dstdir.join(src.file_name().unwrap());
1701
1702 #[cfg(feature = "tracing")]
1703 let _span = trace_io!("install", ?src, ?dst);
1704
1705 t!(fs::create_dir_all(dstdir));
1706 if !src.exists() {
1707 panic!("ERROR: File \"{}\" not found!", src.display());
1708 }
1709
1710 self.copy_link_internal(src, &dst, true);
1711 chmod(&dst, file_type.perms());
1712
1713 if file_type.could_have_split_debuginfo()
1715 && let Some(dbg_file) = split_debuginfo(src)
1716 {
1717 self.install(&dbg_file, dstdir, FileType::Regular);
1718 }
1719 }
1720
1721 pub(crate) fn read(&self, path: &Path) -> String {
1722 if self.config.dry_run() {
1723 return String::new();
1724 }
1725 t!(fs::read_to_string(path))
1726 }
1727
1728 #[track_caller]
1729 pub(crate) fn create_dir(&self, dir: &Path) {
1730 if self.config.dry_run() {
1731 return;
1732 }
1733
1734 #[cfg(feature = "tracing")]
1735 let _span = trace_io!("dir-create", ?dir);
1736
1737 t!(fs::create_dir_all(dir))
1738 }
1739
1740 pub(crate) fn remove_dir(&self, dir: &Path) {
1741 if self.config.dry_run() {
1742 return;
1743 }
1744
1745 #[cfg(feature = "tracing")]
1746 let _span = trace_io!("dir-remove", ?dir);
1747
1748 t!(fs::remove_dir_all(dir))
1749 }
1750
1751 pub(crate) fn clear_dir(&self, dir: &Path) {
1754 if self.config.dry_run() {
1755 return;
1756 }
1757
1758 #[cfg(feature = "tracing")]
1759 let _span = trace_io!("dir-clear", ?dir);
1760
1761 let _ = std::fs::remove_dir_all(dir);
1762 self.create_dir(dir);
1763 }
1764
1765 pub(crate) fn read_dir(&self, dir: &Path) -> impl Iterator<Item = fs::DirEntry> {
1766 let iter = match fs::read_dir(dir) {
1767 Ok(v) => v,
1768 Err(_) if self.config.dry_run() => return vec![].into_iter(),
1769 Err(err) => panic!("could not read dir {dir:?}: {err:?}"),
1770 };
1771 iter.map(|e| t!(e)).collect::<Vec<_>>().into_iter()
1772 }
1773
1774 pub(crate) fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(
1775 &self,
1776 src: P,
1777 link: Q,
1778 ) -> io::Result<()> {
1779 #[cfg(unix)]
1780 use std::os::unix::fs::symlink as symlink_file;
1781 #[cfg(windows)]
1782 use std::os::windows::fs::symlink_file;
1783 if !self.config.dry_run() { symlink_file(src.as_ref(), link.as_ref()) } else { Ok(()) }
1784 }
1785
1786 pub(crate) fn ninja(&self) -> bool {
1789 let mut cmd_finder = crate::core::sanity::Finder::new();
1790
1791 if self.config.ninja_in_file {
1792 if cmd_finder.maybe_have("ninja-build").is_none()
1795 && cmd_finder.maybe_have("ninja").is_none()
1796 {
1797 eprintln!(
1798 "
1799Couldn't find required command: ninja (or ninja-build)
1800
1801You should install ninja as described at
1802<https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages>,
1803or set `ninja = false` in the `[llvm]` section of `bootstrap.toml`.
1804Alternatively, set `download-ci-llvm = true` in that `[llvm]` section
1805to download LLVM rather than building it.
1806"
1807 );
1808 helpers::exit_process(1);
1809 }
1810 }
1811
1812 if !self.config.ninja_in_file
1820 && self.config.host_target.is_msvc()
1821 && cmd_finder.maybe_have("ninja").is_some()
1822 {
1823 return true;
1824 }
1825
1826 self.config.ninja_in_file
1827 }
1828
1829 pub(crate) fn colored_stdout<R, F: FnOnce(&mut dyn WriteColor) -> R>(&self, f: F) -> R {
1830 self.colored_stream_inner(StandardStream::stdout, self.config.stdout_is_tty, f)
1831 }
1832
1833 #[expect(dead_code, reason = "symmetric with `colored_stdout`")]
1834 pub(crate) fn colored_stderr<R, F: FnOnce(&mut dyn WriteColor) -> R>(&self, f: F) -> R {
1835 self.colored_stream_inner(StandardStream::stderr, self.config.stderr_is_tty, f)
1836 }
1837
1838 fn colored_stream_inner<R, F, C>(&self, constructor: C, is_tty: bool, f: F) -> R
1839 where
1840 C: Fn(ColorChoice) -> StandardStream,
1841 F: FnOnce(&mut dyn WriteColor) -> R,
1842 {
1843 let choice = match self.config.color {
1844 flags::Color::Always => ColorChoice::Always,
1845 flags::Color::Never => ColorChoice::Never,
1846 flags::Color::Auto if !is_tty => ColorChoice::Never,
1847 flags::Color::Auto => ColorChoice::Auto,
1848 };
1849 let mut stream = constructor(choice);
1850 let result = f(&mut stream);
1851 stream.reset().unwrap();
1852 result
1853 }
1854
1855 #[cfg_attr(not(feature = "tracing"), expect(dead_code))]
1856 pub(crate) fn report_summary(&self, path: &Path, start_time: Instant) {
1857 self.config.exec_ctx.profiler().report_summary(path, start_time);
1858 }
1859
1860 #[cfg(feature = "tracing")]
1861 pub(crate) fn report_step_graph(self, directory: &Path) {
1862 self.step_graph.into_inner().store_to_dot_files(directory);
1863 }
1864}
1865
1866impl AsRef<ExecutionContext> for Session {
1867 fn as_ref(&self) -> &ExecutionContext {
1868 &self.config.exec_ctx
1869 }
1870}
1871
1872#[cfg(unix)]
1873fn chmod(path: &Path, perms: u32) {
1874 use std::os::unix::fs::*;
1875 t!(fs::set_permissions(path, fs::Permissions::from_mode(perms)));
1876}
1877#[cfg(windows)]
1878fn chmod(_path: &Path, _perms: u32) {}