Bzlmod 是新的外部依赖项系统的代号 Bazel 5.0 中引入的库。引入这个框架的目的是解决 无法逐步修复的旧系统;请参阅 原始设计文档中的“问题陈述”部分 了解详情。
在 Bazel 5.0 中,Bzlmod 在默认情况下处于停用状态;旗帜
必须指定 --experimental_enable_bzlmod
,才能获取以下内容:
效果。顾名思义,此功能目前处于实验阶段;
在该功能正式发布之前,API 和行为可能会发生变化。
要将项目迁移到 Bzlmod,请按照 Bzlmod 迁移指南操作。 您还可以在示例代码库中找到 Bzlmod 用法示例。
Bazel 模块
基于 WORKSPACE
的旧版外部依赖项系统以
通过“代码库规则”(或“代码库规则”)创建的“代码库”。
虽然代码库仍然是新系统中的一个重要概念,但模块是新系统中的
核心依赖项单元。
模块本质上是一个 Bazel 项目,可以有多个版本,每个 用于发布其所依赖的其他模块的元数据。这是 类似于其他依赖项管理系统中熟悉的概念: artifact、npm package、Cargo crate、Go 模块等
模块仅使用 name
和 version
对指定其依赖项,
而不是 WORKSPACE
中的特定网址。然后在
Bazel 注册表;默认情况下,
Bazel Central Registry。在您的工作区中
之后模块会变成代码库。
MODULE.bazel
每个模块的每个版本都有一个 MODULE.bazel
文件,用于声明其
依赖项和其他元数据下面是一个基本示例:
module(
name = "my-module",
version = "1.0",
)
bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")
MODULE.bazel
文件应位于工作区目录的根目录下
(在 WORKSPACE
文件旁边)。与 WORKSPACE
文件不同,您不需要
指定您的传递依赖项;应仅指定
直接依赖项,依赖项的 MODULE.bazel
文件为
以自动发现传递依赖项。
MODULE.bazel
文件类似于 BUILD
文件,因为它不支持任何
控制流的形式;此外,还禁止使用 load
语句。指令
MODULE.bazel
文件支持:
module
,用于指定元数据 关于当前模块的信息,包括其名称、版本等;bazel_dep
,用于指定直接流量 对其他 Bazel 模块的依赖项- 替换项,只能由根模块使用(即不能由 模块)自定义 某些直接或传递依赖项: <ph type="x-smartling-placeholder">
- 与模块扩展相关的指令:
<ph type="x-smartling-placeholder">
- </ph>
use_extension
use_repo
版本格式
Bazel 拥有多元化的生态系统,项目使用各种版本控制方案。通过
是迄今为止最受欢迎的软件版本 SemVer,但
也有一些采用不同架构的优秀项目
Abseil,其
版本基于日期,例如 20210324.2
)。
因此,Bzlmod 采用了更为宽松的 SemVer 规范。通过 差异包括:
- SemVer 规定,“发布”部分版本必须包含 3
细分受众群:
MAJOR.MINOR.PATCH
。在 Bazel 中,这项要求放宽了 允许任意数量的细分受众群 - 在 SemVer 中,“版本”中的每个细分部分只能包含数字。 在 Bazel 中,我们放宽了限制,允许使用字母, 语义匹配“identifiers”在“预发布”中部分。
- 此外,主要、次要和补丁版本递增的语义 未强制执行。(不过,请参阅兼容性级别,了解 详细了解我们如何表示向后兼容性。)
任何有效的 SemVer 版本都是有效的 Bazel 模块版本。此外,还有两个
SemVer 版本 a
和 b
会比较 a < b
和 a < b
对比了 Bazel 模块版本。
版本分辨率
菱形依赖项问题是版本化依赖项的主要问题 管理空间假设您有以下依赖关系图:
A 1.0
/ \
B 1.0 C 1.1
| |
D 1.0 D 1.1
应使用哪个版本的 D?为解决这个问题,Bzlmod 使用 选择最低版本 (MVS) 算法是在 Go 模块系统中引入的。MVS 假设所有新的 模块向后兼容, 版本(在我们的示例中为 D 1.1)。称为“极小” 因为这里的 D 1.1 是能够满足我们要求的最低版本; 即使存在 D 1.2 或更高版本,我们也不会选择这些软件。这样做还有一个好处 版本选择具有高保真度且可重现。
版本解析是在您的计算机上本地执行,而不是由注册表执行。
兼容性级别
请注意,MVS 关于向后兼容性的假设是可行的,因为它 只是将向后不兼容的模块版本视为单独的模块。 就 SemVer 而言,这意味着 A 1.x 和 A 2.x 被视为不同的模块, 并且可以共存于已解析的依赖关系图。进而 主要版本是包含在软件包路径中 确保不存在编译时或链接时冲突。
在 Bazel 中,我们没有这种保证。因此,我们需要一种
version”编号,以检测向后不兼容的版本。此号码
称为兼容性级别,由
其 module()
指令。掌握这些信息后,我们可以抛出错误,
当我们检测到同一模块的版本具有不同的兼容性时
已解析的依赖关系图中存在多个级别。
代码库名称
在 Bazel 中,每个外部依赖项都有一个代码库名称。有时,
都可能通过不同的代码库名称来使用(例如,
@io_bazel_skylib
和 @bazel_skylib
的含义
Bazel skylib)或同
代码库名称可能用于不同项目中的不同依赖项。
在 Bzlmod 中,代码库可以由 Bazel 模块生成, 模块扩展。如需解决代码库名称冲突,请执行以下操作: 我们采用代码库映射 机制。以下是两个重要概念:
规范代码库名称:每个代码库的全局唯一代码库名称 存储库这是代码库所在的目录名称。
其构建方式如下(警告:规范名称格式为 不是您应依赖的 API,它可能随时发生变化):- 对于 Bazel 模块代码库:
module_name~version
(示例。@bazel_skylib~1.0.3
) - 对于模块扩展程序代码库:
module_name~version~extension_name~repo_name
(示例。@rules_cc~0.0.1~cc_configure~local_config_cc
)
- 对于 Bazel 模块代码库:
显而易见的代码库名称:要在
BUILD
和.bzl
文件。同一依赖项可能具有不同的 在不同代码库中使用新名称
具体规则如下:
每个仓库都有一个直接依赖项的仓库映射字典,
这是从显而易见的代码库名称到规范代码库名称的映射。
在构建
标签。请注意,规范代码库名称不会发生冲突,
通过解析 MODULE.bazel
因此可以很容易地捕获和解决冲突,而不会影响
其他依赖项
Strict 依赖项
新的依赖项规范格式使我们能够执行更严格的检查。在 具体而言,我们现在强制要求模块只能使用通过其代码库创建的 直接依赖项这有助于防止意外中断和难以调试的中断 当传递依赖关系图中的内容发生变化时触发。
严格依赖项的实现依据 代码库映射。基本上, 每个代码库的代码库映射包含它的所有直接依赖项, 其他代码库不可见。每个代码库的可见依赖项为 确定如下:
- Bazel 模块代码库可以查看
MODULE.bazel
文件中引入的所有代码库 通过bazel_dep
和use_repo
。 - 模块扩展代码库可以查看该模块的所有可见依赖项 提供扩展程序,以及由同一模块生成的所有其他代码库 。
注册表
Bzlmod 通过从 Bazel 请求依赖项信息来发现依赖项 注册表。Bazel 注册表就是 Bazel 模块的数据库。唯一 受支持的注册表形式是索引注册表, 本地目录或采用特定格式的静态 HTTP 服务器。在 未来,我们计划添加对单模块注册表的支持, 包含项目源代码和历史记录的 Git 代码库。
索引注册表
索引注册表是本地目录或包含
模块列表的相关信息,包括模块主页、维护人员、
每个版本的 MODULE.bazel
文件,以及如何提取每个版本的源代码
版本。值得注意的是,它无需自行提供源归档。
索引注册表必须遵循以下格式:
/bazel_registry.json
:包含注册表元数据的 JSON 文件,例如: <ph type="x-smartling-placeholder">- </ph>
mirrors
:指定用于源归档的镜像列表。module_base_path
,用于指定包含local_repository
在source.json
文件中键入 。
/modules
:包含此目录中每个模块的子目录的目录 注册表中。/modules/$MODULE
:包含每个版本的子目录的目录 以及以下文件: <ph type="x-smartling-placeholder">- </ph>
metadata.json
:包含模块相关信息的 JSON 文件。 包含以下字段: <ph type="x-smartling-placeholder">- </ph>
homepage
:项目首页的网址。maintainers
:JSON 对象列表,其中每个对象都对应 注册表中的模块维护人员的信息。 请注意,这不一定与 项目。versions
:包含此模块的所有版本的列表 此注册表yanked_versions
:此模块的提取版本列表。这个 当前为空操作,但将来,被拉动的版本 或生成错误。
/modules/$MODULE/$VERSION
:包含以下文件的目录: <ph type="x-smartling-placeholder">- </ph>
MODULE.bazel
:此模块版本的MODULE.bazel
文件。source.json
:一个 JSON 文件,其中包含有关如何提取 此模块版本的源代码。- 默认类型为“archive”包含以下字段:
<ph type="x-smartling-placeholder">
- </ph>
url
:来源归档的网址。integrity
: 子资源完整性 归档的校验和。strip_prefix
:提取 来源归档。patches
:字符串列表,其中每个字符串都会指定一个补丁文件, 应用于解压的归档文件补丁文件位于/modules/$MODULE/$VERSION/patches
目录中。patch_strip
:与 Unix 补丁的--strip
参数相同。
- 可以将类型更改为使用包含以下字段的本地路径:
<ph type="x-smartling-placeholder">
- </ph>
type
:local_path
path
:代码库的本地路径,计算公式如下: <ph type="x-smartling-placeholder">- </ph>
- 如果路径是绝对路径,则按原样使用路径。
- 如果路径是相对路径且
module_base_path
是绝对路径, 路径已解析为<module_base_path>/<path>
- 如果路径和
module_base_path
都是相对路径,则路径为 已解析为“<registry_path>/<module_base_path>/<path>
”。 注册表必须托管在本地,并由--registry=file://<registry_path>
使用。 否则,Bazel 将抛出错误。
- 默认类型为“archive”包含以下字段:
<ph type="x-smartling-placeholder">
patches/
:包含补丁文件的可选目录,仅当source.json
包含“archive”时使用类型。
Bazel 中央注册表
Bazel Central Registry (BCR) 是一个索引注册表,位于
bcr.bazel.build.其内容
由 GitHub 代码库提供支持
bazelbuild/bazel-central-registry
。
BCR 由 Bazel 社区维护;欢迎贡献者提交 拉取请求请参阅 Bazel 中央注册表政策和过程。
除了遵循普通索引注册表的格式之外,BCR 还要求
每个模块版本的 presubmit.yml
文件
(/modules/$MODULE/$VERSION/presubmit.yml
)。此文件指定了一些
构建和测试目标 - 这些目标可用于对项目
模块版本,供 BCR 的 CI 流水线使用,以确保互操作性
BCR 中的模块之间传递。
选择注册表
可重复的 Bazel 标志 --registry
可用于指定
并从中请求模块的数据库,这样您就可以将项目设置为
从第三方或内部注册表中删除依赖项。较早的域名注册管理机构
优先级。为方便起见,您可以将 --registry
标志列表放在
项目的 .bazelrc
文件。
模块扩展
借助模块扩展,您可以通过读取输入数据来扩展模块系统
从依赖关系图的各个模块中,执行必要的逻辑来解析
以及通过调用 repo 规则创建代码库。它们很相似
与现今的 WORKSPACE
宏类似,但更适合
模块和传递依赖项。
模块扩展在 .bzl
文件中定义,就像 repo 规则或
WORKSPACE
宏。它们不是直接调用的,每个模块
指定要读取的称为“标记”的数据。接着,
版本解析完成后,便会运行模块扩展。每个扩展程序都会运行
在模块解析完成后(仍在实际进行任何构建之前)一次,以及
读取整个依赖关系图中属于它的所有标记。
[ A 1.1 ]
[ * maven.dep(X 2.1) ]
[ * maven.pom(...) ]
/ \
bazel_dep / \ bazel_dep
/ \
[ B 1.2 ] [ C 1.0 ]
[ * maven.dep(X 1.2) ] [ * maven.dep(X 2.1) ]
[ * maven.dep(Y 1.3) ] [ * cargo.dep(P 1.1) ]
\ /
bazel_dep \ / bazel_dep
\ /
[ D 1.4 ]
[ * maven.dep(Z 1.4) ]
[ * cargo.dep(Q 1.1) ]
在上面的示例依赖关系图中,A 1.1
和 B 1.2
等是 Bazel 模块;
您可以将每个文件视为 MODULE.bazel
文件。每个模块可以指定一些
模块扩展的标记;这里为扩展程序“maven”指定了一些
还有一些则是针对“cargo”指定的。此依赖关系图最终确定后(针对
例如,假设 B 1.2
实际上在 D 1.3
上有 bazel_dep
,但已升级到
D 1.4
(由于 C
),扩展程序“maven”它会读取所有
maven.*
标记,根据其中包含的信息来确定要创建的代码库。
对于“cargo”。
扩展程序使用情况
扩展程序本身都托管在 Bazel 模块中,
您需要先在该模块上添加 bazel_dep
,然后调用
内置 use_extension
函数以将其纳入范围。请考虑以下示例
MODULE.bazel
文件,以使用假设的“maven”在
rules_jvm_external
模块:
bazel_dep(name = "rules_jvm_external", version = "1.0")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
将扩展程序纳入范围内后,您可以使用 dot 语法
为其指定标记请注意,这些标记需要遵循
相应的标记类(请参阅扩展定义)
)。以下示例指定了一些 maven.dep
和 maven.pom
标记。
maven.dep(coord="org.junit:junit:3.0")
maven.dep(coord="com.google.guava:guava:1.2")
maven.pom(pom_xml="//:pom.xml")
如果扩展程序生成了要在模块中使用的代码库,请使用
use_repo
指令
。这是为了满足严格的依赖项条件,并避免使用本地代码库名称
冲突。
use_repo(
maven,
"org_junit_junit",
guava="com_google_guava_guava",
)
扩展程序生成的代码库是其 API 的一部分,因此通过
则应该知道“maven”会生成一个
一个名为“org_junit_junit”,另一个名为“com_google_guava_guava”。包含
use_repo
,则可以选择在模块范围内重命名它们,例如
“guava”此处。
扩展定义
模块扩展的定义与 Repo 规则类似,使用
module_extension
函数。
两者都有实现函数;但代码库规则中
而模块扩展则具有
tag_class
,每个元素都有一个
属性数量。标记类定义了此
。继续讨论我们假设的“maven”上述扩展:
# @rules_jvm_external//:extensions.bzl
maven_dep = tag_class(attrs = {"coord": attr.string()})
maven_pom = tag_class(attrs = {"pom_xml": attr.label()})
maven = module_extension(
implementation=_maven_impl,
tag_classes={"dep": maven_dep, "pom": maven_pom},
)
这些声明明确表明,maven.dep
和 maven.pom
标记
属性架构。
实现函数与 WORKSPACE
宏类似,不同之处在于前者
会获取 module_ctx
对象,
访问依赖关系图和所有相关标记。实现
函数应调用 repo 规则以生成代码库:
# @rules_jvm_external//:extensions.bzl
load("//:repo_rules.bzl", "maven_single_jar")
def _maven_impl(ctx):
coords = []
for mod in ctx.modules:
coords += [dep.coord for dep in mod.tags.dep]
output = ctx.execute(["coursier", "resolve", coords]) # hypothetical call
repo_attrs = process_coursier(output)
[maven_single_jar(**attrs) for attrs in repo_attrs]
在上面的示例中,我们浏览了依赖关系图中的所有模块
(ctx.modules
),每个
其 tags
字段的 bazel_module
对象
公开模块上的所有 maven.*
标记。然后调用 CLI 实用程序
与 Maven 联系并执行解决方法。最后,我们使用分辨率
并使用假设的 maven_single_jar
创建若干代码库
repo 规则。