借助模块扩展,用户可以通过以下方式扩展模块系统:从依赖关系图中的模块读取输入数据,执行必要的逻辑来解析依赖项,最后通过调用 repo 规则来创建 repo。这些扩展具有与 repo 规则类似的功能,因此能够执行文件 I/O、发送网络请求等操作。除此之外,它们还允许 Bazel 与其他软件包管理系统进行交互,同时遵循由 Bazel 模块构建的依赖关系图。
您可以像 repo 规则一样在 .bzl 文件中定义模块扩展。它们不会被直接调用;相反,每个模块都会指定一些称为“标记”的数据,供扩展读取。
Bazel 会先运行模块解析,然后再评估任何扩展。扩展会读取整个依赖关系图中属于它的所有标记。
扩展用法
扩展托管在 Bazel 模块本身中。如需在
模块中使用扩展,请先在托管扩展的模块上添加 bazel_dep,然后
调用 use_extension 内置函数
将其纳入范围。请看以下示例,这是
MODULE.bazel文件中的代码段,用于使用“maven”扩展,该扩展在
rules_jvm_external
模块中定义:
bazel_dep(name = "rules_jvm_external", version = "4.5")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
这会将 use_extension 的返回值绑定到变量,从而允许用户使用点语法为扩展指定标记。标记必须遵循
扩展定义中指定的相应标记类的架构
。以下示例指定了一些 maven.install 和 maven.artifact 标记:
maven.install(artifacts = ["org.junit:junit:4.13.2"])
maven.artifact(group = "com.google.guava",
artifact = "guava",
version = "27.0-jre",
exclusions = ["com.google.j2objc:j2objc-annotations"])
使用 use_repo 指令将扩展生成的 repo
纳入当前模块的范围。
use_repo(maven, "maven")
扩展生成的 repo 是其 API 的一部分。在此示例中,“maven”模块扩展承诺生成一个名为 maven 的
repo。通过上述声明,扩展可以正确解析标签(例如 @maven//:org_junit_junit),以指向“maven”扩展生成的
repo。
扩展定义
您可以使用
module_extension函数定义模块扩展,这与定义 repo 规则类似。不过,
repo 规则具有许多属性,而模块扩展具有
tag_classes,每个
又具有许多属性。标记类定义了此扩展使用的标记的架构。例如,上述“maven”扩展可以定义如下:
# @rules_jvm_external//:extensions.bzl
_install = tag_class(attrs = {"artifacts": attr.string_list(), ...})
_artifact = tag_class(attrs = {"group": attr.string(), "artifact": attr.string(), ...})
maven = module_extension(
implementation = _maven_impl,
tag_classes = {"install": _install, "artifact": _artifact},
)
这些声明表明,可以使用指定的属性架构指定 maven.install 和 maven.artifact 标记。
模块扩展的实现函数与 repo
规则的实现函数类似,不同之处在于,它们会获得一个 module_ctx 对象,
该对象授予对使用该扩展的所有模块和所有相关标记的访问权限。
然后,实现函数会调用
repo 规则来生成 repo。
# @rules_jvm_external//:extensions.bzl
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") # a repo rule
def _maven_impl(ctx):
# This is a fake implementation for demonstration purposes only
# collect artifacts from across the dependency graph
artifacts = []
for mod in ctx.modules:
for install in mod.tags.install:
artifacts += install.artifacts
artifacts += [_to_artifact(artifact) for artifact in mod.tags.artifact]
# call out to the coursier CLI tool to resolve dependencies
output = ctx.execute(["coursier", "resolve", artifacts])
repo_attrs = _process_coursier_output(output)
# call repo rules to generate repos
for attrs in repo_attrs:
http_file(**attrs)
_generate_hub_repo(name = "maven", repo_attrs)
扩展身份
模块扩展由 use_extension 调用中显示的名称和 .bzl 文件标识。在以下示例中,扩展 maven
由 .bzl 文件 @rules_jvm_external//:extension.bzl 和名称
maven 标识:
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
从不同的 .bzl 文件重新导出扩展会为其赋予新的身份,如果传递模块图中使用了这两个版本的扩展,则它们将分别进行评估,并且只会看到与该特定身份关联的标记。
作为扩展作者,您应确保用户只会从一个 .bzl 文件中使用您的模块扩展。
仓库名称和可见性
扩展生成的 repo 的规范名称采用 module_repo_canonical_name~extension_name~repo_name 的形式。对于托管在根模块中的扩展,module_repo_canonical_name 部分将替换为字符串
_main。请注意,规范名称格式不是您应依赖的 API,它可能会随时发生变化。
此命名政策意味着每个扩展都有自己的“repo 命名空间”;两个不同的扩展可以各自定义一个具有相同名称的 repo,而不会有任何冲突风险。这也意味着
repository_ctx.name 会报告 repo 的规范名称,该名称与 repo 规则调用中指定的名称不同。
考虑到模块扩展生成的 repo,有以下几条 repo 可见性规则:
- Bazel 模块 repo 可以通过
bazel_dep和use_repo查看其MODULE.bazel文件 中引入的所有 repo。 - 模块扩展生成的 repo 可以查看托管该扩展的模块可见的所有 repo,以及同一模块扩展生成的所有其他 repo(使用 repo 规则调用中指定的名称作为其显示名称)。
- 这可能会导致冲突。如果模块 repo 可以看到显示名称为
foo的 repo,并且扩展生成了一个指定名称为foo的 repo,那么对于该扩展生成的所有 repo,foo都指向前者。
- 这可能会导致冲突。如果模块 repo 可以看到显示名称为
最佳实践
本部分介绍了编写扩展时的最佳实践,以便扩展易于使用、易于维护,并且能够很好地适应随时间发生的变化。
将每个扩展放在单独的文件中
当扩展位于不同的文件中时,一个扩展可以加载另一个扩展生成的仓库。即使您不使用此功能,最好也将它们放在单独的文件中,以防日后需要使用。这是因为扩展的身份基于其文件,因此稍后将扩展移到另一个文件会更改您的公共 API,并且对于您的用户来说是向后不兼容的更改。