1use std::cell::Cell;
2use std::collections::{BTreeSet, HashMap, HashSet};
3use std::fmt::Display;
4use std::ops::Deref;
5use std::path::{Path, PathBuf};
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::download::{DownloadContext, download_beta_toolchain};
22use crate::core::metadata::Crate;
23#[cfg(feature = "tracing")]
24use crate::trace_io;
25use crate::utils::build_stamp::BuildStamp;
26use crate::utils::channel::GitInfo;
27use crate::utils::exec::{BootstrapCommand, ExecutionContext, command};
28use crate::utils::helpers::{
29 self, dir_is_empty, exe, is_symlink_dir, libdir, set_file_times, split_debuginfo, symlink_dir,
30 t,
31};
32use crate::{debug, trace};
33
34pub(crate) struct Session {
40 pub(crate) config: Config,
42
43 pub(crate) version: String,
45
46 pub(crate) bootstrap_out: PathBuf,
48 pub(crate) fail_fast: bool,
49 pub(crate) test_target: TestTarget,
50 pub(crate) verbosity: usize,
51
52 pub(crate) initial_rustc: PathBuf,
53 pub(crate) initial_rustdoc: PathBuf,
54 pub(crate) initial_cargo: PathBuf,
55 pub(crate) initial_lld: PathBuf,
56 pub(crate) initial_relative_libdir: PathBuf,
57 pub(crate) initial_sysroot: PathBuf,
58
59 pub(crate) cc: HashMap<TargetSelection, cc::Tool>,
62 pub(crate) cxx: HashMap<TargetSelection, cc::Tool>,
63 pub(crate) ar: HashMap<TargetSelection, PathBuf>,
64 pub(crate) ranlib: HashMap<TargetSelection, PathBuf>,
65 pub(crate) wasi_sdk_path: Option<PathBuf>,
66
67 pub(crate) crates: HashMap<String, Crate>,
70 pub(crate) crate_paths: HashMap<PathBuf, String>,
71 pub(crate) is_sudo: bool,
72 pub(crate) prerelease_version: Cell<Option<u32>>,
73
74 #[cfg(feature = "build-metrics")]
75 pub(crate) metrics: crate::utils::metrics::BuildMetrics,
76
77 #[cfg(feature = "tracing")]
78 pub(crate) step_graph: std::cell::RefCell<crate::utils::step_graph::StepGraph>,
79}
80
81impl Deref for Session {
82 type Target = Config;
83
84 fn deref(&self) -> &Self::Target {
85 &self.config
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
91pub(crate) enum DependencyType {
92 Host,
94 Target,
96 TargetSelfContained,
98}
99
100#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
105pub(crate) enum Mode {
106 Std,
108
109 Rustc,
111
112 Codegen,
114
115 ToolBootstrap,
127
128 ToolTarget,
139
140 ToolStd,
144
145 ToolRustcPrivate,
151}
152
153impl Mode {
154 pub(crate) fn must_support_dlopen(&self) -> bool {
155 match self {
156 Mode::Std | Mode::Codegen => true,
157 Mode::ToolBootstrap
158 | Mode::ToolRustcPrivate
159 | Mode::ToolStd
160 | Mode::ToolTarget
161 | Mode::Rustc => false,
162 }
163 }
164}
165
166pub(crate) enum RemapScheme {
170 Compiler,
172 NonCompiler,
174}
175
176#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
177pub(crate) enum CLang {
178 C,
179 Cxx,
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub(crate) enum FileType {
184 Executable,
186 NativeLibrary,
188 Script,
190 Regular,
192}
193
194impl FileType {
195 pub(crate) fn perms(self) -> u32 {
197 match self {
198 FileType::Executable | FileType::Script => 0o755,
199 FileType::Regular | FileType::NativeLibrary => 0o644,
200 }
201 }
202
203 pub(crate) fn could_have_split_debuginfo(self) -> bool {
204 match self {
205 FileType::Executable | FileType::NativeLibrary => true,
206 FileType::Script | FileType::Regular => false,
207 }
208 }
209}
210
211macro_rules! forward {
212 ($( $fn:ident( $($param:ident: $ty:ty),* ) $( -> $ret:ty)? ),+ $(,)? ) => {
213 impl Session {
214 $(
215 pub(crate) fn $fn(&self, $($param: $ty),* ) $( -> $ret)? {
216 self.config.$fn( $($param),* )
217 }
218 )+
219 }
220 }
221}
222
223forward! {
224 do_if_verbose(f: impl Fn()),
225 is_verbose() -> bool,
226 create(path: &Path, s: &str),
227 remove(f: &Path),
228 tempdir() -> PathBuf,
229 download_rustc() -> bool,
230}
231
232pub(crate) struct TargetAndStage {
235 target: TargetSelection,
236 stage: u32,
237}
238
239impl From<(TargetSelection, u32)> for TargetAndStage {
240 fn from((target, stage): (TargetSelection, u32)) -> Self {
241 Self { target, stage }
242 }
243}
244
245impl From<Compiler> for TargetAndStage {
246 fn from(compiler: Compiler) -> Self {
247 Self { target: compiler.host, stage: compiler.stage }
248 }
249}
250
251impl Session {
252 pub(crate) fn new(mut config: Config) -> Session {
257 #[cfg(unix)]
258 let is_sudo = match env::var_os("SUDO_USER") {
261 Some(_sudo_user) => {
262 let uid = unsafe { libc::getuid() };
267 uid == 0
268 }
269 None => false,
270 };
271 #[cfg(not(unix))]
272 let is_sudo = false;
273
274 let dwn_ctx = DownloadContext::from(&config);
275
276 let initial_rustc = config.external_rustc.clone().unwrap_or_else(|| {
277 download_beta_toolchain(&dwn_ctx, &config.out);
278 config
279 .out
280 .join(config.host_target)
281 .join("stage0")
282 .join("bin")
283 .join(exe("rustc", config.host_target))
284 });
285
286 let initial_rustdoc = config
287 .external_rustdoc
288 .clone()
289 .unwrap_or_else(|| initial_rustc.with_file_name(exe("rustdoc", config.host_target)));
290
291 let rustc_paths = command(&initial_rustc)
294 .args(["--print", "sysroot", "--print", "target-libdir"])
295 .run_in_dry_run()
296 .run_capture_stdout(&config)
297 .stdout();
298 let mut rustc_paths = rustc_paths.lines();
299 let initial_sysroot =
300 rustc_paths.next().map(PathBuf::from).expect("Missing sysroot from initial rustc");
301 let initial_target_libdir = rustc_paths
302 .next()
303 .map(PathBuf::from)
304 .expect("Missing target libdir from initial rustc");
305 assert!(rustc_paths.next().is_none());
306
307 let initial_cargo = config.external_cargo.clone().unwrap_or_else(|| {
308 download_beta_toolchain(&dwn_ctx, &config.out);
309 initial_sysroot.join("bin").join(exe("cargo", config.host_target))
310 });
311
312 if config.exec_ctx.dry_run() {
315 config.out = config.out.join("tmp-dry-run");
316 fs::create_dir_all(&config.out).expect("Failed to create dry-run directory");
317 }
318
319 let initial_target_dir = initial_target_libdir
320 .parent()
321 .unwrap_or_else(|| panic!("{initial_target_libdir:?} has no parent"));
322
323 let initial_lld = initial_target_dir.join("bin").join("rust-lld");
324
325 let initial_relative_libdir = if cfg!(test) {
326 PathBuf::default()
328 } else {
329 let ancestor = initial_target_dir.ancestors().nth(2).unwrap_or_else(|| {
330 panic!("Not enough ancestors for {}", initial_target_dir.display())
331 });
332
333 ancestor
334 .strip_prefix(&initial_sysroot)
335 .unwrap_or_else(|_| {
336 panic!(
337 "Couldn’t resolve the initial relative libdir from {}",
338 initial_target_dir.display()
339 )
340 })
341 .to_path_buf()
342 };
343
344 let version = std::fs::read_to_string(config.src.join("src").join("version"))
345 .expect("failed to read src/version");
346 let version = version.trim();
347
348 let mut bootstrap_out = std::env::current_exe()
349 .expect("could not determine path to running process")
350 .parent()
351 .unwrap()
352 .to_path_buf();
353 if bootstrap_out.ends_with("deps") {
356 bootstrap_out.pop();
357 }
358 if !bootstrap_out.join(exe("rustc", config.host_target)).exists() && !cfg!(test) {
359 panic!(
361 "`rustc` not found in {}, run `cargo build --bins` before `cargo run`",
362 bootstrap_out.display()
363 )
364 }
365
366 if config.rust_info.is_from_tarball() && config.description.is_none() {
367 config.description = Some("built from a source tarball".to_owned());
368 }
369
370 let mut sess = Session {
371 initial_lld,
372 initial_relative_libdir,
373 initial_rustc,
374 initial_rustdoc,
375 initial_cargo,
376 initial_sysroot,
377 fail_fast: config.cmd.fail_fast(),
378 test_target: config.cmd.test_target(),
379 verbosity: config.exec_ctx.verbosity as usize,
380 config,
381 version: version.to_string(),
382 bootstrap_out,
383
384 cc: HashMap::new(),
385 cxx: HashMap::new(),
386 ar: HashMap::new(),
387 ranlib: HashMap::new(),
388 wasi_sdk_path: env::var_os("WASI_SDK_PATH").map(PathBuf::from),
389 crates: HashMap::new(),
390 crate_paths: HashMap::new(),
391 is_sudo,
392 prerelease_version: Cell::new(None),
393
394 #[cfg(feature = "build-metrics")]
395 metrics: crate::utils::metrics::BuildMetrics::init(),
396
397 #[cfg(feature = "tracing")]
398 step_graph: std::cell::RefCell::new(crate::utils::step_graph::StepGraph::default()),
399 };
400
401 let local_version_verbose = command(&sess.initial_rustc)
404 .run_in_dry_run()
405 .args(["--version", "--verbose"])
406 .run_capture_stdout(&sess)
407 .stdout();
408 let local_release = local_version_verbose
409 .lines()
410 .filter_map(|x| x.strip_prefix("release:"))
411 .next()
412 .unwrap()
413 .trim();
414 if local_release.split('.').take(2).eq(version.split('.').take(2)) {
415 sess.do_if_verbose(|| println!("auto-detected local-rebuild {local_release}"));
416 sess.config.local_rebuild = true;
417 }
418
419 sess.do_if_verbose(|| println!("finding compilers"));
420 crate::utils::cc_detect::fill_compilers(&mut sess);
421 if !matches!(sess.config.cmd, Subcommand::Setup { .. }) {
427 sess.do_if_verbose(|| println!("running sanity check"));
428 crate::core::sanity::check(&mut sess);
429
430 let rust_submodules = ["library/backtrace"];
433 for s in rust_submodules {
434 sess.require_submodule(
435 s,
436 Some(
437 "The submodule is required for the standard library \
438 and the main Cargo workspace.",
439 ),
440 );
441 }
442 sess.update_existing_submodules();
444
445 sess.do_if_verbose(|| println!("learning about cargo"));
446 crate::core::metadata::build(&mut sess);
447 }
448
449 let build_triple = sess.out.join(sess.host_target);
451 t!(fs::create_dir_all(&build_triple));
452 let host = sess.out.join("host");
453 if host.is_symlink() {
454 #[cfg(windows)]
457 t!(fs::remove_dir(&host));
458 #[cfg(not(windows))]
459 t!(fs::remove_file(&host));
460 }
461 t!(
462 symlink_dir(&sess.config, &build_triple, &host),
463 format!("symlink_dir({} => {}) failed", host.display(), build_triple.display())
464 );
465
466 sess
467 }
468
469 #[cfg_attr(
478 feature = "tracing",
479 instrument(
480 level = "trace",
481 name = "Session::require_submodule",
482 skip_all,
483 fields(submodule = submodule),
484 )
485 )]
486 pub(crate) fn require_submodule(&self, submodule: &str, err_hint: Option<&str>) {
487 if self.rust_info().is_from_tarball() {
488 return;
489 }
490
491 if self.config.dry_run() {
492 return;
493 }
494
495 if cfg!(test) && !self.config.submodules() {
498 return;
499 }
500 self.config.update_submodule(submodule);
501 let absolute_path = self.config.src.join(submodule);
502 if !absolute_path.exists() || dir_is_empty(&absolute_path) {
503 let maybe_enable = if !self.config.submodules()
504 && self.config.rust_info.is_managed_git_subrepository()
505 {
506 "\nConsider setting `build.submodules = true` or manually initializing the submodules."
507 } else {
508 ""
509 };
510 let err_hint = err_hint.map_or_else(String::new, |e| format!("\n{e}"));
511 eprintln!(
512 "submodule {submodule} does not appear to be checked out, \
513 but it is required for this step{maybe_enable}{err_hint}"
514 );
515 helpers::exit_process(1);
516 }
517 }
518
519 pub(crate) fn update_existing_submodules(&self) {
522 if !self.config.submodules() {
525 return;
526 }
527 let output = helpers::git(Some(&self.src))
528 .args(["config", "--file"])
529 .arg(".gitmodules")
530 .args(["--get-regexp", "path"])
531 .run_capture(self)
532 .stdout();
533 std::thread::scope(|s| {
534 for line in output.lines() {
537 let submodule = line.split_once(' ').unwrap().1;
538 let config = self.config.clone();
539 s.spawn(move || {
540 Self::update_existing_submodule(&config, submodule);
541 });
542 }
543 });
544 }
545
546 pub(crate) fn update_existing_submodule(config: &Config, submodule: &str) {
548 if !config.submodules() {
550 return;
551 }
552
553 if config.git_info(false, Path::new(submodule)).is_managed_git_subrepository() {
554 config.update_submodule(submodule);
555 }
556 }
557
558 #[cfg_attr(feature = "tracing", instrument(level = "debug", name = "Session::build", skip_all))]
560 pub(crate) fn build(&mut self) {
561 trace!("setting up job management");
562 unsafe {
563 crate::utils::job::setup(self);
564 }
565
566 {
568 #[cfg(feature = "tracing")]
569 let _hardcoded_span =
570 span!(tracing::Level::DEBUG, "handling hardcoded subcommands (Format, Perf)")
571 .entered();
572
573 match &self.config.cmd {
574 Subcommand::Format { check, all } => {
575 let builder = Builder::new(self);
576 let rustfmt_path = builder.ensure(InternalRustfmt).unwrap_or_else(|| {
577 eprintln!("fmt error: `x fmt` is not supported on this channel");
578 helpers::exit_process(1);
579 });
580 return crate::core::build_steps::format::format(
581 &builder,
582 rustfmt_path,
583 *check,
584 *all,
585 &self.config.paths,
586 );
587 }
588 Subcommand::Perf(args) => {
589 return crate::core::build_steps::perf::perf(
590 &Builder::new(self),
591 args,
592 &self.config.free_args,
593 );
594 }
595 _cmd => {
596 debug!(cmd = ?_cmd, "not a hardcoded subcommand; returning to normal handling");
597 }
598 }
599
600 debug!("handling subcommand normally");
601 }
602
603 if !self.config.dry_run() {
604 #[cfg(feature = "tracing")]
605 let _real_run_span = span!(tracing::Level::DEBUG, "executing real run").entered();
606
607 {
610 #[cfg(feature = "tracing")]
611 let _sanity_check_span =
612 span!(tracing::Level::DEBUG, "(1) executing dry-run sanity-check").entered();
613 self.config.set_dry_run(DryRun::SelfCheck);
614 let builder = Builder::new(self);
615 builder.execute_cli();
616 }
617
618 {
620 #[cfg(feature = "tracing")]
621 let _actual_run_span =
622 span!(tracing::Level::DEBUG, "(2) executing actual run").entered();
623 self.config.set_dry_run(DryRun::Disabled);
624 let builder = Builder::new(self);
625 builder.execute_cli();
626 }
627 } else {
628 #[cfg(feature = "tracing")]
629 let _dry_run_span = span!(tracing::Level::DEBUG, "executing dry run").entered();
630
631 let builder = Builder::new(self);
632 builder.execute_cli();
633 }
634
635 #[cfg(feature = "tracing")]
636 debug!("checking for postponed test failures from `test --no-fail-fast`");
637
638 self.config.exec_ctx().report_failures_and_exit();
640
641 #[cfg(feature = "build-metrics")]
642 self.metrics.persist(self);
643 }
644
645 pub(crate) fn rust_info(&self) -> &GitInfo {
646 &self.config.rust_info
647 }
648
649 pub(crate) fn std_features(&self, target: TargetSelection) -> String {
652 let mut features: BTreeSet<&str> =
653 self.config.rust_std_features.iter().map(|s| s.as_str()).collect();
654
655 match self.config.llvm_libunwind(target) {
656 LlvmLibunwind::InTree => features.insert("llvm-libunwind"),
657 LlvmLibunwind::System => features.insert("system-llvm-libunwind"),
658 LlvmLibunwind::No => false,
659 };
660
661 if self.config.backtrace {
662 features.insert("backtrace");
663 }
664
665 if self.config.profiler_enabled(target) {
666 features.insert("profiler");
667 }
668
669 if target.contains("zkvm") {
671 features.insert("compiler-builtins-mem");
672 }
673
674 features.into_iter().collect::<Vec<_>>().join(" ")
675 }
676
677 pub(crate) fn rustc_features(
679 &self,
680 kind: Kind,
681 target: TargetSelection,
682 crates: &[String],
683 ) -> String {
684 let possible_features_by_crates: HashSet<_> = crates
685 .iter()
686 .flat_map(|krate| &self.crates[krate].features)
687 .map(std::ops::Deref::deref)
688 .collect();
689 let check = |feature: &str| -> bool {
690 crates.is_empty() || possible_features_by_crates.contains(feature)
691 };
692 let mut features = vec![];
693
694 if let Some(allocator_feature_name) = self.config.allocator(target).feature_name()
695 && check(allocator_feature_name)
696 {
697 features.push(allocator_feature_name);
698 }
699 if self.config.llvm_enabled(target) && check("llvm") {
700 features.push("llvm");
701 }
702 if self.config.llvm_offload {
703 features.push("llvm_offload");
704 }
705 if self.config.rust_randomize_layout && check("rustc_randomized_layouts") {
707 features.push("rustc_randomized_layouts");
708 }
709 if self.config.compile_time_deps && kind.is_check_like() {
710 features.push("check_only");
711 }
712
713 if crates.iter().any(|c| c == "rustc_transmute") {
714 features.push("rustc");
717 }
718
719 if !self.config.rust_debug_logging && check("max_level_info") {
725 features.push("max_level_info");
726 }
727
728 features.join(" ")
729 }
730
731 pub(crate) fn cargo_dir(&self, mode: Mode) -> &'static str {
734 match (mode, self.config.rust_optimize.is_release()) {
735 (Mode::Std, _) => "dist",
736 (_, true) => "release",
737 (_, false) => "debug",
738 }
739 }
740
741 pub(crate) fn tools_dir(&self, build_compiler: Compiler) -> PathBuf {
742 let out = self
743 .out
744 .join(build_compiler.host)
745 .join(format!("stage{}-tools-bin", build_compiler.stage + 1));
746 t!(fs::create_dir_all(&out));
747 out
748 }
749
750 pub(crate) fn stage_out(&self, build_compiler: Compiler, mode: Mode) -> PathBuf {
755 use std::fmt::Write;
756
757 fn bootstrap_tool() -> (Option<u32>, &'static str) {
758 (None, "bootstrap-tools")
759 }
760 fn staged_tool(build_compiler: Compiler) -> (Option<u32>, &'static str) {
761 (Some(build_compiler.stage + 1), "tools")
762 }
763
764 let (stage, suffix) = match mode {
765 Mode::Std => (Some(build_compiler.stage), "std"),
767 Mode::Rustc => (Some(build_compiler.stage + 1), "rustc"),
769 Mode::Codegen => (Some(build_compiler.stage + 1), "codegen"),
770 Mode::ToolBootstrap => bootstrap_tool(),
771 Mode::ToolStd | Mode::ToolRustcPrivate => (Some(build_compiler.stage + 1), "tools"),
772 Mode::ToolTarget => {
773 if build_compiler.stage == 0 {
776 bootstrap_tool()
777 } else {
778 staged_tool(build_compiler)
779 }
780 }
781 };
782 let path = self.out.join(build_compiler.host);
783 let mut dir_name = String::new();
784 if let Some(stage) = stage {
785 write!(dir_name, "stage{stage}-").unwrap();
786 }
787 dir_name.push_str(suffix);
788 path.join(dir_name)
789 }
790
791 pub(crate) fn cargo_out(
795 &self,
796 build_compiler: Compiler,
797 mode: Mode,
798 target: TargetSelection,
799 ) -> PathBuf {
800 self.stage_out(build_compiler, mode).join(target).join(self.cargo_dir(mode))
801 }
802
803 pub(crate) fn doc_out(&self, target: TargetSelection) -> PathBuf {
805 self.out.join(target).join("doc")
806 }
807
808 pub(crate) fn json_doc_out(&self, target: TargetSelection) -> PathBuf {
810 self.out.join(target).join("json-doc")
811 }
812
813 pub(crate) fn compiler_doc_out(&self, target: TargetSelection) -> PathBuf {
815 self.out.join(target).join("compiler-doc")
816 }
817
818 pub(crate) fn vendored_crates_path(&self) -> Option<PathBuf> {
820 if self.config.vendor { Some(self.src.join(VENDOR_DIR)) } else { None }
821 }
822
823 pub(crate) fn native_dir(&self, target: TargetSelection) -> PathBuf {
825 self.out.join(target).join("native")
826 }
827
828 pub(crate) fn add_rust_test_threads(&self, cmd: &mut BootstrapCommand) {
830 if env::var_os("RUST_TEST_THREADS").is_none() {
831 cmd.env("RUST_TEST_THREADS", self.jobs().to_string());
832 }
833 }
834
835 pub(crate) fn rustc_snapshot_libdir(&self) -> PathBuf {
837 self.rustc_snapshot_sysroot().join(libdir(self.config.host_target))
838 }
839
840 pub(crate) fn rustc_snapshot_sysroot(&self) -> &Path {
842 &self.initial_sysroot
843 }
844
845 pub(crate) fn info(&self, msg: &str) {
846 match self.config.get_dry_run() {
847 DryRun::SelfCheck => (),
848 DryRun::Disabled | DryRun::UserSelected => {
849 println!("{msg}");
850 }
851 }
852 }
853
854 #[must_use = "Groups should not be dropped until the Step finishes running"]
866 #[track_caller]
867 pub(crate) fn msg(
868 &self,
869 action: impl Into<Kind>,
870 what: impl Display,
871 mode: impl Into<Option<Mode>>,
872 target_and_stage: impl Into<TargetAndStage>,
873 target: impl Into<Option<TargetSelection>>,
874 ) -> Option<gha::Group> {
875 let target_and_stage = target_and_stage.into();
876 let action = action.into();
877 assert!(
878 action != Kind::Test,
879 "Please use `Session::msg_test` instead of `Session::msg(Kind::Test)`"
880 );
881
882 let actual_stage = match mode.into() {
883 Some(Mode::Std) => target_and_stage.stage,
885 Some(
887 Mode::Rustc
888 | Mode::Codegen
889 | Mode::ToolBootstrap
890 | Mode::ToolTarget
891 | Mode::ToolStd
892 | Mode::ToolRustcPrivate,
893 )
894 | None => target_and_stage.stage + 1,
895 };
896
897 let action = action.description();
898 let what = what.to_string();
899 let msg = |fmt| {
900 let space = if !what.is_empty() { " " } else { "" };
901 format!("{action} stage{actual_stage} {what}{space}{fmt}")
902 };
903 let msg = if let Some(target) = target.into() {
904 let build_stage = target_and_stage.stage;
905 let host = target_and_stage.target;
906 if host == target {
907 msg(format_args!("(stage{build_stage} -> stage{actual_stage}, {target})"))
908 } else {
909 msg(format_args!("(stage{build_stage}:{host} -> stage{actual_stage}:{target})"))
910 }
911 } else {
912 msg(format_args!(""))
913 };
914 self.group(&msg)
915 }
916
917 #[must_use = "Groups should not be dropped until the Step finishes running"]
923 #[track_caller]
924 pub(crate) fn msg_test(
925 &self,
926 what: impl Display,
927 target: TargetSelection,
928 stage: u32,
929 ) -> Option<gha::Group> {
930 let action = Kind::Test.description();
931 let msg = format!("{action} stage{stage} {what} ({target})");
932 self.group(&msg)
933 }
934
935 #[must_use = "Groups should not be dropped until the Step finishes running"]
939 #[track_caller]
940 pub(crate) fn msg_unstaged(
941 &self,
942 action: impl Into<Kind>,
943 what: impl Display,
944 target: TargetSelection,
945 ) -> Option<gha::Group> {
946 let action = action.into().description();
947 let msg = format!("{action} {what} for {target}");
948 self.group(&msg)
949 }
950
951 #[track_caller]
952 pub(crate) fn group(&self, msg: &str) -> Option<gha::Group> {
953 match self.config.get_dry_run() {
954 DryRun::SelfCheck => None,
955 DryRun::Disabled | DryRun::UserSelected => Some(gha::group(msg)),
956 }
957 }
958
959 pub(crate) fn jobs(&self) -> u32 {
962 self.config.jobs.unwrap_or_else(|| {
963 std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) as u32
964 })
965 }
966
967 pub(crate) fn debuginfo_map_to(&self, remap_scheme: RemapScheme) -> Option<String> {
968 if !self.config.rust_remap_debuginfo {
969 return None;
970 }
971
972 let sha = self.rust_sha().unwrap_or(&self.version);
973
974 match remap_scheme {
975 RemapScheme::Compiler => {
976 Some(format!("/rustc-dev/{sha}"))
985 }
986 RemapScheme::NonCompiler => {
987 Some(format!("/rustc/{sha}"))
989 }
990 }
991 }
992
993 pub(crate) fn cc(&self, target: TargetSelection) -> PathBuf {
995 if self.config.dry_run() {
996 return PathBuf::new();
997 }
998 self.cc[&target].path().into()
999 }
1000
1001 pub(crate) fn cc_tool(&self, target: TargetSelection) -> cc::Tool {
1003 self.cc[&target].clone()
1004 }
1005
1006 pub(crate) fn cxx_tool(&self, target: TargetSelection) -> cc::Tool {
1008 self.cxx[&target].clone()
1009 }
1010
1011 pub(crate) fn cc_handled_cflags(&self, target: TargetSelection, c: CLang) -> Vec<String> {
1014 if self.config.dry_run() {
1015 return Vec::new();
1016 }
1017 let base = match c {
1018 CLang::C => self.cc[&target].clone(),
1019 CLang::Cxx => self.cxx[&target].clone(),
1020 };
1021
1022 base.args()
1025 .iter()
1026 .map(|s| s.to_string_lossy().into_owned())
1027 .filter(|s| !s.starts_with("-O") && !s.starts_with("/O"))
1028 .collect::<Vec<String>>()
1029 }
1030
1031 pub(crate) fn cc_unhandled_cflags(&self, target: TargetSelection, c: CLang) -> Vec<String> {
1033 let mut base = Vec::new();
1034
1035 if matches!(c, CLang::Cxx) && target.contains("apple-darwin") {
1039 base.push("-stdlib=libc++".into());
1040 }
1041
1042 if &*target.triple == "i686-pc-windows-gnu" {
1046 base.push("-fno-omit-frame-pointer".into());
1047 }
1048
1049 base
1050 }
1051
1052 pub(crate) fn ar(&self, target: TargetSelection) -> Option<PathBuf> {
1054 if self.config.dry_run() {
1055 return None;
1056 }
1057 self.ar.get(&target).cloned()
1058 }
1059
1060 pub(crate) fn ranlib(&self, target: TargetSelection) -> Option<PathBuf> {
1062 if self.config.dry_run() {
1063 return None;
1064 }
1065 self.ranlib.get(&target).cloned()
1066 }
1067
1068 pub(crate) fn cxx(&self, target: TargetSelection) -> Result<PathBuf, String> {
1070 if self.config.dry_run() {
1071 return Ok(PathBuf::new());
1072 }
1073 match self.cxx.get(&target) {
1074 Some(p) => Ok(p.path().into()),
1075 None => Err(format!("target `{target}` is not configured as a host, only as a target")),
1076 }
1077 }
1078
1079 pub(crate) fn linker(&self, target: TargetSelection) -> Option<PathBuf> {
1081 if self.config.dry_run() {
1082 return Some(PathBuf::new());
1083 }
1084 if let Some(linker) = self.config.target_config.get(&target).and_then(|c| c.linker.clone())
1085 {
1086 Some(linker)
1087 } else if target.contains("vxworks") {
1088 Some(self.cxx[&target].path().into())
1091 } else if !self.config.is_host_target(target)
1092 && helpers::use_host_linker(target)
1093 && !target.is_msvc()
1094 {
1095 Some(self.cc(target))
1096 } else if self.config.bootstrap_override_lld.is_used()
1097 && self.is_lld_direct_linker(target)
1098 && self.host_target == target
1099 {
1100 match self.config.bootstrap_override_lld {
1101 BootstrapOverrideLld::SelfContained => Some(self.initial_lld.clone()),
1102 BootstrapOverrideLld::External => Some("lld".into()),
1103 BootstrapOverrideLld::None => None,
1104 }
1105 } else {
1106 None
1107 }
1108 }
1109
1110 pub(crate) fn is_lld_direct_linker(&self, target: TargetSelection) -> bool {
1113 target.is_msvc()
1114 }
1115
1116 pub(crate) fn crt_static(&self, target: TargetSelection) -> Option<bool> {
1118 if target.contains("pc-windows-msvc") {
1119 Some(true)
1120 } else {
1121 self.config.target_config.get(&target).and_then(|t| t.crt_static)
1122 }
1123 }
1124
1125 pub(crate) fn musl_root(&self, target: TargetSelection) -> Option<&Path> {
1130 let configured_root = self
1131 .config
1132 .target_config
1133 .get(&target)
1134 .and_then(|t| t.musl_root.as_ref())
1135 .or(self.config.musl_root.as_ref())
1136 .map(|p| &**p);
1137
1138 if self.config.is_host_target(target) && configured_root.is_none() {
1139 Some(Path::new("/usr"))
1140 } else {
1141 configured_root
1142 }
1143 }
1144
1145 pub(crate) fn musl_libdir(&self, target: TargetSelection) -> Option<PathBuf> {
1147 self.config
1148 .target_config
1149 .get(&target)
1150 .and_then(|t| t.musl_libdir.clone())
1151 .or_else(|| self.musl_root(target).map(|root| root.join("lib")))
1152 }
1153
1154 pub(crate) fn wasi_libdir(&self, target: TargetSelection) -> Option<PathBuf> {
1161 let configured =
1162 self.config.target_config.get(&target).and_then(|t| t.wasi_root.as_ref()).map(|p| &**p);
1163 if let Some(path) = configured {
1164 return Some(path.join("lib").join(target.to_string()));
1165 }
1166 let mut env_root = self.wasi_sdk_path.clone()?;
1167 env_root.push("share");
1168 env_root.push("wasi-sysroot");
1169 env_root.push("lib");
1170 env_root.push(target.to_string());
1171 Some(env_root)
1172 }
1173
1174 pub(crate) fn no_std(&self, target: TargetSelection) -> Option<bool> {
1176 self.config.target_config.get(&target).map(|t| t.no_std)
1177 }
1178
1179 pub(crate) fn remote_tested(&self, target: TargetSelection) -> bool {
1182 self.qemu_rootfs(target).is_some()
1183 || target.contains("android")
1184 || env::var_os("TEST_DEVICE_ADDR").is_some()
1185 }
1186
1187 pub(crate) fn runner(&self, target: TargetSelection) -> Option<String> {
1193 let configured_runner =
1194 self.config.target_config.get(&target).and_then(|t| t.runner.as_ref()).map(|p| &**p);
1195 if let Some(runner) = configured_runner {
1196 return Some(runner.to_owned());
1197 }
1198
1199 if target.starts_with("wasm") && target.contains("wasi") {
1200 self.default_wasi_runner(target)
1201 } else {
1202 None
1203 }
1204 }
1205
1206 fn default_wasi_runner(&self, target: TargetSelection) -> Option<String> {
1210 let mut finder = crate::core::sanity::Finder::new();
1211
1212 if let Some(path) = finder.maybe_have("wasmtime")
1216 && let Ok(mut path) = path.into_os_string().into_string()
1217 {
1218 path.push_str(" run -Wexceptions -C cache=n --dir .");
1219 path.push_str(" --env RUSTC_BOOTSTRAP");
1226
1227 if target.contains("wasip2") {
1228 path.push_str(" --wasi inherit-network --wasi allow-ip-name-lookup");
1229 }
1230
1231 return Some(path);
1232 }
1233
1234 None
1235 }
1236
1237 pub(crate) fn tool_enabled(&self, tool: &str) -> bool {
1242 if !self.config.extended {
1243 return false;
1244 }
1245 match &self.config.tools {
1246 Some(set) => set.contains(tool),
1247 None => true,
1248 }
1249 }
1250
1251 pub(crate) fn qemu_rootfs(&self, target: TargetSelection) -> Option<&Path> {
1257 self.config.target_config.get(&target).and_then(|t| t.qemu_rootfs.as_ref()).map(|p| &**p)
1258 }
1259
1260 pub(crate) fn force_use_stage1(&self, stage: u32, target: TargetSelection) -> bool {
1279 !self.config.full_bootstrap
1280 && !self.config.download_rustc()
1281 && stage >= 2
1282 && (self.hosts.contains(&target) || target == self.host_target)
1283 }
1284
1285 pub(crate) fn force_use_stage2(&self, stage: u32) -> bool {
1291 self.config.download_rustc() && stage >= 2
1292 }
1293
1294 pub(crate) fn release(&self, num: &str) -> String {
1300 match &self.config.channel[..] {
1301 "stable" => num.to_string(),
1302 "beta" => {
1303 if !self.config.omit_git_hash {
1304 format!("{}-beta.{}", num, self.beta_prerelease_version())
1305 } else {
1306 format!("{num}-beta")
1307 }
1308 }
1309 "nightly" => format!("{num}-nightly"),
1310 _ => format!("{num}-dev"),
1311 }
1312 }
1313
1314 fn beta_prerelease_version(&self) -> u32 {
1315 fn extract_beta_rev_from_file<P: AsRef<Path>>(version_file: P) -> Option<String> {
1316 let version = fs::read_to_string(version_file).ok()?;
1317
1318 helpers::extract_beta_rev(&version)
1319 }
1320
1321 if let Some(s) = self.prerelease_version.get() {
1322 return s;
1323 }
1324
1325 let count = extract_beta_rev_from_file(self.src.join("version")).unwrap_or_else(|| {
1329 helpers::git(Some(&self.src))
1333 .arg("rev-list")
1334 .arg("--count")
1335 .arg("--merges")
1336 .arg(format!(
1337 "refs/remotes/origin/{}..HEAD",
1338 self.config.stage0_metadata.config.nightly_branch
1339 ))
1340 .run_in_dry_run()
1341 .run_capture(self)
1342 .stdout()
1343 });
1344 let n = count.trim().parse().unwrap();
1345 self.prerelease_version.set(Some(n));
1346 n
1347 }
1348
1349 pub(crate) fn rust_release(&self) -> String {
1351 self.release(&self.version)
1352 }
1353
1354 pub(crate) fn rust_package_vers(&self) -> String {
1360 match &self.config.channel[..] {
1361 "stable" => self.version.to_string(),
1362 "beta" => "beta".to_string(),
1363 "nightly" => "nightly".to_string(),
1364 _ => format!("{}-dev", self.version),
1365 }
1366 }
1367
1368 pub(crate) fn rust_version(&self) -> String {
1374 let mut version = self.rust_info().version(self, &self.version);
1375 if let Some(ref s) = self.config.description
1376 && !s.is_empty()
1377 {
1378 version.push_str(" (");
1379 version.push_str(s);
1380 version.push(')');
1381 }
1382 version
1383 }
1384
1385 pub(crate) fn rust_sha(&self) -> Option<&str> {
1387 self.rust_info().sha()
1388 }
1389
1390 pub(crate) fn release_num(&self, package: &str) -> String {
1392 if self.config.dry_run() {
1393 return "0.0.0 (dry-run)".into();
1394 }
1395 let toml_file_name = self.src.join(format!("src/tools/{package}/Cargo.toml"));
1396 let toml = t!(fs::read_to_string(toml_file_name));
1397 for line in toml.lines() {
1398 if let Some(stripped) =
1399 line.strip_prefix("version = \"").and_then(|s| s.strip_suffix('"'))
1400 {
1401 return stripped.to_owned();
1402 }
1403 }
1404
1405 panic!("failed to find version in {package}'s Cargo.toml")
1406 }
1407
1408 pub(crate) fn unstable_features(&self) -> bool {
1411 !matches!(&self.config.channel[..], "stable" | "beta")
1412 }
1413
1414 pub(crate) fn in_tree_crates(
1418 &self,
1419 root: &str,
1420 target: Option<TargetSelection>,
1421 ) -> Vec<&Crate> {
1422 let mut ret = Vec::new();
1423 let mut list = vec![root.to_owned()];
1424 let mut visited = HashSet::new();
1425 while let Some(krate) = list.pop() {
1426 let krate = self
1427 .crates
1428 .get(&krate)
1429 .unwrap_or_else(|| panic!("metadata missing for {krate}: {:?}", self.crates));
1430 ret.push(krate);
1431 for dep in &krate.deps {
1432 if !self.crates.contains_key(dep) {
1433 continue;
1435 }
1436 if visited.insert(dep)
1442 && (dep != "profiler_builtins"
1443 || target
1444 .map(|t| self.config.profiler_enabled(t))
1445 .unwrap_or_else(|| self.config.any_profiler_enabled()))
1446 && (dep != "rustc_codegen_llvm"
1447 || self.config.hosts.iter().any(|host| self.config.llvm_enabled(*host)))
1448 {
1449 list.push(dep.clone());
1450 }
1451 }
1452 }
1453
1454 ret.sort_unstable_by(|a, b| Ord::cmp(&a.name, &b.name));
1456 ret
1457 }
1458
1459 pub(crate) fn read_stamp_file(&self, stamp: &BuildStamp) -> Vec<(PathBuf, DependencyType)> {
1460 if self.config.dry_run() {
1461 return Vec::new();
1462 }
1463
1464 if !stamp.path().exists() {
1465 eprintln!(
1466 "ERROR: Unable to find the stamp file {}, did you try to keep a nonexistent build stage?",
1467 stamp.path().display()
1468 );
1469 helpers::exit_process(1);
1470 }
1471
1472 let mut paths = Vec::new();
1473 let contents = t!(fs::read(stamp.path()), stamp.path());
1474 for part in contents.split(|b| *b == 0) {
1477 if part.is_empty() {
1478 continue;
1479 }
1480 let dependency_type = match part[0] as char {
1481 'h' => DependencyType::Host,
1482 's' => DependencyType::TargetSelfContained,
1483 't' => DependencyType::Target,
1484 _ => unreachable!(),
1485 };
1486 let path = PathBuf::from(t!(str::from_utf8(&part[1..])));
1487 paths.push((path, dependency_type));
1488 }
1489 paths
1490 }
1491
1492 #[track_caller]
1497 pub(crate) fn resolve_symlink_and_copy(&self, src: &Path, dst: &Path) {
1498 self.copy_link_internal(src, dst, true);
1499 }
1500
1501 #[track_caller]
1506 pub(crate) fn copy_link(&self, src: &Path, dst: &Path, file_type: FileType) {
1507 self.copy_link_internal(src, dst, false);
1508
1509 if file_type.could_have_split_debuginfo()
1510 && let Some(dbg_file) = split_debuginfo(src)
1511 {
1512 self.copy_link_internal(
1513 &dbg_file,
1514 &dst.with_extension(dbg_file.extension().unwrap()),
1515 false,
1516 );
1517 }
1518 }
1519
1520 #[track_caller]
1521 fn copy_link_internal(&self, src: &Path, dst: &Path, dereference_symlinks: bool) {
1522 if self.config.dry_run() {
1523 return;
1524 }
1525 if src == dst {
1526 return;
1527 }
1528
1529 #[cfg(feature = "tracing")]
1530 let _span = trace_io!("file-copy-link", ?src, ?dst);
1531
1532 if let Err(e) = fs::remove_file(dst)
1533 && cfg!(windows)
1534 && e.kind() != io::ErrorKind::NotFound
1535 {
1536 let now = t!(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH));
1539 let _ = fs::rename(dst, format!("{}-{}", dst.display(), now.as_nanos()));
1540 }
1541 let mut metadata = t!(src.symlink_metadata(), format!("src = {}", src.display()));
1542 let mut src = src.to_path_buf();
1543 if metadata.file_type().is_symlink() {
1544 if dereference_symlinks {
1545 src = t!(fs::canonicalize(src));
1546 metadata = t!(fs::metadata(&src), format!("target = {}", src.display()));
1547 } else {
1548 let link = t!(fs::read_link(src));
1549 if is_symlink_dir(&metadata) {
1550 t!(symlink_dir(&self.config, &link, dst));
1551 } else {
1552 t!(self.symlink_file(link, dst));
1553 }
1554 return;
1555 }
1556 }
1557 if let Ok(()) = fs::hard_link(&src, dst) {
1558 } else {
1561 if let Err(e) = fs::copy(&src, dst) {
1562 panic!("failed to copy `{}` to `{}`: {}", src.display(), dst.display(), e)
1563 }
1564 t!(fs::set_permissions(dst, metadata.permissions()));
1565
1566 let file_times = fs::FileTimes::new()
1569 .set_accessed(t!(metadata.accessed()))
1570 .set_modified(t!(metadata.modified()));
1571 t!(set_file_times(dst, file_times));
1572 }
1573 }
1574
1575 #[track_caller]
1579 pub(crate) fn cp_link_r(&self, src: &Path, dst: &Path) {
1580 if self.config.dry_run() {
1581 return;
1582 }
1583 for f in self.read_dir(src) {
1584 let path = f.path();
1585 let name = path.file_name().unwrap();
1586 let dst = dst.join(name);
1587 if t!(f.file_type()).is_dir() {
1588 t!(fs::create_dir_all(&dst));
1589 self.cp_link_r(&path, &dst);
1590 } else {
1591 self.copy_link(&path, &dst, FileType::Regular);
1592 }
1593 }
1594 }
1595
1596 #[track_caller]
1602 pub(crate) fn cp_link_filtered(&self, src: &Path, dst: &Path, filter: &dyn Fn(&Path) -> bool) {
1603 self.cp_link_filtered_recurse(src, dst, Path::new(""), filter)
1605 }
1606
1607 #[track_caller]
1609 fn cp_link_filtered_recurse(
1610 &self,
1611 src: &Path,
1612 dst: &Path,
1613 relative: &Path,
1614 filter: &dyn Fn(&Path) -> bool,
1615 ) {
1616 for f in self.read_dir(src) {
1617 let path = f.path();
1618 let name = path.file_name().unwrap();
1619 let dst = dst.join(name);
1620 let relative = relative.join(name);
1621 if filter(&relative) {
1623 if t!(f.file_type()).is_dir() {
1624 let _ = fs::remove_dir_all(&dst);
1625 self.create_dir(&dst);
1626 self.cp_link_filtered_recurse(&path, &dst, &relative, filter);
1627 } else {
1628 self.copy_link(&path, &dst, FileType::Regular);
1629 }
1630 }
1631 }
1632 }
1633
1634 pub(crate) fn copy_link_to_folder(&self, src: &Path, dest_folder: &Path) {
1635 let file_name = src.file_name().unwrap();
1636 let dest = dest_folder.join(file_name);
1637 self.copy_link(src, &dest, FileType::Regular);
1638 }
1639
1640 pub(crate) fn install(&self, src: &Path, dstdir: &Path, file_type: FileType) {
1641 if self.config.dry_run() {
1642 return;
1643 }
1644 let dst = dstdir.join(src.file_name().unwrap());
1645
1646 #[cfg(feature = "tracing")]
1647 let _span = trace_io!("install", ?src, ?dst);
1648
1649 t!(fs::create_dir_all(dstdir));
1650 if !src.exists() {
1651 panic!("ERROR: File \"{}\" not found!", src.display());
1652 }
1653
1654 self.copy_link_internal(src, &dst, true);
1655 chmod(&dst, file_type.perms());
1656
1657 if file_type.could_have_split_debuginfo()
1659 && let Some(dbg_file) = split_debuginfo(src)
1660 {
1661 self.install(&dbg_file, dstdir, FileType::Regular);
1662 }
1663 }
1664
1665 pub(crate) fn read(&self, path: &Path) -> String {
1666 if self.config.dry_run() {
1667 return String::new();
1668 }
1669 t!(fs::read_to_string(path))
1670 }
1671
1672 #[track_caller]
1673 pub(crate) fn create_dir(&self, dir: &Path) {
1674 if self.config.dry_run() {
1675 return;
1676 }
1677
1678 #[cfg(feature = "tracing")]
1679 let _span = trace_io!("dir-create", ?dir);
1680
1681 t!(fs::create_dir_all(dir))
1682 }
1683
1684 pub(crate) fn remove_dir(&self, dir: &Path) {
1685 if self.config.dry_run() {
1686 return;
1687 }
1688
1689 #[cfg(feature = "tracing")]
1690 let _span = trace_io!("dir-remove", ?dir);
1691
1692 t!(fs::remove_dir_all(dir))
1693 }
1694
1695 pub(crate) fn clear_dir(&self, dir: &Path) {
1698 if self.config.dry_run() {
1699 return;
1700 }
1701
1702 #[cfg(feature = "tracing")]
1703 let _span = trace_io!("dir-clear", ?dir);
1704
1705 let _ = std::fs::remove_dir_all(dir);
1706 self.create_dir(dir);
1707 }
1708
1709 pub(crate) fn read_dir(&self, dir: &Path) -> impl Iterator<Item = fs::DirEntry> {
1710 let iter = match fs::read_dir(dir) {
1711 Ok(v) => v,
1712 Err(_) if self.config.dry_run() => return vec![].into_iter(),
1713 Err(err) => panic!("could not read dir {dir:?}: {err:?}"),
1714 };
1715 iter.map(|e| t!(e)).collect::<Vec<_>>().into_iter()
1716 }
1717
1718 pub(crate) fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(
1719 &self,
1720 src: P,
1721 link: Q,
1722 ) -> io::Result<()> {
1723 #[cfg(unix)]
1724 use std::os::unix::fs::symlink as symlink_file;
1725 #[cfg(windows)]
1726 use std::os::windows::fs::symlink_file;
1727 if !self.config.dry_run() { symlink_file(src.as_ref(), link.as_ref()) } else { Ok(()) }
1728 }
1729
1730 pub(crate) fn ninja(&self) -> bool {
1733 let mut cmd_finder = crate::core::sanity::Finder::new();
1734
1735 if self.config.ninja_in_file {
1736 if cmd_finder.maybe_have("ninja-build").is_none()
1739 && cmd_finder.maybe_have("ninja").is_none()
1740 {
1741 eprintln!(
1742 "
1743Couldn't find required command: ninja (or ninja-build)
1744
1745You should install ninja as described at
1746<https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages>,
1747or set `ninja = false` in the `[llvm]` section of `bootstrap.toml`.
1748Alternatively, set `download-ci-llvm = true` in that `[llvm]` section
1749to download LLVM rather than building it.
1750"
1751 );
1752 helpers::exit_process(1);
1753 }
1754 }
1755
1756 if !self.config.ninja_in_file
1764 && self.config.host_target.is_msvc()
1765 && cmd_finder.maybe_have("ninja").is_some()
1766 {
1767 return true;
1768 }
1769
1770 self.config.ninja_in_file
1771 }
1772
1773 pub(crate) fn colored_stdout<R, F: FnOnce(&mut dyn WriteColor) -> R>(&self, f: F) -> R {
1774 self.colored_stream_inner(StandardStream::stdout, self.config.stdout_is_tty, f)
1775 }
1776
1777 #[expect(dead_code, reason = "symmetric with `colored_stdout`")]
1778 pub(crate) fn colored_stderr<R, F: FnOnce(&mut dyn WriteColor) -> R>(&self, f: F) -> R {
1779 self.colored_stream_inner(StandardStream::stderr, self.config.stderr_is_tty, f)
1780 }
1781
1782 fn colored_stream_inner<R, F, C>(&self, constructor: C, is_tty: bool, f: F) -> R
1783 where
1784 C: Fn(ColorChoice) -> StandardStream,
1785 F: FnOnce(&mut dyn WriteColor) -> R,
1786 {
1787 let choice = match self.config.color {
1788 flags::Color::Always => ColorChoice::Always,
1789 flags::Color::Never => ColorChoice::Never,
1790 flags::Color::Auto if !is_tty => ColorChoice::Never,
1791 flags::Color::Auto => ColorChoice::Auto,
1792 };
1793 let mut stream = constructor(choice);
1794 let result = f(&mut stream);
1795 stream.reset().unwrap();
1796 result
1797 }
1798
1799 #[cfg_attr(not(feature = "tracing"), expect(dead_code))]
1800 pub(crate) fn report_summary(&self, path: &Path, start_time: Instant) {
1801 self.config.exec_ctx.profiler().report_summary(path, start_time);
1802 }
1803
1804 #[cfg(feature = "tracing")]
1805 pub(crate) fn report_step_graph(self, directory: &Path) {
1806 self.step_graph.into_inner().store_to_dot_files(directory);
1807 }
1808}
1809
1810impl AsRef<ExecutionContext> for Session {
1811 fn as_ref(&self) -> &ExecutionContext {
1812 &self.config.exec_ctx
1813 }
1814}
1815
1816#[cfg(unix)]
1817fn chmod(path: &Path, perms: u32) {
1818 use std::os::unix::fs::*;
1819 t!(fs::set_permissions(path, fs::Permissions::from_mode(perms)));
1820}
1821#[cfg(windows)]
1822fn chmod(_path: &Path, _perms: u32) {}