Rust Bootstrap 工具编写指南:ToolBootstrap / ToolStd / ToolRustcPrivate 三种模式详解
导读
rustc 的构建系统(bootstrap)在编译 Rust 编译器本身之外,还需要构建大量辅助工具,例如 rustdoc、clippy、rustfmt、miri、tidy 等。本指南基于 rustc-dev-guide 中 writing-tools-in-bootstrap 一文,系统讲解 bootstrap 中编写工具的三种模式(Mode::ToolBootstrap、Mode::ToolStd、Mode::ToolRustcPrivate)的适用场景、输出目录、底层实现与注册方式。读完本文,你将掌握如何在 rustc 仓库中为新增工具选择合适的模式、编写对应的 Step 实现,并理解 ToolBuild 与 ToolBuildResult 在整个构建流程中的真实运作机制。
一、三种工具模式总览
bootstrap 依据工具对仓库内构建产物的依赖程度,将工具分为三种类型。它们的核心差异在于:用什么编译器构建、依赖哪些 in-tree 产物、产物输出到哪里。
| 模式 | 使用的编译器 | 依赖的构建产物 | 输出目录 | 典型工具 |
|---|---|---|---|---|
Mode::ToolBootstrap |
stage0 编译器 | 无(纯通用工具) | bootstrap-tools |
tidy、linkchecker、rustbook |
Mode::ToolStd |
本地构建的编译器 | 本地构建的 std | stageN-tools |
build-manifest(历史上为 compiletest) |
Mode::ToolRustcPrivate |
与构建 rustc 相同的编译器 | 本地构建的 rustc 及其 rlib | stageN-tools |
rustdoc、clippy、rustfmt、miri、rust-analyzer |
这一分类直接体现在 Mode 枚举定义 中。值得注意,该枚举还包含 Mode::ToolTarget(用于链接器等目标侧工具),但 rustc-dev-guide 的本文档只规范了上述三种面向开发者的模式,下文严格围绕这三种展开。
二、Mode::ToolBootstrap:与仓库编译器无关的通用工具
2.1 适用场景与语义
Mode::ToolBootstrap 适用于不需要 in-tree 编译器任何产物的工具——它们可以用 stage0 编译器(即下载的发布版编译器)直接构建。源码中的注释给出了更精确的语义:
这些工具只预期在调用 bootstrap 的主机上执行,因此不能被交叉编译;它们总是使用 stage0 编译器构建,可以用 stable Rust 编译;它们本质上不参与 staging 流程。
(见 session.rs)
这意味着这类工具是纯 Rust 的通用程序,不依赖任何 nightly 特性,也不依赖仓库中正在开发的编译器内部库。其输出统一放入 "bootstrap-tools" 目录,该目录没有 stage 前缀,因为这类工具只与 stage0 绑定、不随 stage 演进。
2.2 典型注册示例
在 tool.rs 中,通过 bootstrap_tool! 宏注册的工具默认就是 Mode::ToolBootstrap:
bootstrap_tool!(
Rustbook, "src/tools/rustbook", "rustbook", is_external_tool = true, submodules = SUBMODULES_FOR_RUSTBOOK;
UnstableBookGen, "src/tools/unstable-book-gen", "unstable-book-gen";
Tidy, "src/tools/tidy", "tidy";
Linkchecker, "src/tools/linkchecker", "linkchecker";
CargoTest, "src/tools/cargotest", "cargotest";
Compiletest, "src/tools/compiletest", "compiletest";
RemoteTestClient, "src/tools/remote-test-client", "remote-test-client";
RustInstaller, "src/tools/rust-installer", "rust-installer";
RustdocTheme, "src/tools/rustdoc-themes", "rustdoc-themes";
LintDocs, "src/tools/lint-docs", "lint-docs";
JsonDocCk, "src/tools/jsondocck", "jsondocck";
JsonDocLint, "src/tools/jsondoclint", "jsondoclint";
);
这些工具(tidy、linkchecker、rustbook 等)全部是独立于编译器构建链的辅助程序,由宏生成的 Step 结构体在 run() 中默认填入 mode: Mode::ToolBootstrap(见 tool.rs)。
2.3 构建细节:sccache 缓存
由于 stage0 编译器变化不频繁、且不依赖当前工作目录中的代码,bootstrap 会对 ToolBootstrap 工具启用 sccache 缓存,前提是非增量构建:
if let Some(ref ccache) = builder.config.ccache
&& matches!(self.mode, Mode::ToolBootstrap)
&& !builder.config.incremental
{
cargo.env("RUSTC_WRAPPER", ccache);
}
(见 tool.rs)注释明确说明 ccache 无法处理增量构建,因此该优化仅在 incremental = false 时生效。
三、Mode::ToolStd:依赖本地 std 的工具
3.1 适用场景与语义
Mode::ToolStd 适用于依赖本地构建的 std 的工具。它的输出进入 "stageN-tools" 目录。在源码注释中该模式被描述为:
使用本地构建的 std 的工具,输出放入 "stageN-tools" 目录。它的使用相当少见;历史上 compiletest 需要它,但现在主要由
test-float-parse使用。
(见 session.rs)
注意这里文档与源码的差异:rustc-dev-guide 本文档写于 compiletest 仍依赖 libtest 的时期,而当前仓库中 compiletest 已改用 Mode::ToolBootstrap(见上文宏注册表),Mode::ToolStd 现在主要由 test-float-parse 与 build-manifest 使用。阅读时应以当前仓库源码为准。
3.2 典型注册示例:build-manifest
当前仓库中 Mode::ToolStd 的典型使用者是 build-manifest(发布清单生成工具)。其 Step 实现非常简洁:
fn run(self, builder: &Builder<'_>) -> ToolBuildResult {
// Building with the beta compiler will produce a broken build-manifest that doesn't support
// recently stabilized targets/hosts.
assert!(self.compiler.stage != 0);
builder.ensure(ToolBuild {
build_compiler: self.compiler,
target: self.target,
tool: "build-manifest",
mode: Mode::ToolStd,
path: "src/tools/build-manifest",
source_type: SourceType::InTree,
extra_features: vec![],
allow_features: "",
cargo_args: vec![],
artifact_kind: ToolArtifactKind::Binary,
})
}
(见 tool.rs)
其中 assert!(self.compiler.stage != 0) 是一个重要的实践细节:build-manifest 必须用非 stage0 的本地编译器构建,因为用 beta 编译器构建会产生不识别最新稳定化 target/host 的损坏清单。
3.3 构建流程差异
在 ToolBuild::run() 中,Mode::ToolStd 会先确保本地 std 已构建:
Mode::ToolStd => {
// If compiler was forced, its artifacts should have been prepared earlier.
if !self.build_compiler.is_forced_compiler() {
builder.std(self.build_compiler, target);
}
}
(见 tool.rs)
builder.std 对应 compile.rs 中的 Std 步骤——它使用给定的 build_compiler 为指定 target 构建标准库。is_forced_compiler() 分支处理的是 download-rustc 场景:若编译器是被强制指定的(直接使用下载的编译器),其产物应已预先准备好,无需重复构建。
四、Mode::ToolRustcPrivate:链接 rustc 内部库的工具
4.1 适用场景与语义
Mode::ToolRustcPrivate 是三种模式中最复杂的一种,适用于使用 rustc_private 机制、依赖本地构建的 rustc 及其 rlib 产物的工具。源码注释将其定位为:
使用
rustc_private机制、因此依赖本地构建的 rustc rlib 产物的工具,输出放入 "stageN-tools" 目录。它用于一切把 rustc 当作库链接的工具,例如 rustdoc、clippy、rustfmt、miri 等。
(见 session.rs)
所谓 rustc_private,是指这些工具在其 Cargo.toml 中通过 extern crate rustc_driver、rustc_interface 等方式直接链接 rustc 的私有 crate。这要求:
- 工具必须用与构建 rustc 相同的编译器来编译(否则 rlib 的 ABI/元数据不兼容);
- 工具链接到的是本地构建出的 rustc rlib 产物,而非发布版。
4.2 ToolBuild 自动处理复杂性
文档明确指出:当你选择 Mode::ToolRustcPrivate 时,ToolBuild 实现会自动处理好上述复杂依赖。在 tool.rs 的 run() 中可以看到,ToolRustcPrivate 模式会先确保本地 std 与本地 rustc 都已构建:
Mode::ToolRustcPrivate => {
// FIXME: remove this, it's only needed for download-rustc...
if !self.build_compiler.is_forced_compiler() && builder.download_rustc() {
builder.std(self.build_compiler, self.build_compiler.host);
builder.ensure(compile::Rustc::new(self.build_compiler, target));
}
}
同时,prepare_tool_cargo 会为 ToolRustcPrivate 工具集中注入 rustc 私有库的链接路径:
// Make sure we explicitly add rustc_private libs to path centrally here so that
// RustcPrivate tools can pick them up.
if mode == Mode::ToolRustcPrivate {
cargo.add_rustc_lib_path(builder);
}
(见 tool.rs)
4.3 双编译器模型:RustcPrivateCompilers
ToolRustcPrivate 工具的编译涉及两个编译器,源码中用 RustcPrivateCompilers 结构体专门建模(见 tool.rs):
- build_compiler(stage N-1)编译 target_compiler(stage N)以产出 .rlib
- 这些 .rlib 被复制进 build_compiler 的 sysroot
- build_compiler(stage N-1)编译 <tool>(stage N)
- <tool> 链接来自 target_compiler 的 .rlib
即:stage N-1 的编译器负责构建一切,包括 stage N 的 rustc 和工具本身;工具链接到的是 stage N rustc 的 rlib 产物。RustcPrivateCompilers::new(builder, stage, target) 自动完成两个编译器的推导:
pub fn new(builder: &Builder<'_>, stage: u32, target: TargetSelection) -> Self {
let build_compiler = Self::build_compiler_from_stage(builder, stage);
// This is the compiler we'll link to
let target_compiler = builder.compiler(build_compiler.stage + 1, target);
Self { build_compiler, target_compiler }
}
(见 tool.rs)
4.4 典型注册示例
rust-analyzer 是 ToolRustcPrivate 的代表性使用者,还额外启用了 in-rust-tree feature:
builder.ensure(ToolBuild {
build_compiler,
target,
tool: "rust-analyzer",
mode: Mode::ToolRustcPrivate,
path: "src/tools/rust-analyzer",
extra_features: vec!["in-rust-tree".to_owned()],
source_type: SourceType::InTree,
allow_features: RustAnalyzer::ALLOW_FEATURES,
...
})
(见 tool.rs)
error_index_generator(错误码索引生成器)同样是 ToolRustcPrivate 工具(见 tool.rs),它需要读取 rustc 内部的错误码信息,因此必须链接到 in-tree 的 rustc。
五、Step 骨架:ToolBuild 与 ToolBuildResult
5.1 通用约定
无论选择哪种模式,工具的 Step 实现都必须遵循两条规则(rustc-dev-guide 原文核心约定):
Step的Output类型必须是ToolBuildResult;Step的run()内部必须使用ToolBuild(通常通过builder.ensure(ToolBuild { ... }))。
ToolBuild 结构体(见 tool.rs)承载了构建一个工具所需的全部配置:
struct ToolBuild {
build_compiler: Compiler, // 构建该工具的编译器
target: TargetSelection, // 目标平台
tool: &'static str, // 工具名(用于产物命名与 toolstate 上报)
path: &'static str, // 工具源码在仓库中的相对路径
mode: Mode, // 本文介绍的三种模式之一
source_type: SourceType, // InTree 或 Submodule
extra_features: Vec<String>, // 始终启用的 feature(如 rust-analyzer 的 in-rust-tree)
allow_features: &'static str, // 允许的 nightly-only feature(逗号分隔)
cargo_args: Vec<String>, // 额外传给 cargo 的参数
artifact_kind: ToolArtifactKind, // Binary 或 Library
}
ToolBuildResult(见 tool.rs)则是构建的返回值:
pub struct ToolBuildResult {
pub tool_path: PathBuf, // 构建出的工具产物路径
pub build_compiler: Compiler, // 用于构建该工具的编译器
pub artifacts: Vec<PathBuf>, // 编译过程中产生的所有 Cargo 产物
}
文档特别提示:如果你需要用 builder 的编译器做某些特定的事情,可以从工具 Step 返回的 ToolBuildResult 中获取——即通过 result.build_compiler 拿到实际参与构建的编译器实例。
5.2 最小 Step 模板
综合文档约定与源码实现(如 BuildManifest 的 Step),一个最小化的工具 Step 模板如下:
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct MyTool {
pub compiler: Compiler,
pub target: TargetSelection,
}
impl CommandLineStep for MyTool {
type Output = ToolBuildResult;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/my-tool") // 关联仓库路径,支持 `x build src/tools/my-tool`
}
fn make_run(run: RunConfig<'_>) {
run.builder.ensure(MyTool {
compiler: run.builder.compiler(0, run.builder.config.host_target),
target: run.target,
});
}
fn run(self, builder: &Builder<'_>) -> ToolBuildResult {
builder.ensure(ToolBuild {
build_compiler: self.compiler,
target: self.target,
tool: "my-tool",
mode: Mode::ToolBootstrap, // 按需选择三种模式
path: "src/tools/my-tool",
source_type: SourceType::InTree,
extra_features: vec![],
allow_features: "",
cargo_args: vec![],
artifact_kind: ToolArtifactKind::Binary,
})
}
}
5.3 bootstrap_tool! 宏:批量注册的便捷通道
对于最常见的 ToolBootstrap 工具,bootstrap 提供了 bootstrap_tool! 宏(见 tool.rs)来避免手写样板代码。它自动生成:
Tool枚举及对应变体;Builder::tool_exe(tool)与Builder::tool(tool)便捷方法(前者直接返回tool_path,后者返回完整的ToolBuildResult);- 每个工具的
Step结构体及CommandLineStep实现,默认使用 stage0 编译器、Mode::ToolBootstrap。
宏还支持可选项:is_external_tool(标记依赖 submodule 的外部工具)、allow_features(允许的 nightly feature)、submodules(构建前需确保的 submodule)、artifact_kind(构建二进制或库)。
六、输出目录规则:从源码理解目录命名
三种模式的输出目录差异在 session.rs 的 stage_out 实现 中有最权威的映射:
fn stage_out(&self, build_compiler: Compiler, mode: Mode) -> PathBuf {
let (stage, suffix) = match mode {
Mode::Std => (Some(build_compiler.stage), "std"),
Mode::Rustc => (Some(build_compiler.stage + 1), "rustc"),
Mode::Codegen => (Some(build_compiler.stage + 1), "codegen"),
Mode::ToolBootstrap => (None, "bootstrap-tools"), // 无 stage 前缀
Mode::ToolStd | Mode::ToolRustcPrivate =>
(Some(build_compiler.stage + 1), "tools"), // stageN-tools
...
};
...
}
由此可以精确总结:
Mode::ToolBootstrap→build/{host}/bootstrap-tools/,无 stage 前缀,体现其"与 staging 无关"的语义;Mode::ToolStd/Mode::ToolRustcPrivate→build/{host}/stage{N+1}-tools/,其中 N 是build_compiler的 stage,因此 stage0 编译器构建出的工具出现在stage1-tools;- 工具可执行文件的最终落点是
tools_dir(见 session.rs),即build/{host}/stage{N+1}-tools-bin/,由copy_link_tool_bin(见 tool.rs)把 cargo 输出目录中的产物 copy/link 过去。
一个值得留意的细节:在 Windows 上,tools 目录会被加入 PATH 参与测试,为避免 tidy 与 HTML tidy 混淆,in-tree tidy 会被改名为 rust-tidy(见 tool.rs)。
七、配置与命令行操作
7.1 通过 x.py 构建工具
仓库根目录使用 x(或 x.py)驱动 bootstrap。构建某个工具最常见的方式是按路径触发:
# 构建 rustdoc(ToolRustcPrivate)
./x build src/tools/rustdoc
# 构建 tidy(ToolBootstrap)
./x build src/tools/tidy
# 构建 compiletest(ToolBootstrap)
./x build src/tools/compiletest
should_run 中的 run.path("src/tools/my-tool") 正是这种路径触发方式的注册点。工具的构建会经由 Builder::ensure 的依赖图自动拉取所需前置产物(std / rustc)。
7.2 通过 bootstrap.toml 配置工具的 features
prepare_tool_cargo 支持在 config.toml(即 bootstrap.toml)中按工具名配置额外 feature(见 tool.rs):
# config.toml 示例:为特定工具追加 Cargo features
[build]
# ...
源码中的映射逻辑为:build.tool.TOOL_NAME.features 中列出的 feature 会并入 cargo 的 --features 参数,与 extra_features(硬编码、始终启用)及 prepare_tool_cargo 内部根据全局配置追加的 feature(如 cargo_native_static 时为 cargo 追加 all-static)合并。此外,prepare_tool_cargo 还会统一注入一批环境变量,包括:
SYSROOT:当前编译器的 sysroot,供 clippy 测试等使用;CFG_RELEASE、CFG_RELEASE_CHANNEL、CFG_VERSION、CFG_RELEASE_NUM:rustfmt 等工具引入rustc-ap-rustc_attr时#[cfg(version(...))]所需;CFG_COMMIT_HASH、CFG_COMMIT_DATE等 git 元信息;- 除 cargo 外统一设置
FORCE_ON_BROKEN_PIPE_KILL = -Zon-broken-pipe=kill,避免 rustc/rustdoc 在管道断裂时 ICE; - 对所有工具统一传入
-Zunstable-options,且该 flag 不写入RUSTFLAGS,以免破坏 cargo 的增量缓存。
7.3 高级构建优化:LTO 与 PGO
ToolBuild::run() 还针对 ToolRustcPrivate 工具(以及 cargo)做了 LTO 支持(见 tool.rs):当处于 LTO stage 时,会按 rust.lto 配置(off / thin / fat / thin-local)设置对应的 profile 环境变量,让 miri、clippy、rustfmt、rust-analyzer 等获得额外优化。
PGO 方面,rustdoc / cargo / clippy 各自有独立的 PGO 配置,构建时通过 apply_pgo 应用:
let pgo_config = match self.path {
"src/tools/rustdoc" => Some(&builder.config.rustdoc_pgo),
"src/tools/cargo" => Some(&builder.config.cargo_pgo),
"src/tools/clippy" => Some(&builder.config.clippy_pgo),
_ => None,
};
7.4 toolstate 上报
工具构建的结果会写入 toolstate(见 tool.rs):构建成功但测试失败记为 ToolState::TestFail,构建失败记为 ToolState::BuildFail。这是 rustc 仓库对工具健康状态进行追踪的机制,tools 的 CI 状态由此驱动。
八、如何为你的工具选择模式:决策清单
结合文档与源码语义,选择模式时可遵循如下判断顺序:
- 工具是否完全不依赖 in-tree 编译器产物,且只在本机执行? →
Mode::ToolBootstrap。这是最轻量的选择,可用 stable Rust 编写,用 stage0 编译,产物进bootstrap-tools。注意它不可交叉编译。 - 工具是否仅需本地构建的 std(如需要
libtest或想要新 std 修复),且不需要链接 rustc 内部库? →Mode::ToolStd。产物进stageN-tools,构建前会自动builder.std(...)。 - 工具是否需要把 rustc 当作库使用(
rustc_private,如访问 HIR、查询系统、错误码等)? →Mode::ToolRustcPrivate。ToolBuild会自动处理本地 rustc 及其 rlib 的依赖,产物进stageN-tools。注意构建期涉及build_compiler与target_compiler两个编译器,若需要编译器实例可从容器的ToolBuildResult.build_compiler中获取。
无论哪种模式,都不要忘记:Step 的 Output 必须是 ToolBuildResult,run() 中通过 builder.ensure(ToolBuild { ... }) 完成实际构建,并在必要时访问返回结果中的 tool_path、build_compiler 与 artifacts。
结语
bootstrap 的工具构建体系看似简单(只有三种模式),但其背后是一套精心设计的依赖与目录管理机制:ToolBootstrap 保证了辅助工具的轻量独立,ToolStd 让工具能享受本地 std 的新特性,ToolRustcPrivate 则让 rustdoc、clippy、rustfmt、miri 等重量级工具能够以库的形式复用编译器内部实现。理解 Mode 枚举、ToolBuild 结构体 与 stage_out 目录映射 这三处核心源码,就掌握了在 rustc 仓库中新增或改造工具的全部关键知识。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.2 K634
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown300
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java101
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java60
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript60
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python280