使用 Bzlmod 管理外部依赖项

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

Bzlmod 是新的外部依赖项系统的代号 Bazel 5.0 中引入的库。引入这个框架的目的是解决 无法逐步修复的旧系统;请参阅 原始设计文档中的“问题陈述”部分 了解详情。

在 Bazel 5.0 中,Bzlmod 在默认情况下处于停用状态;旗帜 必须指定 --experimental_enable_bzlmod,才能获取以下内容: 效果。顾名思义,此功能目前处于实验阶段; 在该功能正式发布之前,API 和行为可能会发生变化。

要将项目迁移到 Bzlmod,请按照 Bzlmod 迁移指南操作。 您还可以在示例代码库中找到 Bzlmod 用法示例。

Bazel 模块

基于 WORKSPACE 的旧版外部依赖项系统以 通过“代码库规则”(或“代码库规则”)创建的“代码库”。 虽然代码库仍然是新系统中的一个重要概念,但模块是新系统中的 核心依赖项单元。

模块本质上是一个 Bazel 项目,可以有多个版本,每个 用于发布其所依赖的其他模块的元数据。这是 类似于其他依赖项管理系统中熟悉的概念: artifact、npm package、Cargo crate、Go 模块

模块仅使用 nameversion 对指定其依赖项, 而不是 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 文件支持:

版本格式

Bazel 拥有多元化的生态系统,项目使用各种版本控制方案。通过 是迄今为止最受欢迎的软件版本 SemVer,但 也有一些采用不同架构的优秀项目 Abseil,其 版本基于日期,例如 20210324.2)。

因此,Bzlmod 采用了更为宽松的 SemVer 规范。通过 差异包括:

  • SemVer 规定,“发布”部分版本必须包含 3 细分受众群:MAJOR.MINOR.PATCH。在 Bazel 中,这项要求放宽了 允许任意数量的细分受众群
  • 在 SemVer 中,“版本”中的每个细分部分只能包含数字。 在 Bazel 中,我们放宽了限制,允许使用字母, 语义匹配“identifiers”在“预发布”中部分。
  • 此外,主要、次要和补丁版本递增的语义 未强制执行。(不过,请参阅兼容性级别,了解 详细了解我们如何表示向后兼容性。)

任何有效的 SemVer 版本都是有效的 Bazel 模块版本。此外,还有两个 SemVer 版本 ab 会比较 a < ba < 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)
  • 显而易见的代码库名称:要在 BUILD.bzl 文件。同一依赖项可能具有不同的 在不同代码库中使用新名称
    具体规则如下:

    • 对于 Bazel 模块代码库:module_name 默认值,也可以是 repo_name 属性中指定的名称, bazel_dep
    • 对于模块扩展程序代码库:通过以下方式引入的代码库名称: use_repo

每个仓库都有一个直接依赖项的仓库映射字典, 这是从显而易见的代码库名称到规范代码库名称的映射。 在构建 标签。请注意,规范代码库名称不会发生冲突, 通过解析 MODULE.bazel 因此可以很容易地捕获和解决冲突,而不会影响 其他依赖项

Strict 依赖项

新的依赖项规范格式使我们能够执行更严格的检查。在 具体而言,我们现在强制要求模块只能使用通过其代码库创建的 直接依赖项这有助于防止意外中断和难以调试的中断 当传递依赖关系图中的内容发生变化时触发。

严格依赖项的实现依据 代码库映射。基本上, 每个代码库的代码库映射包含它的所有直接依赖项, 其他代码库不可见。每个代码库的可见依赖项为 确定如下:

  • Bazel 模块代码库可以查看 MODULE.bazel 文件中引入的所有代码库 通过bazel_depuse_repo
  • 模块扩展代码库可以查看该模块的所有可见依赖项 提供扩展程序,以及由同一模块生成的所有其他代码库 。

注册表

Bzlmod 通过从 Bazel 请求依赖项信息来发现依赖项 注册表。Bazel 注册表就是 Bazel 模块的数据库。唯一 受支持的注册表形式是索引注册表, 本地目录或采用特定格式的静态 HTTP 服务器。在 未来,我们计划添加对单模块注册表的支持, 包含项目源代码和历史记录的 Git 代码库。

索引注册表

索引注册表是本地目录或包含 模块列表的相关信息,包括模块主页、维护人员、 每个版本的 MODULE.bazel 文件,以及如何提取每个版本的源代码 版本。值得注意的是,它无需自行提供源归档。

索引注册表必须遵循以下格式:

  • /bazel_registry.json:包含注册表元数据的 JSON 文件,例如: <ph type="x-smartling-placeholder">
      </ph>
    • mirrors:指定用于源归档的镜像列表。
    • module_base_path,用于指定包含 local_repositorysource.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>
        • typelocal_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 将抛出错误。
    • 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.1B 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.depmaven.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.depmaven.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 规则。