首页
/ Rust Bootstrap 工具编写指南:ToolBootstrap / ToolStd / ToolRustcPrivate 三种模式详解

Rust Bootstrap 工具编写指南:ToolBootstrap / ToolStd / ToolRustcPrivate 三种模式详解

2026-09-10 10:41:26作者:邬祺芯Juliet

导读

rustc 的构建系统(bootstrap)在编译 Rust 编译器本身之外,还需要构建大量辅助工具,例如 rustdoc、clippy、rustfmt、miri、tidy 等。本指南基于 rustc-dev-guide 中 writing-tools-in-bootstrap 一文,系统讲解 bootstrap 中编写工具的三种模式(Mode::ToolBootstrapMode::ToolStdMode::ToolRustcPrivate)的适用场景、输出目录、底层实现与注册方式。读完本文,你将掌握如何在 rustc 仓库中为新增工具选择合适的模式、编写对应的 Step 实现,并理解 ToolBuildToolBuildResult 在整个构建流程中的真实运作机制。

一、三种工具模式总览

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_driverrustc_interface 等方式直接链接 rustc 的私有 crate。这要求:

  1. 工具必须用与构建 rustc 相同的编译器来编译(否则 rlib 的 ABI/元数据不兼容);
  2. 工具链接到的是本地构建出的 rustc rlib 产物,而非发布版。

4.2 ToolBuild 自动处理复杂性

文档明确指出:当你选择 Mode::ToolRustcPrivate 时,ToolBuild 实现会自动处理好上述复杂依赖。在 tool.rsrun() 中可以看到,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 原文核心约定):

  1. StepOutput 类型必须是 ToolBuildResult
  2. Steprun() 内部必须使用 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::ToolBootstrapbuild/{host}/bootstrap-tools/,无 stage 前缀,体现其"与 staging 无关"的语义;
  • Mode::ToolStd / Mode::ToolRustcPrivatebuild/{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_RELEASECFG_RELEASE_CHANNELCFG_VERSIONCFG_RELEASE_NUM:rustfmt 等工具引入 rustc-ap-rustc_attr#[cfg(version(...))] 所需;
  • CFG_COMMIT_HASHCFG_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 状态由此驱动。

八、如何为你的工具选择模式:决策清单

结合文档与源码语义,选择模式时可遵循如下判断顺序:

  1. 工具是否完全不依赖 in-tree 编译器产物,且只在本机执行?Mode::ToolBootstrap。这是最轻量的选择,可用 stable Rust 编写,用 stage0 编译,产物进 bootstrap-tools。注意它不可交叉编译。
  2. 工具是否仅需本地构建的 std(如需要 libtest 或想要新 std 修复),且不需要链接 rustc 内部库?Mode::ToolStd。产物进 stageN-tools,构建前会自动 builder.std(...)
  3. 工具是否需要把 rustc 当作库使用(rustc_private,如访问 HIR、查询系统、错误码等)?Mode::ToolRustcPrivateToolBuild 会自动处理本地 rustc 及其 rlib 的依赖,产物进 stageN-tools。注意构建期涉及 build_compilertarget_compiler 两个编译器,若需要编译器实例可从容器的 ToolBuildResult.build_compiler 中获取。

无论哪种模式,都不要忘记:StepOutput 必须是 ToolBuildResultrun() 中通过 builder.ensure(ToolBuild { ... }) 完成实际构建,并在必要时访问返回结果中的 tool_pathbuild_compilerartifacts

结语

bootstrap 的工具构建体系看似简单(只有三种模式),但其背后是一套精心设计的依赖与目录管理机制:ToolBootstrap 保证了辅助工具的轻量独立,ToolStd 让工具能享受本地 std 的新特性,ToolRustcPrivate 则让 rustdoc、clippy、rustfmt、miri 等重量级工具能够以库的形式复用编译器内部实现。理解 Mode 枚举ToolBuild 结构体stage_out 目录映射 这三处核心源码,就掌握了在 rustc 仓库中新增或改造工具的全部关键知识。

热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
927
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
603
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
396
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
527