Skip to content

Add ToolTarget to bootstrap #143641

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jul 19, 2025
2 changes: 1 addition & 1 deletion src/bootstrap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ build/
debuginfo/
...

# Bootstrap host tools (which are always compiled with the stage0 compiler)
# Host tools (which are always compiled with the stage0 compiler)
# are stored here.
bootstrap-tools/

Expand Down
6 changes: 5 additions & 1 deletion src/bootstrap/src/core/build_steps/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use crate::core::build_steps::compile::{
add_to_sysroot, run_cargo, rustc_cargo, rustc_cargo_env, std_cargo, std_crates_for_run_make,
};
use crate::core::build_steps::tool;
use crate::core::build_steps::tool::{COMPILETEST_ALLOW_FEATURES, SourceType, prepare_tool_cargo};
use crate::core::build_steps::tool::{
COMPILETEST_ALLOW_FEATURES, SourceType, get_tool_target_compiler, prepare_tool_cargo,
};
use crate::core::builder::{
self, Alias, Builder, Kind, RunConfig, ShouldRun, Step, StepMetadata, crate_description,
};
Expand Down Expand Up @@ -247,8 +249,10 @@ fn prepare_compiler_for_check(
mode: Mode,
) -> Compiler {
let host = builder.host_target;

match mode {
Mode::ToolBootstrap => builder.compiler(0, host),
Mode::ToolTarget => get_tool_target_compiler(builder, target),
Mode::ToolStd => {
// These tools require the local standard library to be checked
let build_compiler = builder.compiler(builder.top_stage, host);
Expand Down
11 changes: 11 additions & 0 deletions src/bootstrap/src/core/build_steps/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,17 @@ pub(crate) fn get_tool_rustc_compiler(
builder.compiler(target_compiler.stage.saturating_sub(1), builder.config.host_target)
}

/// Returns a compiler that is able to compile a `ToolTarget` tool for the given `target`.
pub(crate) fn get_tool_target_compiler(builder: &Builder<'_>, target: TargetSelection) -> Compiler {
todo!("FIX");
if builder.host_target == target {
builder.compiler(0, builder.host_target)
} else {
// FIXME: should this be builder.top_stage to avoid rebuilds?
builder.compiler(1, target)
}
}

/// Links a built tool binary with the given `name` from the build directory to the
/// tools directory.
fn copy_link_tool_bin(
Expand Down
55 changes: 30 additions & 25 deletions src/bootstrap/src/core/builder/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,23 +537,25 @@ impl Builder<'_> {
}
}

let stage = if compiler.stage == 0 && self.local_rebuild {
let build_compiler_stage = if compiler.stage == 0 && self.local_rebuild {
// Assume the local-rebuild rustc already has stage1 features.
1
} else {
compiler.stage
};

// We synthetically interpret a stage0 compiler used to build tools as a
// "raw" compiler in that it's the exact snapshot we download. Normally
// the stage0 build means it uses libraries build by the stage0
// compiler, but for tools we just use the precompiled libraries that
// we've downloaded
let use_snapshot = mode == Mode::ToolBootstrap;
assert!(!use_snapshot || stage == 0 || self.local_rebuild);

let maybe_sysroot = self.sysroot(compiler);
let sysroot = if use_snapshot { self.rustc_snapshot_sysroot() } else { &maybe_sysroot };
// "raw" compiler in that it's the exact snapshot we download. For things like
// ToolRustc, we would have to use the artificial stage0-sysroot compiler instead.
let use_snapshot =
mode == Mode::ToolBootstrap || (mode == Mode::ToolTarget && build_compiler_stage == 0);
assert!(!use_snapshot || build_compiler_stage == 0 || self.local_rebuild);

let sysroot = if use_snapshot {
self.rustc_snapshot_sysroot().to_path_buf()
} else {
self.sysroot(compiler)
};
let libdir = self.rustc_libdir(compiler);

let sysroot_str = sysroot.as_os_str().to_str().expect("sysroot should be UTF-8");
Expand All @@ -562,7 +564,7 @@ impl Builder<'_> {
}

let mut rustflags = Rustflags::new(target);
if stage != 0 {
if build_compiler_stage != 0 {
if let Ok(s) = env::var("CARGOFLAGS_NOT_BOOTSTRAP") {
cargo.args(s.split_whitespace());
}
Expand Down Expand Up @@ -604,7 +606,7 @@ impl Builder<'_> {
// sysroot. Passing this cfg enables raw-dylib support instead, which makes the native
// library unnecessary. This can be removed when windows-rs enables raw-dylib
// unconditionally.
if let Mode::Rustc | Mode::ToolRustc | Mode::ToolBootstrap = mode {
if let Mode::Rustc | Mode::ToolRustc | Mode::ToolBootstrap | Mode::ToolTarget = mode {
rustflags.arg("--cfg=windows_raw_dylib");
}

Expand Down Expand Up @@ -657,7 +659,7 @@ impl Builder<'_> {
// FIXME(rust-lang/cargo#5754) we shouldn't be using special command arguments
// to the host invocation here, but rather Cargo should know what flags to pass rustc
// itself.
if stage == 0 {
if build_compiler_stage == 0 {
hostflags.arg("--cfg=bootstrap");
}

Expand All @@ -666,7 +668,7 @@ impl Builder<'_> {
// #71458.
let mut rustdocflags = rustflags.clone();
rustdocflags.propagate_cargo_env("RUSTDOCFLAGS");
if stage == 0 {
if build_compiler_stage == 0 {
rustdocflags.env("RUSTDOCFLAGS_BOOTSTRAP");
} else {
rustdocflags.env("RUSTDOCFLAGS_NOT_BOOTSTRAP");
Expand All @@ -677,7 +679,7 @@ impl Builder<'_> {
}

match mode {
Mode::Std | Mode::ToolBootstrap | Mode::ToolStd => {}
Mode::Std | Mode::ToolBootstrap | Mode::ToolStd | Mode::ToolTarget => {}
Mode::Rustc | Mode::Codegen | Mode::ToolRustc => {
// Build proc macros both for the host and the target unless proc-macros are not
// supported by the target.
Expand Down Expand Up @@ -719,7 +721,7 @@ impl Builder<'_> {
// feature on the rustc side.
cargo.arg("-Zbinary-dep-depinfo");
let allow_features = match mode {
Mode::ToolBootstrap | Mode::ToolStd => {
Mode::ToolBootstrap | Mode::ToolStd | Mode::ToolTarget => {
// Restrict the allowed features so we don't depend on nightly
// accidentally.
//
Expand Down Expand Up @@ -827,7 +829,7 @@ impl Builder<'_> {
cargo
.env("RUSTBUILD_NATIVE_DIR", self.native_dir(target))
.env("RUSTC_REAL", self.rustc(compiler))
.env("RUSTC_STAGE", stage.to_string())
.env("RUSTC_STAGE", build_compiler_stage.to_string())
.env("RUSTC_SYSROOT", sysroot)
.env("RUSTC_LIBDIR", libdir)
.env("RUSTDOC", self.bootstrap_out.join("rustdoc"))
Expand Down Expand Up @@ -872,7 +874,7 @@ impl Builder<'_> {
let debuginfo_level = match mode {
Mode::Rustc | Mode::Codegen => self.config.rust_debuginfo_level_rustc,
Mode::Std => self.config.rust_debuginfo_level_std,
Mode::ToolBootstrap | Mode::ToolStd | Mode::ToolRustc => {
Mode::ToolBootstrap | Mode::ToolStd | Mode::ToolRustc | Mode::ToolTarget => {
self.config.rust_debuginfo_level_tools
}
};
Expand All @@ -884,11 +886,10 @@ impl Builder<'_> {
profile_var("DEBUG_ASSERTIONS"),
match mode {
Mode::Std => self.config.std_debug_assertions,
Mode::Rustc => self.config.rustc_debug_assertions,
Mode::Codegen => self.config.rustc_debug_assertions,
Mode::ToolBootstrap => self.config.tools_debug_assertions,
Mode::ToolStd => self.config.tools_debug_assertions,
Mode::ToolRustc => self.config.tools_debug_assertions,
Mode::Rustc | Mode::Codegen => self.config.rustc_debug_assertions,
Mode::ToolBootstrap | Mode::ToolStd | Mode::ToolRustc | Mode::ToolTarget => {
self.config.tools_debug_assertions
}
}
.to_string(),
);
Expand Down Expand Up @@ -959,7 +960,11 @@ impl Builder<'_> {
cargo.env("CFG_VIRTUAL_RUSTC_DEV_SOURCE_BASE_DIR", map_to);
}
}
Mode::Std | Mode::ToolBootstrap | Mode::ToolRustc | Mode::ToolStd => {
Mode::Std
| Mode::ToolBootstrap
| Mode::ToolRustc
| Mode::ToolStd
| Mode::ToolTarget => {
if let Some(ref map_to) =
self.build.debuginfo_map_to(GitRepo::Rustc, RemapScheme::NonCompiler)
{
Expand Down Expand Up @@ -1274,7 +1279,7 @@ impl Builder<'_> {
};

if let Some(limit) = limit
&& (stage == 0
&& (build_compiler_stage == 0
|| self.config.default_codegen_backend(target).unwrap_or_default() == "llvm")
{
rustflags.arg(&format!("-Cllvm-args=-import-instr-limit={limit}"));
Expand Down
1 change: 1 addition & 0 deletions src/bootstrap/src/core/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,7 @@ impl<'a> Builder<'a> {
tool::RemoteTestServer,
tool::RemoteTestClient,
tool::RustInstaller,
tool::FeaturesStatusDump,
tool::Cargo,
tool::RustAnalyzer,
tool::RustAnalyzerProcMacroSrv,
Expand Down
68 changes: 56 additions & 12 deletions src/bootstrap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,24 @@ pub enum Mode {
/// These tools are intended to be only executed on the host system that
/// invokes bootstrap, and they thus cannot be cross-compiled.
///
/// They are always built using the stage0 compiler, and typically they
/// They are always built using the stage0 compiler, and they
/// can be compiled with stable Rust.
///
/// These tools also essentially do not participate in staging.
ToolBootstrap,

/// Build a cross-compilable helper tool. These tools do not depend on unstable features or
/// compiler internals, but they might be cross-compilable (so we cannot build them using the
/// stage0 compiler, unlike `ToolBootstrap`).
///
/// Some of these tools are also shipped in our `dist` archives.
/// While we could compile them using the stage0 compiler when not cross-compiling, we instead
/// use the in-tree compiler (and std) to build them, so that we can ship e.g. std security
/// fixes and avoid depending fully on stage0 for the artifacts that we ship.
///
/// This mode is used e.g. for linkers and linker tools invoked by rustc on its host target.
ToolTarget,

/// Build a tool which uses the locally built std, placing output in the
/// "stageN-tools" directory. Its usage is quite rare, mainly used by
/// compiletest which needs libtest.
Expand All @@ -273,11 +285,21 @@ pub enum Mode {

impl Mode {
pub fn is_tool(&self) -> bool {
matches!(self, Mode::ToolBootstrap | Mode::ToolRustc | Mode::ToolStd)
match self {
Mode::ToolBootstrap | Mode::ToolRustc | Mode::ToolStd | Mode::ToolTarget => true,
Mode::Std | Mode::Codegen | Mode::Rustc => false,
}
}

pub fn must_support_dlopen(&self) -> bool {
matches!(self, Mode::Std | Mode::Codegen)
match self {
Mode::Std | Mode::Codegen => true,
Mode::ToolBootstrap
| Mode::ToolRustc
| Mode::ToolStd
| Mode::ToolTarget
| Mode::Rustc => false,
}
}
}

Expand Down Expand Up @@ -804,17 +826,39 @@ impl Build {
/// stage when running with a particular host compiler.
///
/// The mode indicates what the root directory is for.
fn stage_out(&self, compiler: Compiler, mode: Mode) -> PathBuf {
let suffix = match mode {
Mode::Std => "-std",
Mode::Rustc => "-rustc",
Mode::Codegen => "-codegen",
Mode::ToolBootstrap => {
return self.out.join(compiler.host).join("bootstrap-tools");
fn stage_out(&self, build_compiler: Compiler, mode: Mode) -> PathBuf {
use std::fmt::Write;

fn bootstrap_tool() -> (Option<u32>, &'static str) {
(None, "bootstrap-tools")
}
fn staged_tool(build_compiler: Compiler) -> (Option<u32>, &'static str) {
(Some(build_compiler.stage), "tools")
}

let (stage, suffix) = match mode {
Mode::Std => (Some(build_compiler.stage), "std"),
Mode::Rustc => (Some(build_compiler.stage), "rustc"),
Mode::Codegen => (Some(build_compiler.stage), "codegen"),
Mode::ToolBootstrap => bootstrap_tool(),
Mode::ToolStd | Mode::ToolRustc => (Some(build_compiler.stage), "tools"),
Mode::ToolTarget => {
// If we're not cross-compiling (the common case), share the target directory with
// bootstrap tools to reuse the build cache.
if build_compiler.stage == 0 {
bootstrap_tool()
} else {
staged_tool(build_compiler)
}
}
Mode::ToolStd | Mode::ToolRustc => "-tools",
};
self.out.join(compiler.host).join(format!("stage{}{}", compiler.stage, suffix))
let path = self.out.join(build_compiler.host);
let mut dir_name = String::new();
if let Some(stage) = stage {
write!(dir_name, "stage{stage}-").unwrap();
}
dir_name.push_str(suffix);
path.join(dir_name)
}

/// Returns the root output directory for all Cargo output in a given stage,
Expand Down