codex-rs 的 Bazel 构建体系:codex_rust_crate 宏、Bzlmod 工具链与 BuildBuddy 远程执行实战
【免费下载链接】openinterpreterA coding agent for open models like Kimi K3 and GLM 5.3项目地址: https://gitcode.com/GitHub_Trending/op/openinterpreter
本篇基于仓库内的 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-imageexec 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并提交锁文件,就能跟上这套仍在演进中的构建体系。
【免费下载链接】openinterpreterA coding agent for open models like Kimi K3 and GLM 5.3项目地址: https://gitcode.com/GitHub_Trending/op/openinterpreter
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考