Rust 编译器 bootstrap 构建系统调试指南:从 println 日志到 tracing 结构化追踪
本指南面向需要排查 Rust 编译器(rustc)引导构建(bootstrap)问题的开发者,系统讲解 bootstrap 提供的两套调试与性能剖析手段:基于 --verbose 标志的 println 非结构化日志,以及基于 tracing 条件特性(feature)的结构化日志、Chrome 火焰图、步骤依赖图与命令执行统计。读完本文,你将掌握通过 ./x 命令观察构建细节、用 BOOTSTRAP_TRACING 环境变量按日志级别与 target 精确过滤追踪信息,并理解这些能力的源码实现位置,能够直接运用于 rustc 源码开发与构建问题定位。
概述:bootstrap 调试的两条主线
bootstrap 是 Rust 仓库中负责构建 rustc、标准库及各工具的构建系统(入口为 src/bootstrap/src/cli_main.rs)。其调试与剖析方式分为两类:
- println 非结构化日志:通过向
./x传递-v/-vv获得更详尽的执行细节,无需任何特性开关,开箱即用; tracing条件特性:通过设置BOOTSTRAP_TRACING环境变量启用,提供结构化事件(event)、跨度(span)、Chrome trace 文件、GraphViz 步骤依赖图和命令执行汇总,适合深入分析与性能剖析。
两条主线互补:前者适合快速查看,后者适合系统化分析。下文分别展开,并结合仓库源码说明其底层实现。
println 日志:用 -v / -vv 观察执行细节
bootstrap 内置了大量非结构化日志,其中绝大多数受 --verbose 标志控制,传递 -vv 可获得更多细节。当你需要查看执行的 Cargo 命令和其他详细日志时,可在调用 bootstrap 时加上 -v 或 -vv:
$ ./x dist rustc --dry-run -vv
learning about cargo
running: RUSTC_BOOTSTRAP="1" "/home/jyn/src/rust2/build/x86_64-unknown-linux-gnu/stage0/bin/cargo" "metadata" "--format-version" "1" "--no-deps" "--manifest-path" "/home/jyn/src/rust2/Cargo.toml" (failure_mode=Exit) (created at src/bootstrap/src/core/metadata.rs:81:25, executed at src/bootstrap/src/core/metadata.rs:92:50)
running: RUSTC_BOOTSTRAP="1" "/home/jyn/src/rust2/build/x86_64-unknown-linux-gnu/stage0/bin/cargo" "metadata" "--format-version" "1" "--no-deps" "--manifest-path" "/home/jyn/src/rust2/library/Cargo.toml" (failure_mode=Exit) (created at src/bootstrap/src/core/metadata.rs:81:25, executed at src/bootstrap/src/core/metadata.rs:92:50)
...
从输出可见,bootstrap 会打印出实际执行的 Cargo 命令全文(含 RUSTC_BOOTSTRAP=1 环境变量、完整参数),并附上该命令在源码中创建与执行的位置(created at ... / executed at ...),便于直接跳到对应代码行排查。需要提醒的是,这些日志非结构化,且量可能很大,对 -vv 输出要有心理准备,必要时配合管道与 grep 过滤使用。
tracing 特性:bootstrap 的结构化观测能力
bootstrap 定义了一个条件编译的 tracing Cargo feature(在 src/bootstrap/Cargo.toml 中声明),启用后提供以下能力:
- 结构化日志:基于
tracing的 events 与 spans,带时间戳、日志级别、字段值与源码位置; - Chrome trace 文件:生成
chrome-trace.json,可用 Chrome 浏览器的chrome://tracing标签页打开,或使用 Perfetto 等工具查看,直观呈现各步骤与命令的层级关系和耗时; - GraphViz 步骤依赖图:生成
step-graph-*.dot文件,展示已执行步骤之间的依赖关系,可用 xdot 等工具可视化,或用dot -Tsvg转换为 SVG; - 命令执行汇总:生成
command-stats.txt,以易读的纯文本格式展示哪些命令被执行、多少次执行命中了缓存、哪些命令最耗时。
输出位置约定:结构化日志写入标准错误输出(stderr),其余产物存放在 <build-dir>/bootstrap-trace/<pid> 目录中(其中 <build-dir> 即构建输出目录,如 build/)。为方便取用,bootstrap 还会在 <build-dir>/bootstrap-trace/latest 创建指向最近一次 trace 输出目录的符号链接。
注意:如果以
--dry-run模式执行 bootstrap,trace 输出目录可能发生变化;bootstrap 总会在执行结束时打印 trace 输出文件实际存放的路径。
这一输出目录与符号链接的创建逻辑位于 src/bootstrap/src/cli_main.rs:先构造 out_dir/bootstrap-trace/<pid> 并清空重建,随后在 Unix 上用 std::os::unix::fs::symlink、在 Windows 上用 junction 创建 latest 链接。整个流程收尾时(cli_main.rs)依次调用 sess.report_summary(写 command-stats.txt)、sess.report_step_graph(写 step-graph-*.dot)和 guard.copy_to_dir(落盘 chrome-trace.json),最后向 stderr 打印输出目录路径。
启用 tracing:BOOTSTRAP_TRACING 环境变量
启用条件特性只需在调用 bootstrap 时设置 BOOTSTRAP_TRACING 环境变量:
$ BOOTSTRAP_TRACING=trace ./x build library --stage 1
示例输出(该输出不保证稳定,可能随版本演进变化):
$ BOOTSTRAP_TRACING=trace ./x build library --stage 1 --dry-run
Building bootstrap
Finished `dev` profile [unoptimized] target(s) in 0.05s
15:56:52.477 INFO > tool::LibcxxVersionTool {target: x86_64-unknown-linux-gnu} (builder/mod.rs:1715)
15:56:52.575 INFO > compile::Assemble {target_compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }} (builder/mod.rs:1715)
15:56:52.575 INFO > tool::Compiletest {compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, target: x86_64-unknown-linux-gnu} (builder/mod.rs:1715)
15:56:52.576 INFO > tool::ToolBuild {build_compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, target: x86_64-unknown-linux-gnu, tool: "compiletest", path: "src/tools/compiletest", mode: ToolBootstrap, source_type: InTree, extra_features: [], allow_features: "internal_output_capture", cargo_args: [], artifact_kind: Binary} (builder/mod.rs:1715)
15:56:52.576 INFO > builder::Libdir {compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, target: x86_64-unknown-linux-gnu} (builder/mod.rs:1715)
15:56:52.576 INFO > compile::Sysroot {compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, force_recompile: false} (builder/mod.rs:1715)
15:56:52.578 INFO > compile::Assemble {target_compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }} (builder/mod.rs:1715)
15:56:52.578 INFO > tool::Compiletest {compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, target: x86_64-unknown-linux-gnu} (builder/mod.rs:1715)
15:56:52.578 INFO > tool::ToolBuild {build_compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, target: x86_64-unknown-linux-gnu, tool: "compiletest", path: "src/tools/compiletest", mode: ToolBootstrap, source_type: InTree, extra_features: [], allow_features: "internal_output_capture", cargo_args: [], artifact_kind: Binary} (builder/mod.rs:1715)
15:56:52.578 INFO > builder::Libdir {compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, target: x86_64-unknown-linux-gnu} (builder/mod.rs:1715)
15:56:52.578 INFO > compile::Sysroot {compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, force_recompile: false} (builder/mod.rs:1715)
Finished `release` profile [optimized] target(s) in 0.11s
Tracing/profiling output has been written to <src-root>/build/bootstrap-trace/latest
Build completed successfully in 0:00:00
从输出可以看到 tracing 日志的几个特征:
- 每行带
HH:MM:SS.mmm时间戳与右对齐的日志级别(如INFO); >符号前的缩进反映了 span 的嵌套层级,步骤之间的父子关系一目了然;- 每个 span 携带丰富的字段值,例如
tool::ToolBuild中的tool、path、mode、source_type、artifact_kind等; - 行尾的
(builder/mod.rs:1715)是缩短后的源码位置(仅保留最后两级路径),可据此跳转源码。
该输出的渲染由 src/bootstrap/src/utils/tracing.rs 中的 TracingPrinter(实现 tracing_subscriber 的 Layer)完成:on_new_span 记录 span 字段,on_enter 打印带缩进的 span 进入行,on_event 将事件写入 stderr,缩进深度用原子计数器维护。步骤 span(target 为 STEP)与命令 span(target 为 COMMAND)会被特殊处理以显示动态名称(见下文实现剖析)。
控制 tracing 输出:日志级别与 target 过滤
BOOTSTRAP_TRACING 的值是一个 tracing_subscriber 的 EnvFilter 过滤器。直接设置 BOOTSTRAP_TRACING=trace 会开启全部日志,但信息量往往过大,因此可以用过滤器按两个正交维度收窄:
- 按日志级别(level):如
debug、trace。选择某个级别后,会显示该级别及更高优先级的所有 events/spans(级别从高到低依次为 error、warn、info、debug、trace)。 - 按日志 target:如
bootstrap、bootstrap::core::config,或下面介绍的自定义 target(如CONFIG_HANDLING、STEP)。
自定义 target 用于精确限定感兴趣的 span 类别,因为 BOOTSTRAP_TRACING=trace 的完整输出相当冗长。当前支持以下自定义 target:
| target | 作用 | 事件级别 |
|---|---|---|
CONFIG_HANDLING |
与配置处理相关的 spans | — |
STEP |
所有已执行的步骤 | 执行的命令为 info 级别 |
COMMAND |
所有已执行的命令 | trace 级别 |
IO |
已执行的 I/O 操作 | trace 级别(注意:当前许多 I/O 尚未被追踪) |
这些 target 可以组合使用(自定义 target 的日志通常额外受 TRACE 日志级别门槛限制):
$ BOOTSTRAP_TRACING=CONFIG_HANDLING=trace,STEP=info,COMMAND=trace ./x build library --stage 1
上面的命令等价于:开启配置处理相关 span 的 trace 日志、步骤(STEP)的 info 日志与命令(COMMAND)的 trace 日志,从而在保留关键结构信息的同时压缩输出量。
需要留意的是,BOOTSTRAP_TRACING 指定的级别同样会影响记录进 Chrome trace 文件的 span 范围——级别筛选不仅作用于 stderr 文本输出,也作用于 Chrome 火焰图的采样。
在源码层面,过滤器解析发生在 src/bootstrap/src/utils/tracing.rs:setup_tracing 通过 EnvFilter::from_env(env_name) 从环境变量构造过滤器,再与 TracingPrinter 一起注册为全局默认 subscriber。自定义 target 在源码中的常量定义包括:STEP_SPAN_TARGET = "STEP"(位于 src/bootstrap/src/core/builder/mod.rs)、COMMAND_SPAN_TARGET = "COMMAND" 与 IO_SPAN_TARGET = "IO"(位于 src/bootstrap/src/utils/tracing.rs)。
COMPILER 与 COMPILER_FOR:特殊追踪 target
除上述 target 外,还有两个附加 target:COMPILER 和 COMPILER_FOR,用于追踪 builder.compiler() 与 builder.compiler_for() 的内部行为。这两个 target 属于临时性调试设施,在 rust-lang/rust 的 issue #96176 解决后应当移除。
跳过时间戳:BOOTSTRAP_TRACING_SKIP_TIME
如需对比两个 bootstrap 版本之间执行的步骤差异,可设置 BOOTSTRAP_TRACING_SKIP_TIME=1 来去掉 tracing 输出中的时间戳,从而使输出具备可 diff 性:
$ BOOTSTRAP_TRACING_SKIP_TIME=1 BOOTSTRAP_TRACING=trace ./x build library --stage 1
实现上,src/bootstrap/src/utils/tracing.rs 读取 BOOTSTRAP_TRACING_SKIP_TIME,当值等于 "1" 时关闭 TracingPrinter 的时间戳打印(format_header 仅在 show_time 为真时输出 %H:%M:%S.%3f 格式的定宽时间)。
在 bootstrap 源码中使用 tracing
如果你在修改 bootstrap 源码并希望加入自己的追踪点,需要遵循两条规则(详见 src/bootstrap/src/utils/tracing.rs 的注释说明):
- 日志宏必须经过包装:
tracing的trace!、debug!、warn!、info!、error!宏不能直接使用,而应使用 bootstrap 提供的包装宏(同样定义在 src/bootstrap/src/utils/tracing.rs)。这些宏在feature = "tracing"关闭时展开为空,保证条件编译下零开销; #[instrument]需要条件编译门控:tracing的#[instrument(..)]属性宏须写成#[cfg_attr(feature = "tracing", instrument(..))]的形式。
示例:
#[cfg(feature = "tracing")]
use tracing::instrument;
struct Foo;
impl Step for Foo {
type Output = ();
#[cfg_attr(feature = "tracing", instrument(level = "trace", name = "Foo::should_run", skip_all))]
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
trace!(?run, "entered Foo::should_run");
todo!()
}
fn run(self, builder: &Builder<'_>) -> Self::Output {
trace!(?run, "entered Foo::run");
todo!()
}
}
对 #[instrument] 的使用建议:
- 对细粒度函数使用
trace级别,对核心函数可使用debug级别; - 通过
name = ".."显式指定 instrumentation 名称,以区分不同 Step 的同名方法(如各自的run); - 注意不要让 tracing 基础设施的启用导致行为分叉(例如仅在 tracing 开启时额外构建东西)。
此外,bootstrap 还提供了 trace_io! 宏(src/bootstrap/src/utils/tracing.rs),用于为 I/O 操作创建 target 为 IO 的 span,自动附带调用位置信息,与 IO 自定义 target 对应。
步骤与命令 span 的动态命名实现
tracing 的 span 名称本身不支持动态设置,而每个 bootstrap Step 的名称(如 tool::LibcxxVersionTool)各不相同。为解决这一问题,src/bootstrap/src/core/builder/mod.rs 在 Builder::ensure 中创建 target 为 STEP_SPAN_TARGET 的 info_span!,把真实步骤名放在 step_name 字段中;TracingPrinter 与 Chrome layer 则通过 tracing_subscriber 的 span extensions 机制(StepNameExtension / CommandNameExtension)读取并覆盖显示名称,参见 src/bootstrap/src/utils/tracing.rs 与 src/bootstrap/src/utils/tracing.rs。命令 span 的字段则由 src/bootstrap/src/utils/tracing.rs 的 trace_cmd 生成,包含命令指纹的程序名、短命令与完整命令。
输出文件详解:Chrome trace、步骤图与命令统计
Chrome trace:chrome-trace.json
Chrome trace 文件由 tracing-chrome 库生成,记录所有启用了 tracing 的 span 的进入/退出时间与嵌套关系,形成瀑布式时间线。在 setup_tracing 中(src/bootstrap/src/utils/tracing.rs),由于配置解析完成前还不知道输出目录位置,trace 先写入临时目录的 bootstrap-trace.json,待 TracingGuard::copy_to_dir 时再移动到 <build-dir>/bootstrap-trace/<pid>/chrome-trace.json。打开方式:
- Chrome 地址栏输入
chrome://tracing,加载该 JSON; - 或使用 Perfetto(
ui.perfetto.dev)等在线工具直接打开。
步骤依赖图:step-graph-*.dot
步骤图由 src/bootstrap/src/utils/step_graph.rs 实现:StepGraph::register_step_execution 与 register_cached_step 在步骤执行与缓存命中时记录节点和边,store_to_dot_files 把每个图渲染为 step-graph{key}.dot。图中:
- 实线边表示步骤首次实际执行;
- 虚线边表示步骤执行命中了缓存(
cached标记); - 每个边还带有调用位置的 tooltip;
- 文件名中的
key区分 dry-run 与否:dry-run 模式对应step-graph.dryrun.dot,正常模式对应step-graph.dot。
查看方式:用 xdot 打开 .dot 文件,或用 GraphViz 的命令行工具转换格式:
$ dot -Tsvg step-graph.dot -o step-graph.svg
如果想只关注首次执行(忽略虚线缓存边),可以按 src/bootstrap/src/utils/step_graph.rs 的注释提示,在 DotGraph 渲染时强制 cached: false(该处为源码行为说明,修改需自行承担)。
命令执行统计:command-stats.txt
命令统计由 src/bootstrap/src/utils/exec.rs 的 CommandProfiler 生成:每次命令执行通过 record_execution 记录耗时,缓存命中通过 record_cache_hit 记录。report_summary 按最大耗时降序输出每个命令的执行次数、缓存命中次数与累计耗时,并汇总总缓存命中数与总执行时长,最终写入 command-stats.txt。这个文件能直接回答"哪些命令最慢、哪些命令频繁被缓存"的问题,是性能剖析的首选入口。
局限性与注意事项
--dry-run对输出目录的影响:dry-run 模式下 trace 输出目录可能变化,以执行结束时打印的路径为准(见 src/bootstrap/src/cli_main.rs);- rust-analyzer 集成受限:bootstrap 是
rust-analyzer.linkedProjects之一,但由于 rust-analyzer 对在 linked project 中以特定 feature(此处为tracing)构建/检查自身的支持尚不完善(对应 rust-analyzer issue #8521),目前无法让 rust-analyzer 在启用tracing特性时检查 bootstrap 以获得相关补全; - 输出不稳定:tracing 的文本输出格式始终可能随版本演进而变化,脚本化解析时需注意;
- I/O 追踪不完整:
IOtarget 只能覆盖已接入trace_io!的 I/O 操作,当前尚有大量 I/O 未被追踪; - 外部工具依赖:Chrome trace 需要 Chrome/Perfetto,步骤图需要 GraphViz/xdot 等工具配合查看,这些工具不在本仓库内,需自行准备。
结语
bootstrap 的调试体系围绕"非结构化日志(-v/-vv)"与"结构化 tracing(BOOTSTRAP_TRACING)"两条路径展开:前者即开即用,适合快速排查;后者提供带时间线的 Chrome trace、可视化步骤依赖图和命令缓存/耗时统计,适合深入分析构建流程与性能。理解这些机制对应的源码位置(src/bootstrap/src/utils/tracing.rs、src/bootstrap/src/utils/step_graph.rs、src/bootstrap/src/utils/exec.rs、src/bootstrap/src/cli_main.rs),将帮助你在排查 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.21 K635- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python60
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java131
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java80
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript90
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python290