codex-rs 的 Bazel 构建体系:codex_rust_crate 宏、Bzlmod 工具链与 BuildBuddy 远程执行实战
本篇基于仓库内的 Bazel 构建文档 展开,讲清楚 codex-rs Rust 工作区如何用 Bazel 实现 hermetic(可复现、自包含)构建:MODULE.bazel 如何声明工具链、rules_rs 如何从 Cargo.toml/Cargo.lock 导入第三方 crate、defs.bzl 中的 codex_rust_crate 宏如何把 Bazel 目标与 Cargo 约定对齐,以及如何通过 BuildBuddy 配置启用远程缓存与远程执行。读完后,你应能独立完成本地 Bazel 构建与测试、理解各 --config 的作用边界,并知道新增依赖、新增 crate 时的完整操作流程。
需要先说明适用前提:文档中注明,截至 2026-06-01,该 Bazel 体系仍处于实验阶段,正在持续稳定化。日常开发仍可以 Cargo 为主,Bazel 主要用于 CI、跨平台产物和可复现构建。
核心定位:Cargo 是事实来源,Bazel 是构建执行层
原文明确了这一分工原则:
This repository uses Bazel to build the Rust workspace under
codex-rs. Cargo remains the source of truth for crates and features, while Bazel provides hermetic builds, toolchains, and cross-platform artifacts.
这意味着:
- 依赖声明、feature、crate 划分全部以 codex-rs/Cargo.toml 和 codex-rs/Cargo.lock 为准;
- Bazel 负责的是构建可复现性:版本钉死的 Rust 工具链、自包含(hermetic)的 LLVM、macOS SDK 等,以及跨平台交叉编译产物(包括 Windows gnullvm 这种 Cargo 原生不直接支持的 ABI);
- 因此你几乎不需要为 Bazel 维护一套并行的依赖清单——这正是后文
crate.from_cargo(...)要解决的问题。
高层结构:四个关键构件
原文给出了四段式结构,下面逐一结合仓库中的实际文件展开。
1. MODULE.bazel:Bzlmod 依赖与工具链声明
根目录 MODULE.bazel 声明了整个仓库的 Bazel 模块(模块名为 codex),承担三类职责:
(1)外部模块依赖。 关键依赖包括 rules_rs 0.0.96(新一代 Rust 构建规则,见 MODULE.bazel#L93)、llvm 0.8.11(hermetic LLVM,见 MODULE.bazel#L5)、apple_support、aws-lc 等。
(2)上游模块打补丁。 由于 hermetic LLVM 需要一些尚未上游化的定制(例如 V8 所需的 custom libc++、Windows gnullvm 运行时),仓库通过 single_version_override + patches 对 llvm、abseil-cpp、rules_cc、bzip2 等模块打补丁,补丁文件集中在 patches/ 目录下(如 patches/llvm_rusty_v8_custom_libcxx.patch)。对 rules_rust 本身也打了三个补丁(build-script 工具 runfiles、Windows/MSVC 直接链接参数、Windows process wrapper),见 MODULE.bazel#L103-L117。
(3)工具链注册。 包括:
- macOS SDK 归档下载与 framework 白名单(
@llvm//extensions:osx.bzl,见 MODULE.bazel#L31-L78); - 默认 Rust 工具链:
edition = "2024"、version = "1.95.0"(MODULE.bazel#L190-L197); - 一个独立的 nightly 工具链(
nightly/2025-09-18,dev_components = True),专门服务于需要rustc_private的 argument-comment-lint(MODULE.bazel#L123-L131); - Windows 双 ABI 支持:MSVC 与 gnullvm 两套
repository_set(MODULE.bazel#L135-L188)。
本地 Bazel 版本由 .bazelversion 钉在 9.0.0。
2. rules_rs 的 crate.from_cargo(...):从 Cargo 元数据导入第三方 crate
MODULE.bazel#L209-L228 展示了原文提到的核心机制:
crate = use_extension("@rules_rs//rs:extensions.bzl", "crate")
crate.from_cargo(
cargo_lock = "//codex-rs:Cargo.lock",
cargo_toml = "//codex-rs:Cargo.toml",
platform_triples = [
"aarch64-unknown-linux-gnu",
"aarch64-unknown-linux-musl",
"aarch64-apple-darwin",
"aarch64-pc-windows-msvc",
"aarch64-pc-windows-gnullvm",
"x86_64-unknown-linux-gnu",
"x86_64-unknown-linux-musl",
"x86_64-apple-darwin",
"x86_64-pc-windows-msvc",
"x86_64-pc-windows-gnullvm",
],
)
crate.from_cargo 直接解析 Cargo 工作区的 Cargo.toml 与 Cargo.lock,为每个第三方 crate 生成 Bazel 目标并统一暴露在 @crates 仓库下。platform_triples 列表同时列出了 9 种目标 triple(含 Linux musl、macOS、Windows MSVC 与 gnullvm),这正是"跨平台产物"能力的来源——同一次依赖解析覆盖了所有交叉编译目标。注释中还解释了为何同时保留 MSVC 与 gnullvm 两个 Windows triple:V8 实验仍在消费仅以 MSVC 命名发布的 release 资产。
对于需要"特殊照顾"的上游 crate,仓库用 crate.annotation 做定点修正,例如:
blake3:在 Windows gnullvm 上强制启用purefeature,因为该工具链无法可靠地生成其 x86 原生汇编(MODULE.bazel#L249-L255);zstd-sys、ring:打补丁修正 MSVC 头文件搜索路径(MODULE.bazel#L256-L270);aws-lc-sys/aws-lc-rs:关闭 build script,改用预生成 bindgen 产物(MODULE.bazel#L271-L284)。
这就是原文"Evolving the setup"一节所说"上游 crate 可能需要 patch 或 crate.annotation 才能在 Bazel 沙箱中构建"的具体形态。
3. defs.bzl 的 codex_rust_crate:与 Cargo 约定对齐的宏
根目录 defs.bzl 提供 codex_rust_crate 宏(defs.bzl#L181),封装 rust_library、rust_binary、rust_test,让 Bazel 目标与 Cargo 的目录约定一一对应。原文说它"提供了对大多数一方 crate 合理的默认值,但某些情况下需要微调"。
从宏的参数列表(defs.bazel#L181-L206)可以看到它的覆盖面:
| 参数 | 作用 |
|---|---|
crate_name / crate_features / crate_edition |
对应 Cargo.toml 中的名称、feature、edition。宏文档特别提示 feature 会在整个工作区以单一配置编译(列表内全部启用),应慎用 |
build_script_data |
暴露给 build.rs 运行时使用的数据文件 |
compile_data / lib_data_extra |
库目标的编译期数据 / 运行期数据 |
rustc_flags_extra / rustc_env |
追加 rustc 参数与环境变量(宏默认注入 BAZEL_PACKAGE=<包名>,见 defs.bazel#L282-L284) |
integration_test_args / unit_test_args / *_timeout |
集成/单元测试参数与超时 |
test_shard_counts |
按测试名映射的分片数,启用 Bazel 原生分片并标记 flaky(三次重试) |
test_tags |
传给单元 + 集成测试目标,典型用途是 no-sandbox |
extra_binaries / extra_binaries_non_windows |
把别的 crate 的二进制暴露为测试数据与 CARGO_BIN_EXE_* 环境变量 |
run_tests_with_wine_exec |
为每个集成测试生成 Wine 执行变体(在 Linux 上跑交叉编译的 Windows exec-server) |
宏内部的工作方式(均可在源码中验证):
- 构建脚本:若存在
build.rs,生成<name>-build-script目标(cargo_build_script规则,defs.bazel#L297-L305); - 库:
src/**/*.rs非空时生成rust_library(proc-macro crate 用rust_proc_macro),并同步生成单元测试目标(defs.bazel#L309-L371); - 二进制:按
Cargo.toml解析出的binaries表逐一生成rust_binary,并导出CARGO_BIN_EXE_<name>环境变量供集成测试使用——完整复刻了 Cargo 的行为(defs.bazel#L376-L391); - 集成测试:
tests/*.rs每个文件生成一个测试目标;源码注释(defs.bazel#L497-L513)明确列出四种生成形态:非分片原生测试、分片原生测试(拆成 manual 的rust_test+ 外层workspace_root_test)、Windows 交叉测试、Wine 执行测试。
其中 workspace_root_test(defs.bazel#L142-L179)是一个自研规则,通过 workspace_root_test_launcher.sh.tpl / workspace_root_test_launcher.bat.tpl 生成跨平台启动器:它在运行时解析出真实的仓库根目录、cd 进去,并把 runfiles 路径改写为绝对路径。这是为了让 insta 快照测试看到 Cargo 风格的相对路径(配合 INSTA_WORKSPACE_ROOT/INSTA_SNAPSHOT_PATH 环境变量,defs.bazel#L260-L266),同时兼容仓库使用 --noenable_runfiles(manifest-only runfiles)的策略。
另一个细节:测试目标的 rustc_flags 中统一加入了 --remap-path-prefix=../codex-rs= 和 --remap-path-prefix=codex-rs=(defs.bazel#L526-L532),因为 Bazel 曾对 file!() 宏产生两种不同的路径前缀,剥掉后 insta 快照元数据才与 Cargo 构建一致。
4. 每个 crate 的 BUILD.bazel
各 crate 目录下的 BUILD.bazel 通常只是调用 codex_rust_crate 并做少量调整。最简单的形态见 codex-rs/code-mode-host/BUILD.bazel:
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "code-mode-host",
crate_name = "codex_code_mode_host",
)
当 crate 需要额外的编译期/运行期数据、特殊环境变量或测试定制时,再按宏的参数列表补充。
本地运行 Bazel:justfile 入口
仓库根目录 justfile 暴露了常用入口(该文件的工作目录为 codex-rs):
just bazel-test
just bazel-clippy
这两个 recipe 的实际展开(justfile#L158-L164):
# bazel-test
bazel test --test_tag_filters=-argument-comment-lint //... --keep_going
# bazel-clippy
bazel_targets="$(scripts/list-bazel-clippy-targets.sh)" && bazel build --config=clippy -- ${bazel_targets}
即:测试全仓库目标并排除 nightly-only 的 argument-comment-lint 标签;clippy 通过 --config=clippy 用 rules_rust 的 Clippy aspect 对目标做检查。此外还有几个直接可用的 recipe:
just bazel-codex:bazel run //codex-rs/cli:codex,在 Bazel 构建产物上运行 CLI(justfile#L128-L135);just bazel-lock-update/just bazel-lock-check:见下文"演进"一节。
一个重要的边界事实(原文也强调了):普通的本地 bazel 与 just 调用全部在本地执行;BuildBuddy 缓存、构建事件上报(BES)、远程下载与远程执行都是 opt-in 配置,不选 --config=buildbuddy-* 就不会接触任何远程服务。
BuildBuddy:远程缓存与远程执行
codex-rs 的 CI 与内部构建通过 BuildBuddy 做共享缓存和远程构建/测试。要提速,需要两件事:提供 API key,并选择一个配置。
API key 配置
按 BuildBuddy 的认证文档创建 key 后,加入 ~/.bazelrc:
# Local machine only; this file contains a BuildBuddy credential.
common --remote_header=x-buildbuddy-api-key=<your-buildbuddy-api-key>
把凭据放在工作区外可以降低误提交的概率。如果不同项目需要不同的 key,放到 %workspace%/user.bazelrc——仓库的 .bazelrc 末尾以 try-import %workspace%/user.bazelrc 可选导入该文件(见 .bazelrc#L225),且 .gitignore 已将 user.bazelrc 排除在版本控制之外。切记不要提交或分享含凭据的文件。
选择远程构建配置
外部用户应使用 buildbuddy-generic-rbe 或 buildbuddy-generic;OpenAI 内部用户默认 buildbuddy-openai-rbe。把配置写入 %workspace%/user.bazelrc:
common --config=buildbuddy-openai-rbe
这组配置在 .bazelrc 中有明确定义(文件注释说明:这些配置"只有在用户显式选择时才会接触 BuildBuddy"):
buildbuddy-generic:cache/BES/下载均指向remote.buildbuddy.io,无远程执行;buildbuddy-generic-rbe:在上一项基础上叠加--config=remote,使用remote.buildbuddy.io远程执行器;buildbuddy-openai/buildbuddy-openai-rbe:同样的两级结构,但指向openai.buildbuddy.io。
--config=remote 本体设置 --strategy=remote、--extra_execution_platforms=//:rbe 并把并发提到 --jobs=800。而 //:rbe 平台由 rbe.bzl 生成,其 container-image exec property 钉死了含 git/python3/dotslash 等测试依赖的 Ubuntu 镜像(按主机架构选 x86_64 或 aarch64 镜像及 sha256),保证远程执行环境一致。
各配置的完整对照表(原文表格)
| 调用/配置 | 需要 key | Cache/BES | 构建执行 | 测试执行 |
|---|---|---|---|---|
bazel ... |
否 | 无 | 本地 | 本地 |
bazel ... --config=buildbuddy-generic |
是 | remote.buildbuddy.io |
本地 | 本地 |
bazel ... --config=buildbuddy-generic-rbe |
是 | remote.buildbuddy.io |
远程 | 远程 |
bazel ... --config=buildbuddy-openai |
是 | openai.buildbuddy.io |
本地 | 本地 |
bazel ... --config=buildbuddy-openai-rbe |
是 | openai.buildbuddy.io |
远程 | 远程 |
Cache/BES 主机同时用于远程下载(--experimental_remote_downloader)。
CI 侧:租户选择由统一 wrapper 完成
GitHub Actions 通过 .github/scripts/run_bazel_with_buildbuddy.py 路由所有 Bazel 构建与输出解析命令;更高层的辅助脚本(如 .github/scripts/run-bazel-ci.sh、.github/scripts/rusty_v8_bazel.py)都把远程配置选择委托给它。wrapper 的设计要点(原文描述,代码可印证):
- 它读取 GitHub Actions 的仓库与事件载荷来决定租户,而不是让每个 workflow 文件复制租户选择逻辑;
- 它归一化 Bazel 启动选项,让同一 job 内的所有 Bazel 调用复用同一个 server 和内存中的分析缓存(见脚本中
startup_args函数,.github/scripts/run_bazel_with_buildbuddy.py#L23-L33); - 加载阶段的
bazel query目标发现命令在本地运行,因为它只枚举 label,不需要远程缓存或执行; - 没有 API key 时,wrapper 会剥离远程 CI 配置、退回本地运行;pull request 事件载荷缺失或畸形时"fail closed"到 generic 主机;
- 只有在 GitHub Actions 中、且是受信运行(
openai/codex仓库内的 push/dispatch/同仓 PR)才选择 OpenAI 主机;fork 的 PR 一律本地运行。
CI 各配置与执行位置的对应关系(原文表格):
| CI 配置 | 远程配置 | 构建执行 | 测试执行 |
|---|---|---|---|
ci-linux |
*-rbe |
远程主机 | 远程主机 |
ci-v8 |
*-rbe |
远程主机 | 远程主机 |
ci-macos |
*-rbe |
远程主机 | 本地 |
ci-windows-cross |
*-rbe |
远程主机 | 本地 |
ci-windows |
非 RBE | 本地 | 本地 |
| 无 key 的 CI 回退 | 无 | 本地 | 本地 |
这些 ci-* 配置同样定义在 .bazelrc 中,并各有明确注释:ci-linux 可全量远程构建/测试(覆盖 x86 与 arm runner);ci-macos 构建远程化、测试留在本地(--strategy=TestRunner=darwin-sandbox,local);ci-windows-cross 用 Linux 远程执行构建 Windows gnullvm 二进制、测试留在 Windows runner 以保留 Bazel 分片与 flaky 重试,并强制 V8 的 mksnapshot 在本地执行(因为 Windows 快照必须由 Windows 的 mksnapshot 二进制生成)。wrapper 源码中也维护了"哪些 CI 配置需要远程构建执行"的映射(ci-linux、ci-macos、ci-v8、ci-windows-cross,见 .github/scripts/run_bazel_with_buildbuddy.py#L13-L18)。
若想在本地验证 generic 远程配置:
BUILDBUDDY_API_KEY=... GITHUB_REPOSITORY=my-fork/codex \
./.github/scripts/run_bazel_with_buildbuddy.py \
build --config=ci-linux //codex-rs/cli:codex
演进构建体系:改依赖、加 crate 的标准流程
更新依赖后刷新 Bzlmod 锁文件
改动 Cargo.toml/Cargo.lock 后,在仓库根目录运行:
just bazel-lock-update
它执行 bazel mod deps --lockfile_mode=update(justfile#L146-L147),按需更新 MODULE.bazel.lock。要把锁文件变更和 Cargo 锁文件一起提交。
本地验证锁文件对齐(与 CI 相同检查):
just bazel-lock-check
该 recipe 指向 scripts/check-module-bazel-lock.sh,脚本内部调用同一个 BuildBuddy wrapper 执行 bazel mod deps --lockfile_mode=error,失败时会明确提示运行 just bazel-lock-update 并提交锁文件。
如果某个上游 crate 无法在 Bazel 沙箱中构建、或需要做交叉编译适配,正确姿势是给它打 patch 或在 MODULE.bazel 中加 crate.annotation(前文 blake3/zstd-sys/ring 即是现成范例),而不是改 crate 源码。
新增 crate / binary 的三步流程
- 像往常一样把 crate 加入 Cargo 工作区;
- 创建
BUILD.bazel并调用codex_rust_crate(参考相邻 crate,如 codex-rs/code-mode-host/BUILD.bazel); - 如果依赖需要特殊处理(编译期/运行期数据、集成测试附加二进制、环境变量等),调整
codex_rust_crate参数。
原文特别提到一个常见定制:把 test_tags = ["no-sandbox"] 加给测试目标,让测试在无沙箱模式下运行。文档建议尽量避免,因为它绕过了沙箱隔离;典型必要场景是测试本身使用 Seatbelt——Bazel 沙箱在 macOS 上也是 Seatbelt 实现,而 Seatbelt 不能嵌套。为进一步限制影响面,可以把这类测试隔离到独立 crate。
顺带一提:Bazel 侧的 lint 与测试基础设施
just bazel-clippy使用的--config=clippy会启用rust_clippy_aspect,且因为 rules_rust 不读取 Cargo 的 lint 级别,.bazelrc 里手工维护了一份与codex-rs/Cargo.toml[workspace.lints.clippy]对齐的 deny 列表;- 测试环境默认
RUST_MIN_STACK=8388608(8 MiB),因为 Rust libtest 在 Windows 上以 std-spawned 线程跑测试体,默认 2 MiB 栈对大型 async 测试 future 不够; - 仓库采用
--noenable_runfiles(manifest-only runfiles)策略,这也是workspace_root_test启动器存在的直接原因之一。
参考与延伸阅读
原文给出的外部参考为 Bazel 官方文档(Bazel 概览与 Bzlmod 模块系统)、rules_rust 和 rules_rs 两个规则项目的仓库,此处不再重复外链,可分别在对应官方渠道检索。仓库内的深入阅读路径:
- 文档本身:codex-rs/docs/bazel.md
- 模块与工具链声明:MODULE.bazel、MODULE.bazel.lock
- 宏实现:defs.bzl(
codex_rust_crate于 L181 起,workspace_root_test规则于 L142 起) - 平台定义(含
//:rbe、Windows gnullvm/MSVC 平台):BUILD.bazel、rbe.bzl - 全局 Bazel 配置与 BuildBuddy/CI 配置:.bazelrc
- CI wrapper 及其租户选择逻辑:.github/scripts/run_bazel_with_buildbuddy.py
- 上游补丁集:patches/
小结:codex-rs 的 Bazel 体系本质上是"Cargo 管声明、Bazel 管执行"的双轨制——crate.from_cargo 消除依赖清单的双写,codex_rust_crate 消除构建目标的重复描述,MODULE.bazel + patches/ 保证工具链与上游 crate 的可复现性,BuildBuddy 各 --config 则以 opt-in 方式提供从纯缓存到全远程执行的分级加速。本地开发用 just bazel-test/just bazel-clippy 即可起步,新增依赖后记住 just bazel-lock-update 并提交锁文件,就能跟上这套仍在演进中的构建体系。
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 StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00