Bazel 规则集发布实战指南:从仓库布局、MODULE.bazel 到 CI/CD 与文档化的完整部署流程
【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel
本指南面向准备将自己的 Bazel 规则集(ruleset)发布给他人使用的规则作者(rule writer)。文章以官方「Deploying Rules」文档为主体,系统讲解规则仓库的命名与托管规范、标准目录布局、Bazel 模块(Bzlmod)集成方式、CI/CD 与文档自动化等发布全流程,并结合本仓库(Bazel 自身源码)中的实际实现佐证底层原理。读完本文,你将掌握一套可复用的规则集发布清单:从仓库初始化、
defs.bzl导出约定、toolchain 注册策略,到发布公告中可直接粘贴到用户MODULE.bazel的bazel_dep片段。
一、为什么需要专门的发布指南
规则(rules)是 Bazel 生态系统的扩展单元,用于支持各类语言与工具链的构建。Bazel 本身是“可扩展的构建系统”,其大量能力正是通过社区规则集体现的——例如 Go 规则、C/C++ 规则、Java 规则 等,均可参见 规则推荐列表。
官方之所以专门撰写部署指南,是因为规则集的发布与普通应用代码不同:它要同时服务“规则作者”与“规则用户”两个群体,牵涉到仓库命名、模块命名、toolchain 注册、测试与文档等一整套约定。本文依据 docs/rules/deploying.mdx 展开,并对照 Bazel 主仓库(即当前仓库)中的真实代码与配置,帮助你理解这些约定背后的原因。
二、托管与命名规则:rules_前缀约定
2.1 仓库命名格式
新规则应当托管在你自己组织(organization)下的独立 GitHub 仓库中,官方推荐使用统一的命名格式:
$ORGANIZATION/rules_$NAME例如bazelbuild/rules_go、bazelbuild/rules_java。如果你认为规则应该归属于bazelbuild组织,可以在 GitHub Discussions 发起讨论;否则就遵循<org>_rules_<lang>的约定。
2.2 仓库元数据规范
为了让用户能快速检索和理解规则,官方给出了明确的元数据模板:
| 项目 | 示例 |
|---|---|
| 仓库名称 | bazelbuild/rules_go |
| 仓库描述 | Go rules for Bazel |
| 仓库标签 | golang、bazel |
README.md标题 | Go rules for Bazel |
注意 README 标题要链接到 https://bazel.build,为不熟悉 Bazel 的用户提供正确入口。规则可以按语言(如 Scala)、运行时平台(如 Android)或框架(如 Spring)进行归类分组。
三、标准仓库布局:让用户一眼看懂你的规则集
每个规则仓库都应遵循统一布局,方便用户快速上手。官方以虚构的mockascript语言为例给出了完整结构:
/ LICENSE README MODULE.bazel mockascript/ constraints/ BUILD runfiles/ BUILD runfiles.mocs BUILD defs.bzl tests/ BUILD some_test.sh another_test.py examples/ BUILD bin.mocs lib.mocs test.mocs下面逐项解析每个组成部分。
3.1 MODULE.bazel:定义用户引用你的模块名
在项目根目录的MODULE.bazel中,定义用户引用规则时使用的模块名。若规则属于bazelbuild组织,必须使用rules_<lang>形式;否则使用<org>_rules_<lang>形式(如build_stack_rules_proto)。文档假设仓库属于bazelbuild组织,则:
module(name = "rules_mockascript")对照本仓库根目录的 MODULE.bazel,可以看到 Bazel 自身正是这样声明模块的:
module( name = "bazel", version = "10.0.0-prerelease", repo_name = "io_bazel", )Bazel 自己也是一个 Bazel 模块,其bazel_dep声明(如rules_go、rules_python、rules_java、rules_cc等)正好展示了规则集之间如何通过 Bzlmod 相互依赖。
3.2 README:规则集的“门面”
顶层必须有一个README,简要描述规则集的功能以及用户期望的 API。
3.3 Rules:defs.bzl作为统一入口
规则集通常包含多个规则。约定是:创建以语言命名的目录,并在其中提供入口文件defs.bzl,导出所有规则;同时放置BUILD文件使该目录成为一个 package:
/ mockascript/ BUILD defs.bzlBazel 主仓库内部同样遵循这一模式——例如 src/main/starlark/builtins_bzl 目录下的defs.bzl风格文件,以及tools/build_defs/、tools/build_rules/等目录中大量.bzl入口文件,都体现了“以.bzl文件为 API 入口”的约定。值得说明的是,随着规则从 Bazel 主仓库剥离,社区更倾向于把defs.bzl放在独立规则仓库中。
3.4 Constraints:自定义平台约束的存放位置
如果你的规则定义了 toolchain 规则,很可能会需要自定义constraint_setting和/或constraint_value。约定是将它们放入//<LANG>/constraintspackage:
/ mockascript/ constraints/ BUILD BUILD defs.bzl为什么 constraints 如此重要?文档明确指出:所有规则用户都会用这些约束在BUILD文件中执行平台相关的逻辑(例如使用select())。自定义约束本质上是在定义“整个 Bazel 生态都要说的语言”。因此:
- 先查阅 bazelbuild/platforms 中已有的约束,遵循最佳实践;
- 若约束与语言无关,考虑直接贡献到 platforms 仓库,而非重复发明;
- 谨慎引入新的自定义约束。
从本仓库 MODULE.bazel 可以看到platforms模块(version 1.1.0)正是通过bazel_dep引入的,它提供了跨语言通用的平台约束定义。
3.5 Runfiles 库:统一的//<LANG>/runfiles约定
如果你的规则为访问 runfiles 提供标准库,应将其放在//<LANG>/runfiles目标位置(即//<LANG>/runfiles:runfiles的缩写)。需要访问数据依赖的用户目标,通常会把该目标加入deps属性。
Bazel 主仓库为各语言提供了 runfiles 支持作为参考实现,例如:
- C++:tools/cpp/runfiles/runfiles.h(该头文件目前是 rules_cc 的转发器,指向
@rules_cc//cc/runfiles); - Bash:tools/bash/runfiles/BUILD;
- Java:tools/java/runfiles/BUILD;
- Python:tools/python/runfiles。
此外,仓库 examples 中还提供了runfile.cc、runfile.sh、runfile.py、runfile.go等各语言 runfiles 使用示例,可帮助规则作者理解 runfiles 库的目标形态。runfiles 相关概念可参考 runfiles 概念文档。
四、仓库规则(Repository rules)与 MODULE.bazel 集成
4.1 外部依赖声明
规则集可能有外部依赖,需要在MODULE.bazel中通过bazel_dep声明。本仓库的 MODULE.bazel 是极佳的参考范例——它声明了 30+ 个模块依赖,并说明了间接依赖的处理方式:
# Indirect module dependencies. Minimal versions are specified for compatibility; # repo_name=None avoids accidental usages bazel_dep(name = "buildozer", version = "8.5.1", repo_name = None) bazel_dep(name = "rules_swift", version = "3.3.0") # with repo_name = None, version drops to 2.4.0从源码结构看,repo_name = None用于避免用户代码意外直接引用这些间接依赖,是一种依赖隔离的实践。规则作者可以借鉴这种“显式声明 + 隔离间接依赖”的写法。
4.2 注册 toolchain
规则集也可以在MODULE.bazel中注册 toolchain。这里有一个重要的性能与架构考量,文档特别强调:
在分析阶段解析 toolchain 时,Bazel 需要分析所有已注册的
toolchain目标;但不需要分析toolchain.toolchain属性引用的所有目标。
这意味着:如果注册 toolchain 需要在仓库中执行复杂计算,应当考虑把“含toolchain目标”的仓库与“含<LANG>_toolchain目标”的仓库拆分。前者总是会被拉取(fetched),后者只在用户真正需要构建<LANG>代码时才被拉取,从而避免不必要的仓库获取开销。
toolchain规则的底层实现在 Bazel 源码中为 src/main/java/com/google/devtools/build/lib/rules/platform/ToolchainRule.java,其关键属性在 platforms-and-toolchains 参考文档 中有完整说明:
| 属性 | 说明 |
|---|---|
name | 必填,目标唯一名称 |
toolchain_type | 必填,toolchain_type目标的 label,表示该 toolchain 所服务的角色 |
toolchain | 必填,被选中时实际提供的工具/工具套件目标 |
exec_compatible_with | 默认[],执行平台必须满足的constraint_value列表 |
target_compatible_with | 默认[],目标平台必须满足的constraint_value列表 |
target_settings | 默认[],目标配置必须满足的config_setting列表 |
use_target_platform_constraints | 默认False,为True时继承当前目标平台的约束 |
toolchain 机制的动机可参考 toolchains 扩展文档:规则作者不应把编译器硬编码为规则的私有属性,而应通过toolchain_type+ 平台约束让 Bazel 在执行平台/目标平台之间自动选择合适工具。
4.3 发布公告中的 Release snippet
发布新版本时,在你的发布公告中提供一段用户可直接复制粘贴到MODULE.bazel的片段,通常形式为:
bazel_dep(name = "rules_<LANG>", version = "<VERSION>")例如用户引入rules_go时会写:
bazel_dep(name = "rules_go", version = "0.59.0")本仓库 MODULE.bazel 中真实使用了这一形式。这种“一键粘贴”的发布片段,是降低用户使用门槛的关键细节。
五、测试与示例:保障规则质量
5.1 测试组织方式
规则集必须包含验证规则按预期工作的测试。测试可以放在:
- 规则所服务语言的惯用位置(如
*_test规则的标准位置),或 - 顶层的
tests/目录。
Bazel 主仓库的测试布局极具参考价值:src/test/java(Java 规则测试)、src/test/shell(Shell 集成测试)、src/test/py(Python 测试)分别对应不同语言的测试组织方式。此外,tools/build_rules/test_rules.bzl 展示了“用规则测试规则”的元编程思路——它利用sh_test和 runfiles 工具为 Bazel 自身的测试提供支撑。
5.2 Examples 目录(可选但推荐)
提供examples/目录,展示规则几种基本用法,对用户非常有帮助。本仓库的 examples 目录(含 cpp、go、py、shell、java-native、java-starlark、windows 等子目录)就是最佳示范:每种语言都有可运行的示例构建目标与 README 说明。
六、CI/CD:让规则集持续集成与自动发版
6.1 GitHub Actions 与可复用工作流
许多规则集使用 GitHub Actions。官方推荐直接参考 rules-template 中的.github/workflows配置,其简化方案基于 bazel-contrib 组织托管的“可复用工作流”(reusable workflow):
ci.yaml:在每个 PR 和main分支提交上运行测试;release.yaml:每次向仓库推送 tag 时触发发布。
本仓库的 .github/workflows 展示了真实大型仓库的 CI 组织方式(cherry-pick、labeler、stale、release-helper、scorecard 等工作流),可从中观察成熟项目的自动化运维实践。
6.2 加入 bazelbuild 组织后的持续集成
如果你的仓库隶属于bazelbuild组织,可以申请将其加入 ci.bazel.build 持续集成平台,从而获得组织级的 CI 支持。
七、文档自动化:Stardoc 与 docs/ 目录
规则 API 文档应当自动生成,避免手工维护导致文档与代码脱节。官方建议:
- 使用 Stardoc:按 Stardoc 规范为规则编写注释,即可自动生成 API 文档。本仓库的 MODULE.bazel 也声明了对
stardoc模块的依赖(bazel_dep(name = "stardoc", version = "0.8.0", repo_name = "io_bazel_skydoc")),说明 Bazel 自身就在用 Stardoc 生成文档; - 参考 rules-template 的 docs/ 文件夹:它展示了如何在 Starlark 文件更新时,保持
docs/目录下的 Markdown 内容始终同步。
从文档工程角度看,Bazel 官方文档自身也是版本化生成的:本仓库 docs/versions 下维护了 7.6.1 至 9.1.0 等多个版本的文档快照,docs/versions/index.mdx 作为版本索引,这体现了大型项目“文档与版本绑定、自动更新”的最佳实践。
八、FAQ 深度解读:为什么规则不放进 Bazel 主仓库?
8.1 解耦规则与 Bazel 发布周期
官方明确回答:尽可能将规则与 Bazel 发布周期解耦。原因有三:
- 职责更清晰:能明确每个规则的所有者,减轻 Bazel 核心开发者的负担;
- 用户更灵活:解耦后,用户可以更方便地修改、升级、降级和替换规则;
- 贡献门槛更低:向规则仓库贡献代码通常比向 Bazel 核心贡献更轻量,甚至可能拥有该仓库的完整提交权限;而获取 Bazel 核心的提交权限要复杂得多。
8.2 代价:一次性安装更复杂
解耦的代价是用户需要一次性在MODULE.bazel中声明对规则集的依赖(即上文的bazel_dep片段)。这是为了长期灵活性而接受的一次性成本。
8.3 历史迁移:从//tools/build_rules到独立仓库
Bazel 历史上所有规则都位于主仓库的//tools/build_rules或//tools/build_defs目录下。当前仓库仍保留少量规则(例如 tools/build_rules、tools/build_defs 下的残留),但官方正在持续把剩余规则迁移出去。这解释了为什么本文档强调“新规则应放到独立仓库”——这是 Bazel 生态的既定演进方向。
九、规则集发布完整清单
综合全文,一份可落地的发布清单如下:
- 命名:仓库命名为
$ORGANIZATION/rules_$NAME,设置好描述、标签与 README 标题; - 模块:
MODULE.bazel中module(name = "rules_<LANG>")或<org>_rules_<LANG>; - 布局:
LICENSE、README、MODULE.bazel齐全;语言目录内含BUILD+defs.bzl入口;约束放//<LANG>/constraints;runfiles 库放//<LANG>/runfiles; - 依赖与 toolchain:用
bazel_dep声明外部依赖;register_toolchains注册工具链;必要时拆分 toolchain 仓库以减少不必要的拉取; - 测试:提供
tests/或语言惯用测试位置,确保规则行为可验证; - 示例(可选):提供
examples/展示基本用法; - CI/CD:配置
ci.yaml(PR/主分支测试)与release.yaml(tag 触发发版); - 文档:用 Stardoc 注释规则、自动生成 API 文档,保持
docs/与代码同步; - 发布:公告中包含
bazel_dep(name = "rules_<LANG>", version = "<VERSION>")一键粘贴片段。
遵循以上约定,你的规则集就能无缝融入 Bazel 生态,让用户以标准、可预期的方式发现、安装和使用你的规则。
【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考