Bazel 模块注册表(Registry)完全指南:格式规范、源码实现与配置实战
【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel
本文聚焦 Bazel 外部依赖解析的核心基础设施——模块注册表(registry)。你将从零理解 Index Registry 的目录格式、
bazel_registry.json/metadata.json/source.json三类元数据的字段语义、archive / git_repository / local_path 三种来源类型的底层映射,并学会用--registry接入私有或第三方注册表。阅读后你能独立搭建一个可被 Bazel 直接消费的本地或静态 HTTP 模块注册表,并理解其与http_archive、git_repository、local_repository仓库规则的对应关系。
一、什么是 Bazel 注册表
Bazel 通过registry(注册表)发现外部依赖:它是 Bazel 模块(module)的数据库。Bazel 目前只支持一种类型的注册表——Index Registry(索引注册表),即遵循特定格式的本地目录或静态 HTTP 服务器。索引注册表只负责提供模块的"索引信息"(主页、维护者、每个版本的MODULE.bazel、如何获取每个版本的源码),它本身不需要托管源码压缩包——源码归档可以放在任意 URL 上。
这一设计让注册表可以做到"静态、可镜像、可 fork":只要按格式组织文件并放到静态服务器上即可工作。在 Bazel 源码中,负责实现这一行为的是IndexRegistry类,其类注释明确写道:"Represents a Bazel module registry that serves a list of module metadata from a static HTTP server or a local file path."(见 IndexRegistry.java)。
二、Index Registry 的目录格式
一个符合规范的索引注册表必须具有如下结构:
bazel_registry.json # 可选:注册表级元数据 modules/ ├── $MODULE/ # 每个模块一个子目录 │ ├── metadata.json # 模块级元数据(可选) │ └── $VERSION/ # 每个版本一个子目录 │ ├── MODULE.bazel # 该版本的 MODULE.bazel(必填) │ ├── source.json # 如何获取该版本源码(必填) │ ├── patches/ # 可选:补丁文件,仅 archive 类型使用 │ └── overlay/ # 可选:覆盖文件,仅 archive 类型使用 └── ...各组成部分的要点如下:
bazel_registry.json:可选的注册表级元数据文件,见下文第三节。modules/:包含注册表中每个模块的一个子目录。/modules/$MODULE:模块目录,内含每个版本的子目录(以$MODULE命名),以及模块级metadata.json。/modules/$MODULE/$VERSION:版本目录,包含以下文件:MODULE.bazel:该版本对外展示的模块文件。注意,这是 Bazel 外部依赖解析期间读取的MODULE.bazel,而不是源码归档里的那份(除非使用了非注册表 override)。最佳实践是:用这个文件来声明 release 版本号,而不要在源码归档中的MODULE.bazel里写版本号。关于模块版本化的更多讨论见 external/faq.mdx。source.json:描述如何获取该版本源码的 JSON 文件(必填),详见第五节。patches/:可选目录,存放补丁文件;仅当source.json的 type 为archive时使用。overlay/:可选目录,存放覆盖文件;同样仅当 type 为archive时使用。
从源码看,IndexRegistry严格按此路径约定拼装 URL:模块文件是modules/{name}/{version}/MODULE.bazel,source.json是modules/{name}/{version}/source.json,见 IndexRegistry.java 与 IndexRegistry.java。也就是说,目录即路由:静态服务器上只要有这些文件,Bazel 就能按固定路径抓取。
三、bazel_registry.json:注册表级元数据
bazel_registry.json是可选文件,声明作用于整个注册表的元数据。它支持以下字段:
| 字段 | 类型 | 含义 |
|---|---|---|
mirrors | 字符串数组 | 用于源码归档的镜像列表。镜像 URL = 镜像本身 + 模块source.json中源码 URL 去掉协议后的部分。例如源码 URL 为https://foo.com/bar/baz,mirrors为["https://mirror1.com/", "https://example.com/mirror2/"],则 Bazel 依次尝试https://mirror1.com/foo.com/bar/baz、https://example.com/mirror2/foo.com/bar/baz,最后回退到原始 URLhttps://foo.com/bar/baz |
module_base_path | 字符串 | 当source.json中 type 为local_path时,模块的相对基准路径 |
源码中的BazelRegistryJson内部类恰好声明了这两个字段(mirrors与moduleBasePath),Gson 通过LOWER_CASE_WITH_UNDERSCORES命名策略完成下划线字段到驼峰字段的映射,见 IndexRegistry.java。
镜像的拼装逻辑在createArchiveRepoSpec中实现:先把--module_mirrors命令行标志指定的镜像与bazel_registry.json中声明的镜像合并,逐个拼出"镜像 + authority + path"的 URL,最后再追加原始源码 URL,见 IndexRegistry.java。另外值得注意:bazel_registry.json本身被缓存(volatile字段 + 双重检查锁),不会在每个模块解析时重复下载,见 IndexRegistry.java。
四、metadata.json:模块级元数据与 yanked 版本
metadata.json是可选的模块级 JSON 文件,位于/modules/$MODULE/metadata.json,包含以下字段:
| 字段 | 类型 | 含义 |
|---|---|---|
versions | 字符串数组 | 该注册表中此模块可用版本的列表,应与模块目录下的版本子目录保持一致 |
yanked_versions | JSON 对象 | 该模块被yank(撤回)的版本。键为要撤回的版本号,值为撤回原因的描述(理想情况下附上链接) |
示例:
{ "versions": ["1.0.0", "1.1.0"], "yanked_versions": { "1.0.0": "Contains a critical security vulnerability, see https://example.com/advisory/1.0.0" } }yanked 版本在解析时会导致失败,除非用户通过--allow_yanked_versions显式放行。Bazel 源码中getYankedVersions会读取modules/{name}/metadata.json并解析其中的yankedVersions字段(同样使用LOWER_CASE_WITH_UNDERSCORES映射yanked_versions),见 IndexRegistry.java。metadata.json被视为可变信息,抓取时不带校验和(useChecksum=false),因为注册表维护者可能随时更新 yanked 列表。
关于 yanked 与 lockfile 的联动,tryGetYankedVersionsFromLockfile展示了三条路径(见 IndexRegistry.java):
- 该版本曾在 lockfile 中被记录为 yanked(用户已放行),则直接复用 lockfile 中的 yanked 信息,避免不必要的网络访问;
--lockfile_mode=error下metadata.json无法被抓取(它本身是可变文件),此时假定没有 yanked 版本;- lockfile 中记录了该模块的
source.json校验和、且不在已选 yanked 列表中,则说明选版时它未被 yanked,为保持构建可复现不再刷新 yanked 信息。
另外,官方Bazel Central Registry(BCR)对metadata.json有更严格的要求(需要更多字段),普通私有注册表只需满足上述最小字段。
五、source.json:源码获取方式
source.json是必填文件,描述如何获取某个特定版本的源码。其 schema 取决于type字段,type默认为archive。IndexRegistry.getRepoSpec根据 type 分派到三种不同的 RepoSpec 构建路径(见 IndexRegistry.java)。
5.1 type = archive(默认)
该类型的模块版本由http_archive仓库规则支撑:下载指定 URL 的归档并解压。支持以下字段:
| 字段 | 类型 | 说明 |
|---|---|---|
url | 字符串 | 源码归档的 URL(必填) |
mirror_urls | 字符串列表 | 归档的镜像 URL,按顺序在url之后作为后备尝试 |
integrity | 字符串 | 归档的 Subresource Integrity 校验和(必填) |
strip_prefix | 字符串 | 解压时要去掉的目录前缀 |
overlay | JSON 对象 | 叠加在解压后归档之上的覆盖文件。文件位于/modules/$MODULE/$VERSION/overlay/目录下;键为覆盖文件名,值为其 integrity 校验和。overlay 先于 patch 应用 |
patches | JSON 对象 | 应用于解压后归档的补丁文件。文件位于/modules/$MODULE/$VERSION/patches/目录下;键为补丁文件名,值为其 integrity 校验和。补丁在 overlay 之后、按patches中的顺序应用 |
patch_strip | 数字 | 与 Unixpatch的--strip参数相同 |
archive_type | 字符串 | 下载文件的归档类型(同http_archive的type) |
示例:
{ "type": "archive", "url": "https://github.com/example/foo/archive/refs/tags/v1.2.3.tar.gz", "integrity": "sha256-...", "strip_prefix": "foo-1.2.3", "patches": { "fix_build.patch": "sha256-...", "add_visibility.patch": "sha256-..." }, "patch_strip": 1 }源码中的ArchiveSourceJson内部类精确对应了这些字段(见 IndexRegistry.java),而createArchiveRepoSpec则完成了从 JSON 到ArchiveRepoSpecBuilder的转换,包括:拼接镜像 URL、将补丁映射为"<registry>/modules/<module>/<version>/patches/<file>→ integrity"的键值对、把 overlay 文件也解析为带 integrity 的 RemoteFile,最后连同远端MODULE.bazel(remote_module_file)一起构建 RepoSpec,见 IndexRegistry.java。
校验与防御细节(同样来自源码,见 IndexRegistry.java 与 IndexRegistryTest.java 中的testGetArchiveRepoSpec_empty*系列用例):
url缺失、integrity缺失或为空白,直接报错;- 任何补丁或 overlay 文件缺失 integrity,直接报错(对应测试
testGetArchiveRepoSpec_emptyPatchIntegrity/emptyOverlayIntegrity/whitespaceIntegrity等)。
5.2 type = git_repository
该类型由git_repository仓库规则支撑,通过克隆 Git 仓库获取。支持以下字段:
| 字段 | 类型 | 说明 |
|---|---|---|
remote | 字符串 | Git 远端地址(必填) |
commit | 字符串 | 要检出的 commit 哈希 |
shallow_since | 字符串 | 浅克隆的截止时间 |
tag | 字符串 | 要检出的 tag |
init_submodules | 布尔 | 是否初始化子模块 |
verbose | 布尔 | 是否输出详细日志 |
strip_prefix | 字符串 | 检出后去掉的目录前缀 |
patch_strip | 数字 | 补丁的 strip 层级 |
patches | JSON 对象 | 应用于克隆仓库的补丁文件,位于/modules/$MODULE/$VERSION/patches/,按出现顺序应用 |
示例:
{ "type": "git_repository", "remote": "https://github.com/example/bar.git", "commit": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "init_submodules": true, "patches": { "fix_compile.patch": "sha256-..." } }源码中的GitRepoSourceJson类(见 IndexRegistry.java)与createGitRepoSpec(见 IndexRegistry.java)实现了该类型。注意其中的安全校验:
remote只允许https://、ssh://、git://、file://四种 scheme(ALLOWED_GIT_SCHEMES常量,见 IndexRegistry.java);commit必须是 7~64 位十六进制哈希;tag不能以-开头(防止被误解析为命令行选项)。
5.3 type = local_path
该类型由local_repository仓库规则支撑,符号链接到本地磁盘目录。仅支持一个字段:
| 字段 | 类型 | 说明 |
|---|---|---|
path | 字符串 | 仓库的本地路径,按如下规则解析 |
path的解析规则(源码实现见 IndexRegistry.java):
path为绝对路径时,原样使用;path为相对路径、且bazel_registry.json中module_base_path为绝对路径时,解析为<module_base_path>/<path>;path与module_base_path均为相对路径时,解析为<registry_path>/<module_base_path>/<path>。此时注册表必须托管在本地、并通过--registry=file://<registry_path>使用,否则 Bazel 会报错。
{ "type": "local_path", "path": "vendor/foo" }配合bazel_registry.json中的"module_base_path": "third_party",相对路径vendor/foo最终解析为<registry_path>/third_party/vendor/foo。
六、Bazel Central Registry(BCR)
Bazel Central Registry(BCR)是官方公共注册表,由 Bazel 社区维护,其内容托管在 GitHub 的bazelbuild/bazel-central-registry仓库,可通过 Web 前端浏览其内容。由于 BCR 只是一个内容更丰富的 Index Registry,你可以直接 fork 它来搭建自己的私有注册表,或学习它的组织方式。
在遵循普通 Index Registry 格式之外,BCR 还要求每个模块版本提供一个presubmit.yml文件(/modules/$MODULE/$VERSION/presubmit.yml),它声明几个必要的构建与测试 target,用于校验该模块版本的合法性,同时 BCR 的 CI 流水线也用它保证模块之间的互操作性。如果你的注册表需要类似的质量门槛,可以参考这一约定。
七、选择与配置注册表:--registry标志
可重复的--registry标志用于指定请求模块的注册表列表,使项目可以从第三方或内部注册表拉取依赖:
bazel build //... --registry=https://example.com/registry --registry=https://bcr.bazel.build关键语义:
- 顺序即优先级:排在越前面的注册表优先级越高,Bazel 会先在靠前的注册表中查找模块,找不到时才回退到后面的注册表。这一语义与源码中
--registry标志的帮助文本一致:"modules will be looked up in earlier registries first, and only fall back to later registries when they're missing from the earlier ones"(见 RepositoryOptions.java)。 - 推荐写入
.bazelrc:为方便使用,可以把一串--registry标志放进项目根目录的.bazelrc文件。 - GitHub 托管的注册表:如果注册表托管在 GitHub(例如
bazelbuild/bazel-central-registry的 fork),--registry值需要使用raw.githubusercontent.com下的裸地址。例如在my-org这个 fork 的main分支上,应设置为--registry=https://raw.githubusercontent.com/my-org/bazel-central-registry/main/。 - 默认行为的改变:使用
--registry标志后,Bazel Central Registry不再默认启用;如需保留,需显式加回--registry=https://bcr.bazel.build。
注册表 URL 的方案(scheme)也有限制:RegistryFactoryImpl只接受http://、https://和file://,其他协议会抛出 "Unrecognized registry URL protocol" 错误;同时会根据 scheme 与--lockfile_mode组合推导出对注册表文件的校验和处理模式(KnownFileHashesMode)——file://本地注册表完全忽略校验和,http(s)注册表在error模式下强制要求所有下载带校验和,见 RegistryFactoryImpl.java 与 IndexRegistry.java。
相关辅助标志
--module_mirrors:额外指定源码镜像 URL,优先级高于注册表提供的镜像。支持按注册表指定:--module_mirrors=https://bcr.bazel.build=https://mirror.example.com,也支持全局逗号分隔列表;设为空值可禁用注册表默认镜像。所有镜像下载都会用注册表中存储的哈希校验(并被 lockfile 固定),见 RepositoryOptions.java。--allow_yanked_versions:以module@version,module2@version2形式显式放行被 yanked 的版本,否则解析会失败,见 RepositoryOptions.java。
八、实战:搭建一个本地 Index Registry
下面给出一个可直接运行的最小示例,把上文格式串起来。假设项目根目录结构如下:
my-registry/ ├── bazel_registry.json └── modules/ └── my_lib/ ├── metadata.json └── 1.0.0/ ├── MODULE.bazel └── source.json第 1 步:注册表级元数据bazel_registry.json
{ "mirrors": ["https://mirror.example.com/"], "module_base_path": "" }第 2 步:模块级元数据modules/my_lib/metadata.json
{ "versions": ["1.0.0"], "yanked_versions": {} }第 3 步:版本模块文件modules/my_lib/1.0.0/MODULE.bazel
module( name = "my_lib", version = "1.0.0", compatibility_level = 1, )第 4 步:版本源码信息modules/my_lib/1.0.0/source.json
{ "url": "https://github.com/example/my_lib/archive/refs/tags/v1.0.0.tar.gz", "integrity": "sha256-<base64 校验和>", "strip_prefix": "my_lib-1.0.0" }第 5 步:在项目中使用
在项目.bazelrc中添加:
common --registry=file:///absolute/path/to/my-registry或在命令行直接传入:
bazel build //... --registry=file:///absolute/path/to/my-registry --registry=https://bcr.bazel.build之后在MODULE.bazel中bazel_dep(name = "my_lib", version = "1.0.0")即可完成依赖解析。把my-registry目录原样上传到任意静态 Web 服务器,并把file://换成https://地址,就得到了一个对外可用的 HTTP 注册表——因为IndexRegistry对本地目录与静态 HTTP 服务器一视同仁,只是抓取路径从文件系统换成了 URL。
九、验证与进一步阅读
如果你想在源码层面验证本文所述行为,可以从这些位置入手:
- 核心实现:IndexRegistry.java(目录格式、三类 source、镜像、yanked、lockfile 联动);
- 注册表工厂与协议校验:RegistryFactoryImpl.java;
- 标志定义:RepositoryOptions.java(
--registry、--module_mirrors、--allow_yanked_versions); - 测试用例:IndexRegistryTest.java(覆盖 HTTP/file 注册表、三种 RepoSpec、yanked 版本、各类 integrity 缺失报错)、IndexRegistryGitRepositoryTest.java、RegistryFactoryTest.java;
- 关联文档:external/module.mdx(模块与 yanked 版本语义)、external/faq.mdx(模块版本化最佳实践)、external/overview.mdx(Bzlmod 概览)。
理解注册表是掌握 Bazel 外部依赖体系的关键一步:它把"依赖从哪来、源码怎么拿、版本是否可信"这几个问题统一沉淀为一份静态目录结构,让依赖解析既透明又可审计,也为企业内网镜像、离线分发和供应链安全提供了清晰的落地路径。
【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考