Bazel 规则集发布实战指南:从仓库布局、MODULE.bazel 到 CI/CD 与文档化的完整部署流程
2026/9/13 12:04:14 网站建设 项目流程

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.bazelbazel_dep片段。

一、为什么需要专门的发布指南

规则(rules)是 Bazel 生态系统的扩展单元,用于支持各类语言与工具链的构建。Bazel 本身是“可扩展的构建系统”,其大量能力正是通过社区规则集体现的——例如 Go 规则、C/C++ 规则、Java 规则 等,均可参见 规则推荐列表。

官方之所以专门撰写部署指南,是因为规则集的发布与普通应用代码不同:它要同时服务“规则作者”与“规则用户”两个群体,牵涉到仓库命名、模块命名、toolchain 注册、测试与文档等一整套约定。本文依据 docs/rules/deploying.mdx 展开,并对照 Bazel 主仓库(即当前仓库)中的真实代码与配置,帮助你理解这些约定背后的原因。

二、托管与命名规则:rules_前缀约定

2.1 仓库命名格式

新规则应当托管在你自己组织(organization)下的独立 GitHub 仓库中,官方推荐使用统一的命名格式:

$ORGANIZATION/rules_$NAME

例如bazelbuild/rules_gobazelbuild/rules_java。如果你认为规则应该归属于bazelbuild组织,可以在 GitHub Discussions 发起讨论;否则就遵循<org>_rules_<lang>的约定。

2.2 仓库元数据规范

为了让用户能快速检索和理解规则,官方给出了明确的元数据模板:

项目示例
仓库名称bazelbuild/rules_go
仓库描述Go rules for Bazel
仓库标签golangbazel
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_gorules_pythonrules_javarules_cc等)正好展示了规则集之间如何通过 Bzlmod 相互依赖。

3.2 README:规则集的“门面”

顶层必须有一个README,简要描述规则集的功能以及用户期望的 API。

3.3 Rules:defs.bzl作为统一入口

规则集通常包含多个规则。约定是:创建以语言命名的目录,并在其中提供入口文件defs.bzl,导出所有规则;同时放置BUILD文件使该目录成为一个 package:

/ mockascript/ BUILD defs.bzl

Bazel 主仓库内部同样遵循这一模式——例如 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.ccrunfile.shrunfile.pyrunfile.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 文档应当自动生成,避免手工维护导致文档与代码脱节。官方建议:

  1. 使用 Stardoc:按 Stardoc 规范为规则编写注释,即可自动生成 API 文档。本仓库的 MODULE.bazel 也声明了对stardoc模块的依赖(bazel_dep(name = "stardoc", version = "0.8.0", repo_name = "io_bazel_skydoc")),说明 Bazel 自身就在用 Stardoc 生成文档;
  2. 参考 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 发布周期解耦。原因有三:

  1. 职责更清晰:能明确每个规则的所有者,减轻 Bazel 核心开发者的负担;
  2. 用户更灵活:解耦后,用户可以更方便地修改、升级、降级和替换规则;
  3. 贡献门槛更低:向规则仓库贡献代码通常比向 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 生态的既定演进方向。

九、规则集发布完整清单

综合全文,一份可落地的发布清单如下:

  1. 命名:仓库命名为$ORGANIZATION/rules_$NAME,设置好描述、标签与 README 标题;
  2. 模块MODULE.bazelmodule(name = "rules_<LANG>")<org>_rules_<LANG>
  3. 布局LICENSEREADMEMODULE.bazel齐全;语言目录内含BUILD+defs.bzl入口;约束放//<LANG>/constraints;runfiles 库放//<LANG>/runfiles
  4. 依赖与 toolchain:用bazel_dep声明外部依赖;register_toolchains注册工具链;必要时拆分 toolchain 仓库以减少不必要的拉取;
  5. 测试:提供tests/或语言惯用测试位置,确保规则行为可验证;
  6. 示例(可选):提供examples/展示基本用法;
  7. CI/CD:配置ci.yaml(PR/主分支测试)与release.yaml(tag 触发发版);
  8. 文档:用 Stardoc 注释规则、自动生成 API 文档,保持docs/与代码同步;
  9. 发布:公告中包含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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询