Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
let uid = unsafe { libc::getuid() };
1//! Implementation of bootstrap, the Rust build system.2//!3//! This module, and its descendants, are the implementation of the Rust build4//! system. Most of this build system is backed by Cargo but the outer layer5//! here serves as the ability to orchestrate calling Cargo, sequencing Cargo6//! builds, building artifacts like LLVM, etc. The goals of bootstrap are:7//!8//! * To be an easily understandable, easily extensible, and maintainable build9//! system.10//! * Leverage standard tools in the Rust ecosystem to build the compiler, aka11//! crates.io and Cargo.12//! * A standard interface to build across all platforms, including MSVC13//!14//! ## Further information15//!16//! More documentation can be found in each respective module below, and you can17//! also check out the `src/bootstrap/README.md` file for more information.18#![cfg_attr(test, allow(unused))]1920use std::cell::Cell;21use std::collections::{BTreeSet, HashMap, HashSet};22use std::fmt::Display;23use std::path::{Path, PathBuf};24use std::sync::OnceLock;25use std::time::{Instant, SystemTime};26use std::{env, fs, io, str};2728use build_helper::ci::gha;29use build_helper::exit;30use cc::Tool;31use termcolor::{ColorChoice, StandardStream, WriteColor};32use utils::build_stamp::BuildStamp;33use utils::channel::GitInfo;34use utils::exec::ExecutionContext;3536use crate::core::builder;37use crate::core::builder::Kind;38use crate::core::config::{BootstrapOverrideLld, DryRun, LlvmLibunwind, TargetSelection, flags};39use crate::utils::exec::{BootstrapCommand, command};40use crate::utils::helpers::{self, dir_is_empty, exe, libdir, set_file_times, split_debuginfo};4142mod core;43mod utils;4445pub use core::builder::PathSet;46#[cfg(feature = "tracing")]47pub use core::builder::STEP_SPAN_TARGET;48pub use core::config::flags::{Flags, Subcommand};49pub use core::config::{ChangeId, Config};5051#[cfg(feature = "tracing")]52use tracing::{instrument, span};53pub use utils::change_tracker::{54 CONFIG_CHANGE_HISTORY, find_recent_config_change_ids, human_readable_changes,55};56pub use utils::helpers::{PanicTracker, symlink_dir};57#[cfg(feature = "tracing")]58pub use utils::tracing::setup_tracing;5960use crate::core::build_steps::vendor::VENDOR_DIR;6162const LLVM_TOOLS: &[&str] = &[63 "llvm-cov", // used to generate coverage report64 "llvm-nm", // used to inspect binaries; it shows symbol names, their sizes and visibility65 "llvm-objcopy", // used to transform ELFs into binary format which flashing tools consume66 "llvm-objdump", // used to disassemble programs67 "llvm-profdata", // used to inspect and merge files generated by profiles68 "llvm-readobj", // used to get information from ELFs/objects that the other tools don't provide69 "llvm-size", // used to prints the size of the linker sections of a program70 "llvm-strip", // used to discard symbols from binary files to reduce their size71 "llvm-ar", // used for creating and modifying archive files72 "llvm-as", // used to convert LLVM assembly to LLVM bitcode73 "llvm-dis", // used to disassemble LLVM bitcode74 "llvm-link", // Used to link LLVM bitcode75 "llc", // used to compile LLVM bytecode76 "opt", // used to optimize LLVM bytecode77];7879/// LLD file names for all flavors.80const LLD_FILE_NAMES: &[&str] = &["ld.lld", "ld64.lld", "lld-link", "wasm-ld"];8182/// Extra `--check-cfg` to add when building the compiler or tools83/// (Mode restriction, config name, config values (if any))84#[expect(clippy::type_complexity)] // It's fine for hard-coded list and type is explained above.85const EXTRA_CHECK_CFGS: &[(Option<Mode>, &str, Option<&[&'static str]>)] = &[86 (Some(Mode::Rustc), "bootstrap", None),87 (Some(Mode::Codegen), "bootstrap", None),88 (Some(Mode::ToolRustcPrivate), "bootstrap", None),89 (Some(Mode::ToolStd), "bootstrap", None),90 (Some(Mode::ToolRustcPrivate), "rust_analyzer", None),91 (Some(Mode::ToolStd), "rust_analyzer", None),92 // Any library specific cfgs like `target_os`, `target_arch` should be put in93 // priority the `[lints.rust.unexpected_cfgs.check-cfg]` table94 // in the appropriate `library/{std,alloc,core}/Cargo.toml`95];9697/// A structure representing a Rust compiler.98///99/// Each compiler has a `stage` that it is associated with and a `host` that100/// corresponds to the platform the compiler runs on. This structure is used as101/// a parameter to many methods below.102#[derive(Eq, PartialOrd, Ord, Clone, Copy, Debug)]103pub struct Compiler {104 stage: u32,105 host: TargetSelection,106 /// Indicates whether the compiler was forced to use a specific stage.107 /// This field is ignored in `Hash` and `PartialEq` implementations as only the `stage`108 /// and `host` fields are relevant for those.109 forced_compiler: bool,110}111112impl std::hash::Hash for Compiler {113 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {114 self.stage.hash(state);115 self.host.hash(state);116 }117}118119impl PartialEq for Compiler {120 fn eq(&self, other: &Self) -> bool {121 self.stage == other.stage && self.host == other.host122 }123}124125/// Represents a codegen backend.126#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]127pub enum CodegenBackendKind {128 #[default]129 Llvm,130 Cranelift,131 Gcc,132 Custom(String),133}134135impl CodegenBackendKind {136 /// Name of the codegen backend, as identified in the `compiler` directory137 /// (`rustc_codegen_<name>`).138 pub fn name(&self) -> &str {139 match self {140 CodegenBackendKind::Llvm => "llvm",141 CodegenBackendKind::Cranelift => "cranelift",142 CodegenBackendKind::Gcc => "gcc",143 CodegenBackendKind::Custom(name) => name,144 }145 }146147 /// Name of the codegen backend's crate, e.g. `rustc_codegen_cranelift`.148 pub fn crate_name(&self) -> String {149 format!("rustc_codegen_{}", self.name())150 }151152 pub fn is_llvm(&self) -> bool {153 matches!(self, Self::Llvm)154 }155156 pub fn is_cranelift(&self) -> bool {157 matches!(self, Self::Cranelift)158 }159160 pub fn is_gcc(&self) -> bool {161 matches!(self, Self::Gcc)162 }163}164165impl std::str::FromStr for CodegenBackendKind {166 type Err = &'static str;167168 fn from_str(s: &str) -> Result<Self, Self::Err> {169 match s.to_lowercase().as_str() {170 "" => Err("Invalid empty backend name"),171 "gcc" => Ok(Self::Gcc),172 "llvm" => Ok(Self::Llvm),173 "cranelift" => Ok(Self::Cranelift),174 _ => Ok(Self::Custom(s.to_string())),175 }176 }177}178179#[derive(PartialEq, Eq, Copy, Clone, Debug)]180pub enum TestTarget {181 /// Run unit, integration and doc tests (default).182 Default,183 /// Run unit, integration, doc tests, examples, bins, benchmarks (no doc tests).184 AllTargets,185 /// Only run doc tests.186 DocOnly,187 /// Only run unit and integration tests.188 Tests,189}190191impl TestTarget {192 fn runs_doctests(&self) -> bool {193 matches!(self, TestTarget::DocOnly | TestTarget::Default)194 }195}196197pub enum GitRepo {198 Rustc,199 Llvm,200}201202/// Global configuration for the build system.203///204/// This structure transitively contains all configuration for the build system.205/// All filesystem-encoded configuration is in `config`, all flags are in206/// `flags`, and then parsed or probed information is listed in the keys below.207///208/// This structure is a parameter of almost all methods in the build system,209/// although most functions are implemented as free functions rather than210/// methods specifically on this structure itself (to make it easier to211/// organize).212pub struct Build {213 /// User-specified configuration from `bootstrap.toml`.214 config: Config,215216 // Version information217 version: String,218219 // Properties derived from the above configuration220 src: PathBuf,221 out: PathBuf,222 bootstrap_out: PathBuf,223 cargo_info: GitInfo,224 rust_analyzer_info: GitInfo,225 clippy_info: GitInfo,226 miri_info: GitInfo,227 rustfmt_info: GitInfo,228 enzyme_info: GitInfo,229 in_tree_llvm_info: GitInfo,230 in_tree_gcc_info: GitInfo,231 local_rebuild: bool,232 fail_fast: bool,233 test_target: TestTarget,234 verbosity: usize,235236 /// Build triple for the pre-compiled snapshot compiler.237 host_target: TargetSelection,238 /// Which triples to produce a compiler toolchain for.239 hosts: Vec<TargetSelection>,240 /// Which triples to build libraries (core/alloc/std/test/proc_macro) for.241 targets: Vec<TargetSelection>,242243 initial_rustc: PathBuf,244 initial_rustdoc: PathBuf,245 initial_cargo: PathBuf,246 initial_lld: PathBuf,247 initial_relative_libdir: PathBuf,248 initial_sysroot: PathBuf,249250 // Runtime state filled in later on251 // C/C++ compilers and archiver for all targets252 cc: HashMap<TargetSelection, cc::Tool>,253 cxx: HashMap<TargetSelection, cc::Tool>,254 ar: HashMap<TargetSelection, PathBuf>,255 ranlib: HashMap<TargetSelection, PathBuf>,256 wasi_sdk_path: Option<PathBuf>,257258 // Miscellaneous259 // allow bidirectional lookups: both name -> path and path -> name260 crates: HashMap<String, Crate>,261 crate_paths: HashMap<PathBuf, String>,262 is_sudo: bool,263 prerelease_version: Cell<Option<u32>>,264265 #[cfg(feature = "build-metrics")]266 metrics: crate::utils::metrics::BuildMetrics,267268 #[cfg(feature = "tracing")]269 step_graph: std::cell::RefCell<crate::utils::step_graph::StepGraph>,270}271272#[derive(Debug, Clone)]273struct Crate {274 name: String,275 deps: HashSet<String>,276 path: PathBuf,277 features: Vec<String>,278}279280impl Crate {281 fn local_path(&self, build: &Build) -> PathBuf {282 self.path.strip_prefix(&build.config.src).unwrap().into()283 }284}285286/// When building Rust various objects are handled differently.287#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]288pub enum DependencyType {289 /// Libraries originating from proc-macros.290 Host,291 /// Typical Rust libraries.292 Target,293 /// Non Rust libraries and objects shipped to ease usage of certain targets.294 TargetSelfContained,295}296297/// The various "modes" of invoking Cargo.298///299/// These entries currently correspond to the various output directories of the300/// build system, with each mod generating output in a different directory.301#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]302pub enum Mode {303 /// Build the standard library, placing output in the "stageN-std" directory.304 Std,305306 /// Build librustc, and compiler libraries, placing output in the "stageN-rustc" directory.307 Rustc,308309 /// Build a codegen backend for rustc, placing the output in the "stageN-codegen" directory.310 Codegen,311312 /// Build a tool, placing output in the "bootstrap-tools"313 /// directory. This is for miscellaneous sets of tools that extend314 /// bootstrap.315 ///316 /// These tools are intended to be only executed on the host system that317 /// invokes bootstrap, and they thus cannot be cross-compiled.318 ///319 /// They are always built using the stage0 compiler, and they320 /// can be compiled with stable Rust.321 ///322 /// These tools also essentially do not participate in staging.323 ToolBootstrap,324325 /// Build a cross-compilable helper tool. These tools do not depend on unstable features or326 /// compiler internals, but they might be cross-compilable (so we cannot build them using the327 /// stage0 compiler, unlike `ToolBootstrap`).328 ///329 /// Some of these tools are also shipped in our `dist` archives.330 /// While we could compile them using the stage0 compiler when not cross-compiling, we instead331 /// use the in-tree compiler (and std) to build them, so that we can ship e.g. std security332 /// fixes and avoid depending fully on stage0 for the artifacts that we ship.333 ///334 /// This mode is used e.g. for linkers and linker tools invoked by rustc on its host target.335 ToolTarget,336337 /// Build a tool which uses the locally built std, placing output in the338 /// "stageN-tools" directory. Its usage is quite rare; historically it was339 /// needed by compiletest, but now it is mainly used by `test-float-parse`.340 ToolStd,341342 /// Build a tool which uses the `rustc_private` mechanism, and thus343 /// the locally built rustc rlib artifacts,344 /// placing the output in the "stageN-tools" directory. This is used for345 /// everything that links to rustc as a library, such as rustdoc, clippy,346 /// rustfmt, miri, etc.347 ToolRustcPrivate,348}349350impl Mode {351 pub fn must_support_dlopen(&self) -> bool {352 match self {353 Mode::Std | Mode::Codegen => true,354 Mode::ToolBootstrap355 | Mode::ToolRustcPrivate356 | Mode::ToolStd357 | Mode::ToolTarget358 | Mode::Rustc => false,359 }360 }361}362363/// When `rust.rust_remap_debuginfo` is requested, the compiler needs to know how to364/// opportunistically unremap compiler vs non-compiler sources. We use two schemes,365/// [`RemapScheme::Compiler`] and [`RemapScheme::NonCompiler`].366pub enum RemapScheme {367 /// The [`RemapScheme::Compiler`] scheme will remap to `/rustc-dev/{hash}`.368 Compiler,369 /// The [`RemapScheme::NonCompiler`] scheme will remap to `/rustc/{hash}`.370 NonCompiler,371}372373#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]374pub enum CLang {375 C,376 Cxx,377}378379#[derive(Debug, Clone, Copy, PartialEq, Eq)]380pub enum FileType {381 /// An executable binary file (like a `.exe`).382 Executable,383 /// A native, binary library file (like a `.so`, `.dll`, `.a`, `.lib` or `.o`).384 NativeLibrary,385 /// An executable (non-binary) script file (like a `.py` or `.sh`).386 Script,387 /// Any other regular file that is non-executable.388 Regular,389}390391impl FileType {392 /// Get Unix permissions appropriate for this file type.393 pub fn perms(self) -> u32 {394 match self {395 FileType::Executable | FileType::Script => 0o755,396 FileType::Regular | FileType::NativeLibrary => 0o644,397 }398 }399400 pub fn could_have_split_debuginfo(self) -> bool {401 match self {402 FileType::Executable | FileType::NativeLibrary => true,403 FileType::Script | FileType::Regular => false,404 }405 }406}407408macro_rules! forward {409 ( $( $fn:ident( $($param:ident: $ty:ty),* ) $( -> $ret:ty)? ),+ $(,)? ) => {410 impl Build {411 $( fn $fn(&self, $($param: $ty),* ) $( -> $ret)? {412 self.config.$fn( $($param),* )413 } )+414 }415 }416}417418forward! {419 do_if_verbose(f: impl Fn()),420 is_verbose() -> bool,421 create(path: &Path, s: &str),422 remove(f: &Path),423 tempdir() -> PathBuf,424 llvm_link_shared() -> bool,425 download_rustc() -> bool,426}427428/// An alternative way of specifying what target and stage is involved in some bootstrap activity.429/// Ideally using a `Compiler` directly should be preferred.430struct TargetAndStage {431 target: TargetSelection,432 stage: u32,433}434435impl From<(TargetSelection, u32)> for TargetAndStage {436 fn from((target, stage): (TargetSelection, u32)) -> Self {437 Self { target, stage }438 }439}440441impl From<Compiler> for TargetAndStage {442 fn from(compiler: Compiler) -> Self {443 Self { target: compiler.host, stage: compiler.stage }444 }445}446447impl Build {448 /// Creates a new set of build configuration from the `flags` on the command449 /// line and the filesystem `config`.450 ///451 /// By default all build output will be placed in the current directory.452 pub fn new(mut config: Config) -> Build {453 let src = config.src.clone();454 let out = config.out.clone();455456 #[cfg(unix)]457 // keep this consistent with the equivalent check in x.py:458 // https://github.com/rust-lang/rust/blob/a8a33cf27166d3eabaffc58ed3799e054af3b0c6/src/bootstrap/bootstrap.py#L796-L797459 let is_sudo = match env::var_os("SUDO_USER") {460 Some(_sudo_user) => {461 // SAFETY: getuid() system call is always successful and no return value is reserved462 // to indicate an error.463 //464 // For more context, see https://man7.org/linux/man-pages/man2/geteuid.2.html465 let uid = unsafe { libc::getuid() };466 uid == 0467 }468 None => false,469 };470 #[cfg(not(unix))]471 let is_sudo = false;472473 let rust_info = config.rust_info.clone();474 let cargo_info = config.cargo_info.clone();475 let rust_analyzer_info = config.rust_analyzer_info.clone();476 let clippy_info = config.clippy_info.clone();477 let miri_info = config.miri_info.clone();478 let rustfmt_info = config.rustfmt_info.clone();479 let enzyme_info = config.enzyme_info.clone();480 let in_tree_llvm_info = config.in_tree_llvm_info.clone();481 let in_tree_gcc_info = config.in_tree_gcc_info.clone();482483 let initial_target_libdir = command(&config.initial_rustc)484 .run_in_dry_run()485 .args(["--print", "target-libdir"])486 .run_capture_stdout(&config)487 .stdout()488 .trim()489 .to_owned();490491 let initial_target_dir = Path::new(&initial_target_libdir)492 .parent()493 .unwrap_or_else(|| panic!("{initial_target_libdir} has no parent"));494495 let initial_lld = initial_target_dir.join("bin").join("rust-lld");496497 let initial_relative_libdir = if cfg!(test) {498 // On tests, bootstrap uses the shim rustc, not the one from the stage0 toolchain.499 PathBuf::default()500 } else {501 let ancestor = initial_target_dir.ancestors().nth(2).unwrap_or_else(|| {502 panic!("Not enough ancestors for {}", initial_target_dir.display())503 });504505 ancestor506 .strip_prefix(&config.initial_sysroot)507 .unwrap_or_else(|_| {508 panic!(509 "Couldn’t resolve the initial relative libdir from {}",510 initial_target_dir.display()511 )512 })513 .to_path_buf()514 };515516 let version = std::fs::read_to_string(src.join("src").join("version"))517 .expect("failed to read src/version");518 let version = version.trim();519520 let mut bootstrap_out = std::env::current_exe()521 .expect("could not determine path to running process")522 .parent()523 .unwrap()524 .to_path_buf();525 // Since bootstrap is hardlink to deps/bootstrap-*, Solaris can sometimes give526 // path with deps/ which is bad and needs to be avoided.527 if bootstrap_out.ends_with("deps") {528 bootstrap_out.pop();529 }530 if !bootstrap_out.join(exe("rustc", config.host_target)).exists() && !cfg!(test) {531 // this restriction can be lifted whenever https://github.com/rust-lang/rfcs/pull/3028 is implemented532 panic!(533 "`rustc` not found in {}, run `cargo build --bins` before `cargo run`",534 bootstrap_out.display()535 )536 }537538 if rust_info.is_from_tarball() && config.description.is_none() {539 config.description = Some("built from a source tarball".to_owned());540 }541542 let mut build = Build {543 initial_lld,544 initial_relative_libdir,545 initial_rustc: config.initial_rustc.clone(),546 initial_rustdoc: config.initial_rustdoc.clone(),547 initial_cargo: config.initial_cargo.clone(),548 initial_sysroot: config.initial_sysroot.clone(),549 local_rebuild: config.local_rebuild,550 fail_fast: config.cmd.fail_fast(),551 test_target: config.cmd.test_target(),552 verbosity: config.exec_ctx.verbosity as usize,553554 host_target: config.host_target,555 hosts: config.hosts.clone(),556 targets: config.targets.clone(),557558 config,559 version: version.to_string(),560 src,561 out,562 bootstrap_out,563564 cargo_info,565 rust_analyzer_info,566 clippy_info,567 miri_info,568 rustfmt_info,569 enzyme_info,570 in_tree_llvm_info,571 in_tree_gcc_info,572 cc: HashMap::new(),573 cxx: HashMap::new(),574 ar: HashMap::new(),575 ranlib: HashMap::new(),576 wasi_sdk_path: env::var_os("WASI_SDK_PATH").map(PathBuf::from),577 crates: HashMap::new(),578 crate_paths: HashMap::new(),579 is_sudo,580 prerelease_version: Cell::new(None),581582 #[cfg(feature = "build-metrics")]583 metrics: crate::utils::metrics::BuildMetrics::init(),584585 #[cfg(feature = "tracing")]586 step_graph: std::cell::RefCell::new(crate::utils::step_graph::StepGraph::default()),587 };588589 // If local-rust is the same major.minor as the current version, then force a590 // local-rebuild591 let local_version_verbose = command(&build.initial_rustc)592 .run_in_dry_run()593 .args(["--version", "--verbose"])594 .run_capture_stdout(&build)595 .stdout();596 let local_release = local_version_verbose597 .lines()598 .filter_map(|x| x.strip_prefix("release:"))599 .next()600 .unwrap()601 .trim();602 if local_release.split('.').take(2).eq(version.split('.').take(2)) {603 build.do_if_verbose(|| println!("auto-detected local-rebuild {local_release}"));604 build.local_rebuild = true;605 }606607 build.do_if_verbose(|| println!("finding compilers"));608 utils::cc_detect::fill_compilers(&mut build);609 // When running `setup`, the profile is about to change, so any requirements we have now may610 // be different on the next invocation. Don't check for them until the next time x.py is611 // run. This is ok because `setup` never runs any build commands, so it won't fail if commands are missing.612 //613 // Similarly, for `setup` we don't actually need submodules or cargo metadata.614 if !matches!(build.config.cmd, Subcommand::Setup { .. }) {615 build.do_if_verbose(|| println!("running sanity check"));616 crate::core::sanity::check(&mut build);617618 // Make sure we update these before gathering metadata so we don't get an error about missing619 // Cargo.toml files.620 let rust_submodules = ["library/backtrace"];621 for s in rust_submodules {622 build.require_submodule(623 s,624 Some(625 "The submodule is required for the standard library \626 and the main Cargo workspace.",627 ),628 );629 }630 // Now, update all existing submodules.631 build.update_existing_submodules();632633 build.do_if_verbose(|| println!("learning about cargo"));634 crate::core::metadata::build(&mut build);635 }636637 // Create symbolic link to use host sysroot from a consistent path (e.g., in the rust-analyzer config file).638 let build_triple = build.out.join(build.host_target);639 t!(fs::create_dir_all(&build_triple));640 let host = build.out.join("host");641 if host.is_symlink() {642 // Left over from a previous build; overwrite it.643 // This matters if `build.build` has changed between invocations.644 #[cfg(windows)]645 t!(fs::remove_dir(&host));646 #[cfg(not(windows))]647 t!(fs::remove_file(&host));648 }649 t!(650 symlink_dir(&build.config, &build_triple, &host),651 format!("symlink_dir({} => {}) failed", host.display(), build_triple.display())652 );653654 build655 }656657 /// Updates a submodule, and exits with a failure if submodule management658 /// is disabled and the submodule does not exist.659 ///660 /// The given submodule name should be its path relative to the root of661 /// the main repository.662 ///663 /// The given `err_hint` will be shown to the user if the submodule is not664 /// checked out and submodule management is disabled.665 #[cfg_attr(666 feature = "tracing",667 instrument(668 level = "trace",669 name = "Build::require_submodule",670 skip_all,671 fields(submodule = submodule),672 ),673 )]674 pub fn require_submodule(&self, submodule: &str, err_hint: Option<&str>) {675 if self.rust_info().is_from_tarball() {676 return;677 }678679 if self.config.dry_run() {680 return;681 }682683 // When testing bootstrap itself, it is much faster to ignore684 // submodules. Almost all Steps work fine without their submodules.685 if cfg!(test) && !self.config.submodules() {686 return;687 }688 self.config.update_submodule(submodule);689 let absolute_path = self.config.src.join(submodule);690 if !absolute_path.exists() || dir_is_empty(&absolute_path) {691 let maybe_enable = if !self.config.submodules()692 && self.config.rust_info.is_managed_git_subrepository()693 {694 "\nConsider setting `build.submodules = true` or manually initializing the submodules."695 } else {696 ""697 };698 let err_hint = err_hint.map_or_else(String::new, |e| format!("\n{e}"));699 eprintln!(700 "submodule {submodule} does not appear to be checked out, \701 but it is required for this step{maybe_enable}{err_hint}"702 );703 exit!(1);704 }705 }706707 /// If any submodule has been initialized already, sync it unconditionally.708 /// This avoids contributors checking in a submodule change by accident.709 fn update_existing_submodules(&self) {710 // Avoid running git when there isn't a git checkout, or the user has711 // explicitly disabled submodules in `bootstrap.toml`.712 if !self.config.submodules() {713 return;714 }715 let output = helpers::git(Some(&self.src))716 .args(["config", "--file"])717 .arg(".gitmodules")718 .args(["--get-regexp", "path"])719 .run_capture(self)720 .stdout();721 std::thread::scope(|s| {722 // Look for `submodule.$name.path = $path`723 // Sample output: `submodule.src/rust-installer.path src/tools/rust-installer`724 for line in output.lines() {725 let submodule = line.split_once(' ').unwrap().1;726 let config = self.config.clone();727 s.spawn(move || {728 Self::update_existing_submodule(&config, submodule);729 });730 }731 });732 }733734 /// Updates the given submodule only if it's initialized already; nothing happens otherwise.735 pub fn update_existing_submodule(config: &Config, submodule: &str) {736 // Avoid running git when there isn't a git checkout.737 if !config.submodules() {738 return;739 }740741 if config.git_info(false, Path::new(submodule)).is_managed_git_subrepository() {742 config.update_submodule(submodule);743 }744 }745746 /// Executes the entire build, as configured by the flags and configuration.747 #[cfg_attr(feature = "tracing", instrument(level = "debug", name = "Build::build", skip_all))]748 pub fn build(&mut self) {749 trace!("setting up job management");750 unsafe {751 crate::utils::job::setup(self);752 }753754 // Handle hard-coded subcommands.755 {756 #[cfg(feature = "tracing")]757 let _hardcoded_span =758 span!(tracing::Level::DEBUG, "handling hardcoded subcommands (Format, Perf)")759 .entered();760761 match &self.config.cmd {762 Subcommand::Format { check, all } => {763 return core::build_steps::format::format(764 &builder::Builder::new(self),765 *check,766 *all,767 &self.config.paths,768 );769 }770 Subcommand::Perf(args) => {771 return core::build_steps::perf::perf(&builder::Builder::new(self), args);772 }773 _cmd => {774 debug!(cmd = ?_cmd, "not a hardcoded subcommand; returning to normal handling");775 }776 }777778 debug!("handling subcommand normally");779 }780781 if !self.config.dry_run() {782 #[cfg(feature = "tracing")]783 let _real_run_span = span!(tracing::Level::DEBUG, "executing real run").entered();784785 // We first do a dry-run. This is a sanity-check to ensure that786 // steps don't do anything expensive in the dry-run.787 {788 #[cfg(feature = "tracing")]789 let _sanity_check_span =790 span!(tracing::Level::DEBUG, "(1) executing dry-run sanity-check").entered();791 self.config.set_dry_run(DryRun::SelfCheck);792 let builder = builder::Builder::new(self);793 builder.execute_cli();794 }795796 // Actual run.797 {798 #[cfg(feature = "tracing")]799 let _actual_run_span =800 span!(tracing::Level::DEBUG, "(2) executing actual run").entered();801 self.config.set_dry_run(DryRun::Disabled);802 let builder = builder::Builder::new(self);803 builder.execute_cli();804 }805 } else {806 #[cfg(feature = "tracing")]807 let _dry_run_span = span!(tracing::Level::DEBUG, "executing dry run").entered();808809 let builder = builder::Builder::new(self);810 builder.execute_cli();811 }812813 #[cfg(feature = "tracing")]814 debug!("checking for postponed test failures from `test --no-fail-fast`");815816 // Check for postponed failures from `test --no-fail-fast`.817 self.config.exec_ctx().report_failures_and_exit();818819 #[cfg(feature = "build-metrics")]820 self.metrics.persist(self);821 }822823 fn rust_info(&self) -> &GitInfo {824 &self.config.rust_info825 }826827 /// Gets the space-separated set of activated features for the standard library.828 /// This can be configured with the `std-features` key in bootstrap.toml.829 fn std_features(&self, target: TargetSelection) -> String {830 let mut features: BTreeSet<&str> =831 self.config.rust_std_features.iter().map(|s| s.as_str()).collect();832833 match self.config.llvm_libunwind(target) {834 LlvmLibunwind::InTree => features.insert("llvm-libunwind"),835 LlvmLibunwind::System => features.insert("system-llvm-libunwind"),836 LlvmLibunwind::No => false,837 };838839 if self.config.backtrace {840 features.insert("backtrace");841 }842843 if self.config.profiler_enabled(target) {844 features.insert("profiler");845 }846847 // If zkvm target, generate memcpy, etc.848 if target.contains("zkvm") {849 features.insert("compiler-builtins-mem");850 }851852 if self.config.llvm_enzyme {853 features.insert("llvm_enzyme");854 }855856 features.into_iter().collect::<Vec<_>>().join(" ")857 }858859 /// Gets the space-separated set of activated features for the compiler.860 fn rustc_features(&self, kind: Kind, target: TargetSelection, crates: &[String]) -> String {861 let possible_features_by_crates: HashSet<_> = crates862 .iter()863 .flat_map(|krate| &self.crates[krate].features)864 .map(std::ops::Deref::deref)865 .collect();866 let check = |feature: &str| -> bool {867 crates.is_empty() || possible_features_by_crates.contains(feature)868 };869 let mut features = vec![];870 if self.config.jemalloc(target) && check("jemalloc") {871 features.push("jemalloc");872 }873 if (self.config.llvm_enabled(target) || kind == Kind::Check) && check("llvm") {874 features.push("llvm");875 }876 if self.config.llvm_enzyme {877 features.push("llvm_enzyme");878 }879 if self.config.llvm_offload {880 features.push("llvm_offload");881 }882 // keep in sync with `bootstrap/compile.rs:rustc_cargo_env`883 if self.config.rust_randomize_layout && check("rustc_randomized_layouts") {884 features.push("rustc_randomized_layouts");885 }886 if self.config.compile_time_deps && kind == Kind::Check {887 features.push("check_only");888 }889890 if crates.iter().any(|c| c == "rustc_transmute") {891 // for `x test rustc_transmute`, this feature isn't enabled automatically by a892 // dependent crate.893 features.push("rustc");894 }895896 // If debug logging is on, then we want the default for tracing:897 // https://github.com/tokio-rs/tracing/blob/3dd5c03d907afdf2c39444a29931833335171554/tracing/src/level_filters.rs#L26898 // which is everything (including debug/trace/etc.)899 // if its unset, if debug_assertions is on, then debug_logging will also be on900 // as well as tracing *ignoring* this feature when debug_assertions is on901 if !self.config.rust_debug_logging && check("max_level_info") {902 features.push("max_level_info");903 }904905 features.join(" ")906 }907908 /// Component directory that Cargo will produce output into (e.g.909 /// release/debug)910 fn cargo_dir(&self, mode: Mode) -> &'static str {911 match (mode, self.config.rust_optimize.is_release()) {912 (Mode::Std, _) => "dist",913 (_, true) => "release",914 (_, false) => "debug",915 }916 }917918 fn tools_dir(&self, build_compiler: Compiler) -> PathBuf {919 let out = self920 .out921 .join(build_compiler.host)922 .join(format!("stage{}-tools-bin", build_compiler.stage + 1));923 t!(fs::create_dir_all(&out));924 out925 }926927 /// Returns the root directory for all output generated in a particular928 /// stage when being built with a particular build compiler.929 ///930 /// The mode indicates what the root directory is for.931 fn stage_out(&self, build_compiler: Compiler, mode: Mode) -> PathBuf {932 use std::fmt::Write;933934 fn bootstrap_tool() -> (Option<u32>, &'static str) {935 (None, "bootstrap-tools")936 }937 fn staged_tool(build_compiler: Compiler) -> (Option<u32>, &'static str) {938 (Some(build_compiler.stage + 1), "tools")939 }940941 let (stage, suffix) = match mode {942 // Std is special, stage N std is built with stage N rustc943 Mode::Std => (Some(build_compiler.stage), "std"),944 // The rest of things are built with stage N-1 rustc945 Mode::Rustc => (Some(build_compiler.stage + 1), "rustc"),946 Mode::Codegen => (Some(build_compiler.stage + 1), "codegen"),947 Mode::ToolBootstrap => bootstrap_tool(),948 Mode::ToolStd | Mode::ToolRustcPrivate => (Some(build_compiler.stage + 1), "tools"),949 Mode::ToolTarget => {950 // If we're not cross-compiling (the common case), share the target directory with951 // bootstrap tools to reuse the build cache.952 if build_compiler.stage == 0 {953 bootstrap_tool()954 } else {955 staged_tool(build_compiler)956 }957 }958 };959 let path = self.out.join(build_compiler.host);960 let mut dir_name = String::new();961 if let Some(stage) = stage {962 write!(dir_name, "stage{stage}-").unwrap();963 }964 dir_name.push_str(suffix);965 path.join(dir_name)966 }967968 /// Returns the root output directory for all Cargo output in a given stage,969 /// running a particular compiler, whether or not we're building the970 /// standard library, and targeting the specified architecture.971 fn cargo_out(&self, build_compiler: Compiler, mode: Mode, target: TargetSelection) -> PathBuf {972 self.stage_out(build_compiler, mode).join(target).join(self.cargo_dir(mode))973 }974975 /// Root output directory of LLVM for `target`976 ///977 /// Note that if LLVM is configured externally then the directory returned978 /// will likely be empty.979 fn llvm_out(&self, target: TargetSelection) -> PathBuf {980 if self.config.llvm_from_ci && self.config.is_host_target(target) {981 self.config.ci_llvm_root()982 } else {983 self.out.join(target).join("llvm")984 }985 }986987 fn enzyme_out(&self, target: TargetSelection) -> PathBuf {988 self.out.join(&*target.triple).join("enzyme")989 }990991 fn offload_out(&self, target: TargetSelection) -> PathBuf {992 self.out.join(&*target.triple).join("offload")993 }994995 fn lld_out(&self, target: TargetSelection) -> PathBuf {996 self.out.join(target).join("lld")997 }998999 /// Output directory for all documentation for a target1000 fn doc_out(&self, target: TargetSelection) -> PathBuf {1001 self.out.join(target).join("doc")1002 }10031004 /// Output directory for all JSON-formatted documentation for a target1005 fn json_doc_out(&self, target: TargetSelection) -> PathBuf {1006 self.out.join(target).join("json-doc")1007 }10081009 fn test_out(&self, target: TargetSelection) -> PathBuf {1010 self.out.join(target).join("test")1011 }10121013 /// Output directory for all documentation for a target1014 fn compiler_doc_out(&self, target: TargetSelection) -> PathBuf {1015 self.out.join(target).join("compiler-doc")1016 }10171018 /// Output directory for some generated md crate documentation for a target (temporary)1019 fn md_doc_out(&self, target: TargetSelection) -> PathBuf {1020 self.out.join(target).join("md-doc")1021 }10221023 /// Path to the vendored Rust crates.1024 fn vendored_crates_path(&self) -> Option<PathBuf> {1025 if self.config.vendor { Some(self.src.join(VENDOR_DIR)) } else { None }1026 }10271028 /// Returns the path to `FileCheck` binary for the specified target1029 fn llvm_filecheck(&self, target: TargetSelection) -> PathBuf {1030 let target_config = self.config.target_config.get(&target);1031 if let Some(s) = target_config.and_then(|c| c.llvm_filecheck.as_ref()) {1032 s.to_path_buf()1033 } else if let Some(s) = target_config.and_then(|c| c.llvm_config.as_ref()) {1034 let llvm_bindir = command(s).arg("--bindir").run_capture_stdout(self).stdout();1035 let filecheck = Path::new(llvm_bindir.trim()).join(exe("FileCheck", target));1036 if filecheck.exists() {1037 filecheck1038 } else {1039 // On Fedora the system LLVM installs FileCheck in the1040 // llvm subdirectory of the libdir.1041 let llvm_libdir = command(s).arg("--libdir").run_capture_stdout(self).stdout();1042 let lib_filecheck =1043 Path::new(llvm_libdir.trim()).join("llvm").join(exe("FileCheck", target));1044 if lib_filecheck.exists() {1045 lib_filecheck1046 } else {1047 // Return the most normal file name, even though1048 // it doesn't exist, so that any error message1049 // refers to that.1050 filecheck1051 }1052 }1053 } else {1054 let base = self.llvm_out(target).join("build");1055 let base = if !self.ninja() && target.is_msvc() {1056 if self.config.llvm_optimize {1057 if self.config.llvm_release_debuginfo {1058 base.join("RelWithDebInfo")1059 } else {1060 base.join("Release")1061 }1062 } else {1063 base.join("Debug")1064 }1065 } else {1066 base1067 };1068 base.join("bin").join(exe("FileCheck", target))1069 }1070 }10711072 /// Directory for libraries built from C/C++ code and shared between stages.1073 fn native_dir(&self, target: TargetSelection) -> PathBuf {1074 self.out.join(target).join("native")1075 }10761077 /// Root output directory for rust_test_helpers library compiled for1078 /// `target`1079 fn test_helpers_out(&self, target: TargetSelection) -> PathBuf {1080 self.native_dir(target).join("rust-test-helpers")1081 }10821083 /// Adds the `RUST_TEST_THREADS` env var if necessary1084 fn add_rust_test_threads(&self, cmd: &mut BootstrapCommand) {1085 if env::var_os("RUST_TEST_THREADS").is_none() {1086 cmd.env("RUST_TEST_THREADS", self.jobs().to_string());1087 }1088 }10891090 /// Returns the libdir of the snapshot compiler.1091 fn rustc_snapshot_libdir(&self) -> PathBuf {1092 self.rustc_snapshot_sysroot().join(libdir(self.config.host_target))1093 }10941095 /// Returns the sysroot of the snapshot compiler.1096 fn rustc_snapshot_sysroot(&self) -> &Path {1097 static SYSROOT_CACHE: OnceLock<PathBuf> = OnceLock::new();1098 SYSROOT_CACHE.get_or_init(|| {1099 command(&self.initial_rustc)1100 .run_in_dry_run()1101 .args(["--print", "sysroot"])1102 .run_capture_stdout(self)1103 .stdout()1104 .trim()1105 .to_owned()1106 .into()1107 })1108 }11091110 fn info(&self, msg: &str) {1111 match self.config.get_dry_run() {1112 DryRun::SelfCheck => (),1113 DryRun::Disabled | DryRun::UserSelected => {1114 println!("{msg}");1115 }1116 }1117 }11181119 /// Return a `Group` guard for a [`Step`] that:1120 /// - Performs `action`1121 /// - If the action is `Kind::Test`, use [`Build::msg_test`] instead.1122 /// - On `what`1123 /// - Where `what` possibly corresponds to a `mode`1124 /// - `action` is performed with/on the given compiler (`target_and_stage`).1125 /// - Since for some steps it is not possible to pass a single compiler here, it is also1126 /// possible to pass the host and stage explicitly.1127 /// - With a given `target`.1128 ///1129 /// [`Step`]: crate::core::builder::Step1130 #[must_use = "Groups should not be dropped until the Step finishes running"]1131 #[track_caller]1132 fn msg(1133 &self,1134 action: impl Into<Kind>,1135 what: impl Display,1136 mode: impl Into<Option<Mode>>,1137 target_and_stage: impl Into<TargetAndStage>,1138 target: impl Into<Option<TargetSelection>>,1139 ) -> Option<gha::Group> {1140 let target_and_stage = target_and_stage.into();1141 let action = action.into();1142 assert!(1143 action != Kind::Test,1144 "Please use `Build::msg_test` instead of `Build::msg(Kind::Test)`"1145 );11461147 let actual_stage = match mode.into() {1148 // Std has the same stage as the compiler that builds it1149 Some(Mode::Std) => target_and_stage.stage,1150 // Other things have stage corresponding to their build compiler + 11151 Some(1152 Mode::Rustc1153 | Mode::Codegen1154 | Mode::ToolBootstrap1155 | Mode::ToolTarget1156 | Mode::ToolStd1157 | Mode::ToolRustcPrivate,1158 )1159 | None => target_and_stage.stage + 1,1160 };11611162 let action = action.description();1163 let what = what.to_string();1164 let msg = |fmt| {1165 let space = if !what.is_empty() { " " } else { "" };1166 format!("{action} stage{actual_stage} {what}{space}{fmt}")1167 };1168 let msg = if let Some(target) = target.into() {1169 let build_stage = target_and_stage.stage;1170 let host = target_and_stage.target;1171 if host == target {1172 msg(format_args!("(stage{build_stage} -> stage{actual_stage}, {target})"))1173 } else {1174 msg(format_args!("(stage{build_stage}:{host} -> stage{actual_stage}:{target})"))1175 }1176 } else {1177 msg(format_args!(""))1178 };1179 self.group(&msg)1180 }11811182 /// Return a `Group` guard for a [`Step`] that tests `what` with the given `stage` and `target`.1183 /// Use this instead of [`Build::msg`] for test steps, because for them it is not always clear1184 /// what exactly is a build compiler.1185 ///1186 /// [`Step`]: crate::core::builder::Step1187 #[must_use = "Groups should not be dropped until the Step finishes running"]1188 #[track_caller]1189 fn msg_test(1190 &self,1191 what: impl Display,1192 target: TargetSelection,1193 stage: u32,1194 ) -> Option<gha::Group> {1195 let action = Kind::Test.description();1196 let msg = format!("{action} stage{stage} {what} ({target})");1197 self.group(&msg)1198 }11991200 /// Return a `Group` guard for a [`Step`] that is only built once and isn't affected by `--stage`.1201 ///1202 /// [`Step`]: crate::core::builder::Step1203 #[must_use = "Groups should not be dropped until the Step finishes running"]1204 #[track_caller]1205 fn msg_unstaged(1206 &self,1207 action: impl Into<Kind>,1208 what: impl Display,1209 target: TargetSelection,1210 ) -> Option<gha::Group> {1211 let action = action.into().description();1212 let msg = format!("{action} {what} for {target}");1213 self.group(&msg)1214 }12151216 #[track_caller]1217 fn group(&self, msg: &str) -> Option<gha::Group> {1218 match self.config.get_dry_run() {1219 DryRun::SelfCheck => None,1220 DryRun::Disabled | DryRun::UserSelected => Some(gha::group(msg)),1221 }1222 }12231224 /// Returns the number of parallel jobs that have been configured for this1225 /// build.1226 fn jobs(&self) -> u32 {1227 self.config.jobs.unwrap_or_else(|| {1228 std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) as u321229 })1230 }12311232 fn debuginfo_map_to(&self, which: GitRepo, remap_scheme: RemapScheme) -> Option<String> {1233 if !self.config.rust_remap_debuginfo {1234 return None;1235 }12361237 match which {1238 GitRepo::Rustc => {1239 let sha = self.rust_sha().unwrap_or(&self.version);12401241 match remap_scheme {1242 RemapScheme::Compiler => {1243 // For compiler sources, remap via `/rustc-dev/{sha}` to allow1244 // distinguishing between compiler sources vs library sources, since1245 // `rustc-dev` dist component places them under1246 // `$sysroot/lib/rustlib/rustc-src/rust` as opposed to `rust-src`'s1247 // `$sysroot/lib/rustlib/src/rust`.1248 //1249 // Keep this scheme in sync with `rustc_metadata::rmeta::decoder`'s1250 // `try_to_translate_virtual_to_real`.1251 Some(format!("/rustc-dev/{sha}"))1252 }1253 RemapScheme::NonCompiler => {1254 // For non-compiler sources, use `/rustc/{sha}` remapping scheme.1255 Some(format!("/rustc/{sha}"))1256 }1257 }1258 }1259 GitRepo::Llvm => Some(String::from("/rustc/llvm")),1260 }1261 }12621263 /// Returns the path to the C compiler for the target specified.1264 fn cc(&self, target: TargetSelection) -> PathBuf {1265 if self.config.dry_run() {1266 return PathBuf::new();1267 }1268 self.cc[&target].path().into()1269 }12701271 /// Returns the internal `cc::Tool` for the C compiler.1272 fn cc_tool(&self, target: TargetSelection) -> Tool {1273 self.cc[&target].clone()1274 }12751276 /// Returns the internal `cc::Tool` for the C++ compiler.1277 fn cxx_tool(&self, target: TargetSelection) -> Tool {1278 self.cxx[&target].clone()1279 }12801281 /// Returns C flags that `cc-rs` thinks should be enabled for the1282 /// specified target by default.1283 fn cc_handled_clags(&self, target: TargetSelection, c: CLang) -> Vec<String> {1284 if self.config.dry_run() {1285 return Vec::new();1286 }1287 let base = match c {1288 CLang::C => self.cc[&target].clone(),1289 CLang::Cxx => self.cxx[&target].clone(),1290 };12911292 // Filter out -O and /O (the optimization flags) that we picked up1293 // from cc-rs, that's up to the caller to figure out.1294 base.args()1295 .iter()1296 .map(|s| s.to_string_lossy().into_owned())1297 .filter(|s| !s.starts_with("-O") && !s.starts_with("/O"))1298 .collect::<Vec<String>>()1299 }13001301 /// Returns extra C flags that `cc-rs` doesn't handle.1302 fn cc_unhandled_cflags(1303 &self,1304 target: TargetSelection,1305 which: GitRepo,1306 c: CLang,1307 ) -> Vec<String> {1308 let mut base = Vec::new();13091310 // If we're compiling C++ on macOS then we add a flag indicating that1311 // we want libc++ (more filled out than libstdc++), ensuring that1312 // LLVM/etc are all properly compiled.1313 if matches!(c, CLang::Cxx) && target.contains("apple-darwin") {1314 base.push("-stdlib=libc++".into());1315 }13161317 // Work around an apparently bad MinGW / GCC optimization,1318 // See: https://lists.llvm.org/pipermail/cfe-dev/2016-December/051980.html1319 // See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=789361320 if &*target.triple == "i686-pc-windows-gnu" {1321 base.push("-fno-omit-frame-pointer".into());1322 }13231324 if let Some(map_to) = self.debuginfo_map_to(which, RemapScheme::NonCompiler) {1325 let map = format!("{}={}", self.src.display(), map_to);1326 let cc = self.cc(target);1327 if cc.ends_with("clang") || cc.ends_with("gcc") {1328 base.push(format!("-fdebug-prefix-map={map}"));1329 } else if cc.ends_with("clang-cl.exe") {1330 base.push("-Xclang".into());1331 base.push(format!("-fdebug-prefix-map={map}"));1332 }1333 }1334 base1335 }13361337 /// Returns the path to the `ar` archive utility for the target specified.1338 fn ar(&self, target: TargetSelection) -> Option<PathBuf> {1339 if self.config.dry_run() {1340 return None;1341 }1342 self.ar.get(&target).cloned()1343 }13441345 /// Returns the path to the `ranlib` utility for the target specified.1346 fn ranlib(&self, target: TargetSelection) -> Option<PathBuf> {1347 if self.config.dry_run() {1348 return None;1349 }1350 self.ranlib.get(&target).cloned()1351 }13521353 /// Returns the path to the C++ compiler for the target specified.1354 fn cxx(&self, target: TargetSelection) -> Result<PathBuf, String> {1355 if self.config.dry_run() {1356 return Ok(PathBuf::new());1357 }1358 match self.cxx.get(&target) {1359 Some(p) => Ok(p.path().into()),1360 None => Err(format!("target `{target}` is not configured as a host, only as a target")),1361 }1362 }13631364 /// Returns the path to the linker for the given target if it needs to be overridden.1365 fn linker(&self, target: TargetSelection) -> Option<PathBuf> {1366 if self.config.dry_run() {1367 return Some(PathBuf::new());1368 }1369 if let Some(linker) = self.config.target_config.get(&target).and_then(|c| c.linker.clone())1370 {1371 Some(linker)1372 } else if target.contains("vxworks") {1373 // need to use CXX compiler as linker to resolve the exception functions1374 // that are only existed in CXX libraries1375 Some(self.cxx[&target].path().into())1376 } else if !self.config.is_host_target(target)1377 && helpers::use_host_linker(target)1378 && !target.is_msvc()1379 {1380 Some(self.cc(target))1381 } else if self.config.bootstrap_override_lld.is_used()1382 && self.is_lld_direct_linker(target)1383 && self.host_target == target1384 {1385 match self.config.bootstrap_override_lld {1386 BootstrapOverrideLld::SelfContained => Some(self.initial_lld.clone()),1387 BootstrapOverrideLld::External => Some("lld".into()),1388 BootstrapOverrideLld::None => None,1389 }1390 } else {1391 None1392 }1393 }13941395 // Is LLD configured directly through `-Clinker`?1396 // Only MSVC targets use LLD directly at the moment.1397 fn is_lld_direct_linker(&self, target: TargetSelection) -> bool {1398 target.is_msvc()1399 }14001401 /// Returns if this target should statically link the C runtime, if specified1402 fn crt_static(&self, target: TargetSelection) -> Option<bool> {1403 if target.contains("pc-windows-msvc") {1404 Some(true)1405 } else {1406 self.config.target_config.get(&target).and_then(|t| t.crt_static)1407 }1408 }14091410 /// Returns the "musl root" for this `target`, if defined.1411 ///1412 /// If this is a native target (host is also musl) and no musl-root is given,1413 /// it falls back to the system toolchain in /usr.1414 fn musl_root(&self, target: TargetSelection) -> Option<&Path> {1415 let configured_root = self1416 .config1417 .target_config1418 .get(&target)1419 .and_then(|t| t.musl_root.as_ref())1420 .or(self.config.musl_root.as_ref())1421 .map(|p| &**p);14221423 if self.config.is_host_target(target) && configured_root.is_none() {1424 Some(Path::new("/usr"))1425 } else {1426 configured_root1427 }1428 }14291430 /// Returns the "musl libdir" for this `target`.1431 fn musl_libdir(&self, target: TargetSelection) -> Option<PathBuf> {1432 self.config1433 .target_config1434 .get(&target)1435 .and_then(|t| t.musl_libdir.clone())1436 .or_else(|| self.musl_root(target).map(|root| root.join("lib")))1437 }14381439 /// Returns the `lib` directory for the WASI target specified, if1440 /// configured.1441 ///1442 /// This first consults `wasi-root` as configured in per-target1443 /// configuration, and failing that it assumes that `$WASI_SDK_PATH` is1444 /// set in the environment, and failing that `None` is returned.1445 fn wasi_libdir(&self, target: TargetSelection) -> Option<PathBuf> {1446 let configured =1447 self.config.target_config.get(&target).and_then(|t| t.wasi_root.as_ref()).map(|p| &**p);1448 if let Some(path) = configured {1449 return Some(path.join("lib").join(target.to_string()));1450 }1451 let mut env_root = self.wasi_sdk_path.clone()?;1452 env_root.push("share");1453 env_root.push("wasi-sysroot");1454 env_root.push("lib");1455 env_root.push(target.to_string());1456 Some(env_root)1457 }14581459 /// Returns `true` if this is a no-std `target`, if defined1460 fn no_std(&self, target: TargetSelection) -> Option<bool> {1461 self.config.target_config.get(&target).map(|t| t.no_std)1462 }14631464 /// Returns `true` if the target will be tested using the `remote-test-client`1465 /// and `remote-test-server` binaries.1466 fn remote_tested(&self, target: TargetSelection) -> bool {1467 self.qemu_rootfs(target).is_some()1468 || target.contains("android")1469 || env::var_os("TEST_DEVICE_ADDR").is_some()1470 }14711472 /// Returns an optional "runner" to pass to `compiletest` when executing1473 /// test binaries.1474 ///1475 /// An example of this would be a WebAssembly runtime when testing the wasm1476 /// targets.1477 fn runner(&self, target: TargetSelection) -> Option<String> {1478 let configured_runner =1479 self.config.target_config.get(&target).and_then(|t| t.runner.as_ref()).map(|p| &**p);1480 if let Some(runner) = configured_runner {1481 return Some(runner.to_owned());1482 }14831484 if target.starts_with("wasm") && target.contains("wasi") {1485 self.default_wasi_runner(target)1486 } else {1487 None1488 }1489 }14901491 /// When a `runner` configuration is not provided and a WASI-looking target1492 /// is being tested this is consulted to prove the environment to see if1493 /// there's a runtime already lying around that seems reasonable to use.1494 fn default_wasi_runner(&self, target: TargetSelection) -> Option<String> {1495 let mut finder = crate::core::sanity::Finder::new();14961497 // Look for Wasmtime, and for its default options be sure to disable1498 // its caching system since we're executing quite a lot of tests and1499 // ideally shouldn't pollute the cache too much.1500 if let Some(path) = finder.maybe_have("wasmtime")1501 && let Ok(mut path) = path.into_os_string().into_string()1502 {1503 path.push_str(" run -C cache=n --dir .");1504 // Make sure that tests have access to RUSTC_BOOTSTRAP. This (for example) is1505 // required for libtest to work on beta/stable channels.1506 //1507 // NB: with Wasmtime 20 this can change to `-S inherit-env` to1508 // inherit the entire environment rather than just this single1509 // environment variable.1510 path.push_str(" --env RUSTC_BOOTSTRAP");15111512 if target.contains("wasip2") {1513 path.push_str(" --wasi inherit-network --wasi allow-ip-name-lookup");1514 }15151516 return Some(path);1517 }15181519 None1520 }15211522 /// Returns whether the specified tool is configured as part of this build.1523 ///1524 /// This requires that both the `extended` key is set and the `tools` key is1525 /// either unset or specifically contains the specified tool.1526 fn tool_enabled(&self, tool: &str) -> bool {1527 if !self.config.extended {1528 return false;1529 }1530 match &self.config.tools {1531 Some(set) => set.contains(tool),1532 None => true,1533 }1534 }15351536 /// Returns the root of the "rootfs" image that this target will be using,1537 /// if one was configured.1538 ///1539 /// If `Some` is returned then that means that tests for this target are1540 /// emulated with QEMU and binaries will need to be shipped to the emulator.1541 fn qemu_rootfs(&self, target: TargetSelection) -> Option<&Path> {1542 self.config.target_config.get(&target).and_then(|t| t.qemu_rootfs.as_ref()).map(|p| &**p)1543 }15441545 /// Temporary directory that extended error information is emitted to.1546 fn extended_error_dir(&self) -> PathBuf {1547 self.out.join("tmp/extended-error-metadata")1548 }15491550 /// Tests whether the `compiler` compiling for `target` should be forced to1551 /// use a stage1 compiler instead.1552 ///1553 /// Currently, by default, the build system does not perform a "full1554 /// bootstrap" by default where we compile the compiler three times.1555 /// Instead, we compile the compiler two times. The final stage (stage2)1556 /// just copies the libraries from the previous stage, which is what this1557 /// method detects.1558 ///1559 /// Here we return `true` if:1560 ///1561 /// * The build isn't performing a full bootstrap1562 /// * The `compiler` is in the final stage, 21563 /// * We're not cross-compiling, so the artifacts are already available in1564 /// stage11565 ///1566 /// When all of these conditions are met the build will lift artifacts from1567 /// the previous stage forward.1568 fn force_use_stage1(&self, stage: u32, target: TargetSelection) -> bool {1569 !self.config.full_bootstrap1570 && !self.config.download_rustc()1571 && stage >= 21572 && (self.hosts.contains(&target) || target == self.host_target)1573 }15741575 /// Checks whether the `compiler` compiling for `target` should be forced to1576 /// use a stage2 compiler instead.1577 ///1578 /// When we download the pre-compiled version of rustc and compiler stage is >= 2,1579 /// it should be forced to use a stage2 compiler.1580 fn force_use_stage2(&self, stage: u32) -> bool {1581 self.config.download_rustc() && stage >= 21582 }15831584 /// Given `num` in the form "a.b.c" return a "release string" which1585 /// describes the release version number.1586 ///1587 /// For example on nightly this returns "a.b.c-nightly", on beta it returns1588 /// "a.b.c-beta.1" and on stable it just returns "a.b.c".1589 fn release(&self, num: &str) -> String {1590 match &self.config.channel[..] {1591 "stable" => num.to_string(),1592 "beta" => {1593 if !self.config.omit_git_hash {1594 format!("{}-beta.{}", num, self.beta_prerelease_version())1595 } else {1596 format!("{num}-beta")1597 }1598 }1599 "nightly" => format!("{num}-nightly"),1600 _ => format!("{num}-dev"),1601 }1602 }16031604 fn beta_prerelease_version(&self) -> u32 {1605 fn extract_beta_rev_from_file<P: AsRef<Path>>(version_file: P) -> Option<String> {1606 let version = fs::read_to_string(version_file).ok()?;16071608 helpers::extract_beta_rev(&version)1609 }16101611 if let Some(s) = self.prerelease_version.get() {1612 return s;1613 }16141615 // First check if there is a version file available.1616 // If available, we read the beta revision from that file.1617 // This only happens when building from a source tarball when Git should not be used.1618 let count = extract_beta_rev_from_file(self.src.join("version")).unwrap_or_else(|| {1619 // Figure out how many merge commits happened since we branched off main.1620 // That's our beta number!1621 // (Note that we use a `..` range, not the `...` symmetric difference.)1622 helpers::git(Some(&self.src))1623 .arg("rev-list")1624 .arg("--count")1625 .arg("--merges")1626 .arg(format!(1627 "refs/remotes/origin/{}..HEAD",1628 self.config.stage0_metadata.config.nightly_branch1629 ))1630 .run_in_dry_run()1631 .run_capture(self)1632 .stdout()1633 });1634 let n = count.trim().parse().unwrap();1635 self.prerelease_version.set(Some(n));1636 n1637 }16381639 /// Returns the value of `release` above for Rust itself.1640 fn rust_release(&self) -> String {1641 self.release(&self.version)1642 }16431644 /// Returns the "package version" for a component.1645 ///1646 /// The package version is typically what shows up in the names of tarballs.1647 /// For channels like beta/nightly it's just the channel name, otherwise it's the release1648 /// version.1649 fn rust_package_vers(&self) -> String {1650 match &self.config.channel[..] {1651 "stable" => self.version.to_string(),1652 "beta" => "beta".to_string(),1653 "nightly" => "nightly".to_string(),1654 _ => format!("{}-dev", self.version),1655 }1656 }16571658 /// Returns the `version` string associated with this compiler for Rust1659 /// itself.1660 ///1661 /// Note that this is a descriptive string which includes the commit date,1662 /// sha, version, etc.1663 fn rust_version(&self) -> String {1664 let mut version = self.rust_info().version(self, &self.version);1665 if let Some(ref s) = self.config.description1666 && !s.is_empty()1667 {1668 version.push_str(" (");1669 version.push_str(s);1670 version.push(')');1671 }1672 version1673 }16741675 /// Returns the full commit hash.1676 fn rust_sha(&self) -> Option<&str> {1677 self.rust_info().sha()1678 }16791680 /// Returns the `a.b.c` version that the given package is at.1681 fn release_num(&self, package: &str) -> String {1682 let toml_file_name = self.src.join(format!("src/tools/{package}/Cargo.toml"));1683 let toml = t!(fs::read_to_string(toml_file_name));1684 for line in toml.lines() {1685 if let Some(stripped) =1686 line.strip_prefix("version = \"").and_then(|s| s.strip_suffix('"'))1687 {1688 return stripped.to_owned();1689 }1690 }16911692 panic!("failed to find version in {package}'s Cargo.toml")1693 }16941695 /// Returns `true` if unstable features should be enabled for the compiler1696 /// we're building.1697 fn unstable_features(&self) -> bool {1698 !matches!(&self.config.channel[..], "stable" | "beta")1699 }17001701 /// Returns a Vec of all the dependencies of the given root crate,1702 /// including transitive dependencies and the root itself. Only includes1703 /// "local" crates (those in the local source tree, not from a registry).1704 fn in_tree_crates(&self, root: &str, target: Option<TargetSelection>) -> Vec<&Crate> {1705 let mut ret = Vec::new();1706 let mut list = vec![root.to_owned()];1707 let mut visited = HashSet::new();1708 while let Some(krate) = list.pop() {1709 let krate = self1710 .crates1711 .get(&krate)1712 .unwrap_or_else(|| panic!("metadata missing for {krate}: {:?}", self.crates));1713 ret.push(krate);1714 for dep in &krate.deps {1715 if !self.crates.contains_key(dep) {1716 // Ignore non-workspace members.1717 continue;1718 }1719 // Don't include optional deps if their features are not1720 // enabled. Ideally this would be computed from `cargo1721 // metadata --features …`, but that is somewhat slow. In1722 // the future, we may want to consider just filtering all1723 // build and dev dependencies in metadata::build.1724 if visited.insert(dep)1725 && (dep != "profiler_builtins"1726 || target1727 .map(|t| self.config.profiler_enabled(t))1728 .unwrap_or_else(|| self.config.any_profiler_enabled()))1729 && (dep != "rustc_codegen_llvm"1730 || self.config.hosts.iter().any(|host| self.config.llvm_enabled(*host)))1731 {1732 list.push(dep.clone());1733 }1734 }1735 }1736 ret.sort_unstable_by_key(|krate| krate.name.clone()); // reproducible order needed for tests1737 ret1738 }17391740 fn read_stamp_file(&self, stamp: &BuildStamp) -> Vec<(PathBuf, DependencyType)> {1741 if self.config.dry_run() {1742 return Vec::new();1743 }17441745 if !stamp.path().exists() {1746 eprintln!(1747 "ERROR: Unable to find the stamp file {}, did you try to keep a nonexistent build stage?",1748 stamp.path().display()1749 );1750 crate::exit!(1);1751 }17521753 let mut paths = Vec::new();1754 let contents = t!(fs::read(stamp.path()), stamp.path());1755 // This is the method we use for extracting paths from the stamp file passed to us. See1756 // run_cargo for more information (in compile.rs).1757 for part in contents.split(|b| *b == 0) {1758 if part.is_empty() {1759 continue;1760 }1761 let dependency_type = match part[0] as char {1762 'h' => DependencyType::Host,1763 's' => DependencyType::TargetSelfContained,1764 't' => DependencyType::Target,1765 _ => unreachable!(),1766 };1767 let path = PathBuf::from(t!(str::from_utf8(&part[1..])));1768 paths.push((path, dependency_type));1769 }1770 paths1771 }17721773 /// Copies a file from `src` to `dst`.1774 ///1775 /// If `src` is a symlink, `src` will be resolved to the actual path1776 /// and copied to `dst` instead of the symlink itself.1777 #[track_caller]1778 pub fn resolve_symlink_and_copy(&self, src: &Path, dst: &Path) {1779 self.copy_link_internal(src, dst, true);1780 }17811782 /// Links a file from `src` to `dst`.1783 /// Attempts to use hard links if possible, falling back to copying.1784 /// You can neither rely on this being a copy nor it being a link,1785 /// so do not write to dst.1786 #[track_caller]1787 pub fn copy_link(&self, src: &Path, dst: &Path, file_type: FileType) {1788 self.copy_link_internal(src, dst, false);17891790 if file_type.could_have_split_debuginfo()1791 && let Some(dbg_file) = split_debuginfo(src)1792 {1793 self.copy_link_internal(1794 &dbg_file,1795 &dst.with_extension(dbg_file.extension().unwrap()),1796 false,1797 );1798 }1799 }18001801 #[track_caller]1802 fn copy_link_internal(&self, src: &Path, dst: &Path, dereference_symlinks: bool) {1803 if self.config.dry_run() {1804 return;1805 }1806 if src == dst {1807 return;1808 }18091810 #[cfg(feature = "tracing")]1811 let _span = trace_io!("file-copy-link", ?src, ?dst);18121813 if let Err(e) = fs::remove_file(dst)1814 && cfg!(windows)1815 && e.kind() != io::ErrorKind::NotFound1816 {1817 // workaround for https://github.com/rust-lang/rust/issues/1271261818 // if removing the file fails, attempt to rename it instead.1819 let now = t!(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH));1820 let _ = fs::rename(dst, format!("{}-{}", dst.display(), now.as_nanos()));1821 }1822 let mut metadata = t!(src.symlink_metadata(), format!("src = {}", src.display()));1823 let mut src = src.to_path_buf();1824 if metadata.file_type().is_symlink() {1825 if dereference_symlinks {1826 src = t!(fs::canonicalize(src));1827 metadata = t!(fs::metadata(&src), format!("target = {}", src.display()));1828 } else {1829 let link = t!(fs::read_link(src));1830 t!(self.symlink_file(link, dst));1831 return;1832 }1833 }1834 if let Ok(()) = fs::hard_link(&src, dst) {1835 // Attempt to "easy copy" by creating a hard link (symlinks are privileged on windows),1836 // but if that fails just fall back to a slow `copy` operation.1837 } else {1838 if let Err(e) = fs::copy(&src, dst) {1839 panic!("failed to copy `{}` to `{}`: {}", src.display(), dst.display(), e)1840 }1841 t!(fs::set_permissions(dst, metadata.permissions()));18421843 // Restore file times because changing permissions on e.g. Linux using `chmod` can cause1844 // file access time to change.1845 let file_times = fs::FileTimes::new()1846 .set_accessed(t!(metadata.accessed()))1847 .set_modified(t!(metadata.modified()));1848 t!(set_file_times(dst, file_times));1849 }1850 }18511852 /// Links the `src` directory recursively to `dst`. Both are assumed to exist1853 /// when this function is called.1854 /// Will attempt to use hard links if possible and fall back to copying.1855 #[track_caller]1856 pub fn cp_link_r(&self, src: &Path, dst: &Path) {1857 if self.config.dry_run() {1858 return;1859 }1860 for f in self.read_dir(src) {1861 let path = f.path();1862 let name = path.file_name().unwrap();1863 let dst = dst.join(name);1864 if t!(f.file_type()).is_dir() {1865 t!(fs::create_dir_all(&dst));1866 self.cp_link_r(&path, &dst);1867 } else {1868 self.copy_link(&path, &dst, FileType::Regular);1869 }1870 }1871 }18721873 /// Copies the `src` directory recursively to `dst`. Both are assumed to exist1874 /// when this function is called.1875 /// Will attempt to use hard links if possible and fall back to copying.1876 /// Unwanted files or directories can be skipped1877 /// by returning `false` from the filter function.1878 #[track_caller]1879 pub fn cp_link_filtered(&self, src: &Path, dst: &Path, filter: &dyn Fn(&Path) -> bool) {1880 // Immediately recurse with an empty relative path1881 self.cp_link_filtered_recurse(src, dst, Path::new(""), filter)1882 }18831884 // Inner function does the actual work1885 #[track_caller]1886 fn cp_link_filtered_recurse(1887 &self,1888 src: &Path,1889 dst: &Path,1890 relative: &Path,1891 filter: &dyn Fn(&Path) -> bool,1892 ) {1893 for f in self.read_dir(src) {1894 let path = f.path();1895 let name = path.file_name().unwrap();1896 let dst = dst.join(name);1897 let relative = relative.join(name);1898 // Only copy file or directory if the filter function returns true1899 if filter(&relative) {1900 if t!(f.file_type()).is_dir() {1901 let _ = fs::remove_dir_all(&dst);1902 self.create_dir(&dst);1903 self.cp_link_filtered_recurse(&path, &dst, &relative, filter);1904 } else {1905 self.copy_link(&path, &dst, FileType::Regular);1906 }1907 }1908 }1909 }19101911 fn copy_link_to_folder(&self, src: &Path, dest_folder: &Path) {1912 let file_name = src.file_name().unwrap();1913 let dest = dest_folder.join(file_name);1914 self.copy_link(src, &dest, FileType::Regular);1915 }19161917 fn install(&self, src: &Path, dstdir: &Path, file_type: FileType) {1918 if self.config.dry_run() {1919 return;1920 }1921 let dst = dstdir.join(src.file_name().unwrap());19221923 #[cfg(feature = "tracing")]1924 let _span = trace_io!("install", ?src, ?dst);19251926 t!(fs::create_dir_all(dstdir));1927 if !src.exists() {1928 panic!("ERROR: File \"{}\" not found!", src.display());1929 }19301931 self.copy_link_internal(src, &dst, true);1932 chmod(&dst, file_type.perms());19331934 // If this file can have debuginfo, look for split debuginfo and install it too.1935 if file_type.could_have_split_debuginfo()1936 && let Some(dbg_file) = split_debuginfo(src)1937 {1938 self.install(&dbg_file, dstdir, FileType::Regular);1939 }1940 }19411942 fn read(&self, path: &Path) -> String {1943 if self.config.dry_run() {1944 return String::new();1945 }1946 t!(fs::read_to_string(path))1947 }19481949 #[track_caller]1950 fn create_dir(&self, dir: &Path) {1951 if self.config.dry_run() {1952 return;1953 }19541955 #[cfg(feature = "tracing")]1956 let _span = trace_io!("dir-create", ?dir);19571958 t!(fs::create_dir_all(dir))1959 }19601961 fn remove_dir(&self, dir: &Path) {1962 if self.config.dry_run() {1963 return;1964 }19651966 #[cfg(feature = "tracing")]1967 let _span = trace_io!("dir-remove", ?dir);19681969 t!(fs::remove_dir_all(dir))1970 }19711972 /// Make sure that `dir` will be an empty existing directory after this function ends.1973 /// If it existed before, it will be first deleted.1974 fn clear_dir(&self, dir: &Path) {1975 if self.config.dry_run() {1976 return;1977 }19781979 #[cfg(feature = "tracing")]1980 let _span = trace_io!("dir-clear", ?dir);19811982 let _ = std::fs::remove_dir_all(dir);1983 self.create_dir(dir);1984 }19851986 fn read_dir(&self, dir: &Path) -> impl Iterator<Item = fs::DirEntry> {1987 let iter = match fs::read_dir(dir) {1988 Ok(v) => v,1989 Err(_) if self.config.dry_run() => return vec![].into_iter(),1990 Err(err) => panic!("could not read dir {dir:?}: {err:?}"),1991 };1992 iter.map(|e| t!(e)).collect::<Vec<_>>().into_iter()1993 }19941995 fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, link: Q) -> io::Result<()> {1996 #[cfg(unix)]1997 use std::os::unix::fs::symlink as symlink_file;1998 #[cfg(windows)]1999 use std::os::windows::fs::symlink_file;2000 if !self.config.dry_run() { symlink_file(src.as_ref(), link.as_ref()) } else { Ok(()) }
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.