模块扩展

<ph type="x-smartling-placeholder"></ph> 报告问题 查看来源 敬上 每晚 · 7.3。 · 7.2 条 · 7.1。 · 7.0。 · 6.5

模块扩展允许用户通过读取输入数据来扩展模块系统 从依赖关系图的各个模块中,执行必要的逻辑来解析 以及通过调用 repo 规则创建代码库。这些扩展程序 具有与代码库规则类似的功能,使它们能够执行文件 I/O、 发送网络请求等等。除此之外,它们还可让 Bazel 与其他软件包管理系统交互 使用 Bazel 模块构建的依赖关系图。

您可以在 .bzl 文件中定义模块扩展,就像定义 Repo 规则一样。它们是 不能直接调用相反,每个模块都会指定一段称为“标记”的数据 以供扩展程序读取在评估任何模块之前,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.installmaven.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 指令引入代码库 在当前模块的作用域内

use_repo(maven, "maven")

扩展程序生成的代码库是其 API 的一部分。在此示例中, “maven”模块扩展会承诺生成一个名为 maven 的代码库。使用 声明,该扩展程序会正确解析 @maven//:org_junit_junit,指向由“maven”生成的代码库 。

扩展定义

您可以定义模块扩展(类似于 Repo 规则),使用 module_extension 函数。不过, 虽然 repo 规则有多个属性,但模块扩展 tag_class,每个元素都含有 属性。标记类用于定义此扩展程序所使用的标记的架构。对于 “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.installmaven.artifact 标记可以 属性架构。

模块扩展的实现功能与 repo 的实现功能类似 但会获取一个 module_ctx 对象, 该权限可授予对使用该扩展程序和所有相关标记的所有模块的访问权限。 然后,实现函数会调用 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)

扩展程序身份

模块扩展由显示的名称和 .bzl 文件标识 在对 use_extension 的调用中。在以下示例中,扩展程序 maven.bzl 文件 @rules_jvm_external//:extension.bzl 和 名称 maven

maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

从其他 .bzl 文件重新导出扩展程序可为其提供新身份 如果传递模块图中同时使用了两个版本的扩展, 那么系统会单独评估它们,并且只会看到 具有这种特定身份认同的群体。

作为扩展程序的开发者,您应确保用户只会使用您的 模块扩展。.bzl

代码库名称和可见性

扩展程序生成的代码库具有 module_repo_canonical_name~extension_name~repo_name 形式的规范名称。对于托管在 根模块,module_repo_canonical_name 部分是 替换为字符串_main。请注意,规范名称格式不是 您应依赖的 API - 该 API 随时可能发生变化。

此命名政策表示,每个扩展程序都有自己的“repo 命名空间”;二 不同的扩展程序可以分别定义具有相同名称的代码库,而不会存在风险 避免任何冲突这还意味着,repository_ctx.name 会报告规范名称 代码库的名称,该名称不同于代码库规则中指定的名称 调用。

考虑到模块扩展程序生成的代码库, 几项代码库可见性规则:

  • Bazel 模块代码库可以查看其 MODULE.bazel 文件中引入的所有代码库 通过bazel_depuse_repo
  • 由模块扩展程序生成的代码库可以查看 托管扩展程序的模块,以及 相同的模块扩展程序(使用在 Repo 规则调用中指定的名称, 和显而易见的名称)。
    • 这可能会导致冲突。如果模块代码库可以看到包含以下内容的代码库: 表观名称 foo,并且该扩展程序会生成一个包含 指定名称 foo,则针对该扩展程序生成的所有代码库 foo 是指前者。

最佳做法

本部分介绍了编写扩展程序的最佳实践, 易于使用、可维护,并能很好地适应随着时间的推移而发生的变化。

将每个扩展名放在单独的文件中

当扩展程序位于不同的文件中时,允许加载同一个扩展程序 创建代码库即使您不使用 所以最好将它们放在不同的文件中 以备不时之需 。这是因为扩展程序的身份基于其文件,因此将 转换为另一个文件,之后公共 API 也会随之更改。 不兼容的更改。

指定操作系统和架构

如果您的扩展程序依赖于操作系统或其架构类型, 请务必使用 os_dependent 在扩展定义中指明这一点 和 arch_dependent 布尔值属性。这样可以确保 Bazel 识别 如果其中任一方面发生变化,则需要重新评估。

只有根模块会直接影响代码库名称

请注意,当扩展程序创建代码库时 扩展的命名空间也就是说,如果不同 模块使用相同的扩展程序,并最终创建具有相同 名称。这通常表现为模块扩展程序的 tag_class 具有 name 作为代码库规则的 name 值传递的参数。

例如,假设根模块 A 依赖于模块 B。两个模块 依赖于模块 mylang。如果同时调用 AB mylang.toolchain(name="foo"),他们都会尝试创建一个名为 foo 模块内,mylang 模块内将会发生错误。

为避免这种情况,请移除直接设置代码库名称的功能, 或仅允许根模块执行此操作。允许根模块 因为没有什么能依赖它,所以不必担心 另一个模块创建了有冲突的名称。