2022 年 BazelCon 将于 11 月 16 日至 17 日在纽约和线上举办。
立即报名!

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

本页面介绍了使用各个方面的基础知识和优势,并提供了简单和高级的示例。

通过方面,您可以使用额外的信息和操作来增强 build 依赖项图表。在某些方面比较实用的典型场景如下:

  • 集成 Bazel 的 IDE 可以使用各方面来收集有关项目的信息。
  • 代码生成工具可以利用各方面以与目标无关的方式执行输入。例如,BUILD 文件可以指定 protobuf 库定义的层次结构,特定于语言的规则可以使用某些方面来附加针对特定语言生成 protobuf 支持代码的操作。

方面基础知识

BUILD 文件提供项目源代码的说明:哪些源文件是项目的一部分、应基于这些文件构建哪些工件(目标)以及这些文件之间的依赖关系等。Bazel 使用这些信息来执行构建,也就是说,它会确定生成工件(例如运行编译器或链接器)并执行这些操作所需的一组操作。为此,Bazel 通过在目标之间构建依赖关系图并访问此图来收集这些操作,

请参考以下 BUILD 文件:

java_library(name = 'W', ...)
java_library(name = 'Y', deps = [':W'], ...)
java_library(name = 'Z', deps = [':W'], ...)
java_library(name = 'Q', ...)
java_library(name = 'T', deps = [':Q'], ...)
java_library(name = 'X', deps = [':Y',':Z'], runtime_deps = [':T'], ...)

BUILD 文件定义了如下图所示的依赖项图:

构建图

图 1. BUILD 文件依赖项图表。

Bazel 会通过针对上例中的每个目标调用相应规则(在本例中为“java_library”的实现函数)来分析此依赖关系图。规则实现函数会生成操作,这些工件会构建工件(例如 .jar 文件)并将信息(例如这些工件的位置和名称)传递到提供程序中这些目标的反向依赖项。

方面与规则类似,因为它们具有实现操作并返回提供程序的实现函数。但是,它们的强大力量来自为他们构建依赖项图的方式。方面有一个实现,以及与其一起传播的所有属性的列表。假设有一个属性 A 沿名为“deps”的属性传递。此宽高比可应用于目标 X,从而生成宽高比应用节点 A(X)。在应用过程中,方面 A 以递归方式应用于 X 在其“依赖项”属性中引用的所有目标(A' 传播列表中的所有属性)。

因此,将宽高比 A 应用于目标 X 的单次操作会生成目标的原始依赖关系图,如下图所示:

具有宽高比的 build 图

图 2. 包含多个方面的构建图。

唯一被覆盖的边缘是沿着传播集特性的边缘,因此,此示例不会覆盖 runtime_deps 边缘。然后,在影子图的所有节点上调用宽高比实现函数,方式与在原始图的节点上调用规则实现的方式类似。

简单示例

此示例演示了如何以递归方式输出某条规则及其所有具有 deps 属性的依赖项。它展示了宽高比实现和宽高比定义,以及如何从 Bazel 命令行调用相应宽高比。

def _print_aspect_impl(target, ctx):
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the files that make up the sources and
        # print their paths.
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                print(f.path)
    return []

print_aspect = aspect(
    implementation = _print_aspect_impl,
    attr_aspects = ['deps'],
)

让我们将示例拆分成几个部分,然后逐一检查。

方面定义

print_aspect = aspect(
    implementation = _print_aspect_impl,
    attr_aspects = ['deps'],
)

宽高比定义与规则定义类似,通过 aspect 函数进行定义。

就像规则一样,方面也有一个实现函数,在本例中为 _print_aspect_impl

attr_aspects 是宽高比的沿袭规则属性列表。在这种情况下,宽高比将沿其所应用的规则的 deps 属性传播。

attr_aspects 的另一个常见参数是 ['*'],它会将宽高比传播到规则的所有属性。

方面实现

def _print_aspect_impl(target, ctx):
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the files that make up the sources and
        # print their paths.
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                print(f.path)
    return []

宽高比实现函数与规则实现函数类似。它们会返回 providers,可以生成操作,并采用两个参数:

  • target:应用该宽高比的目标
  • ctx:可用于访问属性并生成输出和操作的 ctx 对象。

实现函数可以通过 ctx.rule.attr 访问目标规则的属性。它可以检查应用该扩展的目标目标(通过 target 参数)提供的提供程序。

必须提供方面才能返回提供商列表。在此示例中,方面未提供任何内容,因此会返回空列表。

使用命令行调用宽高比

应用宽高比的最简单方法是在命令行中使用 --aspects 参数。假设上述方面是在名为 print.bzl 的文件中定义:

bazel build //MyExample:example --aspects print.bzl%print_aspect

会将 print_aspect 应用于目标 example 以及可以通过 deps 属性以递归方式访问的所有目标规则。

--aspects 标志接受一个参数,该参数是 <extension file label>%<aspect top-level name> 格式的宽高比规范。

高级示例

以下示例演示了如何使用目标规则中针对目标文件进行计数的方面,并可能按扩展名过滤文件。其中介绍了如何使用提供程序返回值、如何使用参数将参数传递到宽高比实现,以及如何从规则调用宽高比。

file_count.bzl 文件:

FileCountInfo = provider(
    fields = {
        'count' : 'number of files'
    }
)

def _file_count_aspect_impl(target, ctx):
    count = 0
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the sources counting files
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
                    count = count + 1
    # Get the counts from our dependencies.
    for dep in ctx.rule.attr.deps:
        count = count + dep[FileCountInfo].count
    return [FileCountInfo(count = count)]

file_count_aspect = aspect(
    implementation = _file_count_aspect_impl,
    attr_aspects = ['deps'],
    attrs = {
        'extension' : attr.string(values = ['*', 'h', 'cc']),
    }
)

def _file_count_rule_impl(ctx):
    for dep in ctx.attr.deps:
        print(dep[FileCountInfo].count)

file_count_rule = rule(
    implementation = _file_count_rule_impl,
    attrs = {
        'deps' : attr.label_list(aspects = [file_count_aspect]),
        'extension' : attr.string(default = '*'),
    },
)

BUILD.bazel 文件:

load('//:file_count.bzl', 'file_count_rule')

cc_library(
    name = 'lib',
    srcs = [
        'lib.h',
        'lib.cc',
    ],
)

cc_binary(
    name = 'app',
    srcs = [
        'app.h',
        'app.cc',
        'main.cc',
    ],
    deps = ['lib'],
)

file_count_rule(
    name = 'file_count',
    deps = ['app'],
    extension = 'h',
)

方面定义

file_count_aspect = aspect(
    implementation = _file_count_aspect_impl,
    attr_aspects = ['deps'],
    attrs = {
        'extension' : attr.string(values = ['*', 'h', 'cc']),
    }
)

以下示例展示了宽高比通过 deps 属性传播的方式。

attrs 用于定义某个宽高比的一组属性。公共方面属性的类型为 string,称为参数。必须为参数指定 values 属性。此示例有一个名为 extension 的参数,该参数的值可以有 '*', 'h', or 'cc' 作为值。

宽高比的参数值取自请求宽高比的规则名称相同的字符串属性(请参阅 file_count_rule 的定义)。不能通过命令行使用具有参数的宽高比,因为没有指定参数的语法。

此外,宽高比还可以具有 labellabel_list 类型的专用属性。私有标签属性可用于指定对方面生成的操作所需的工具或库的依赖项。此示例中未定义私有属性,但以下代码段演示了如何将工具传递到某个方面:

...
    attrs = {
        '_protoc' : attr.label(
            default = Label('//tools:protoc'),
            executable = True,
            cfg = "exec"
        )
    }
...

方面实现

FileCountInfo = provider(
    fields = {
        'count' : 'number of files'
    }
)

def _file_count_aspect_impl(target, ctx):
    count = 0
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the sources counting files
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
                    count = count + 1
    # Get the counts from our dependencies.
    for dep in ctx.rule.attr.deps:
        count = count + dep[FileCountInfo].count
    return [FileCountInfo(count = count)]

与规则实现函数一样,宽高比实现函数会返回可供其依赖项访问的提供程序的结构。

在此示例中,FileCountInfo 被定义为具有一个字段 count 的提供程序。最佳做法是使用 fields 属性明确定义提供程序的字段。

方面应用 A(X) 的一组提供程序来自于实现目标 X 的规则,且来自方面 A 的实现。规则实现传播的提供程序在应用方面应用之前创建和冻结,不能从某个方面进行修改。如果除了目标 OutputGroupInfo(规则和宽高比指定了不同的输出组)和 InstrumentedFilesInfo(取自相应方面)外,每个目标和应用于它的某个方面都具有相同的类型,这会产生错误。这意味着,宽高比实现可能永远不会返回 DefaultInfo

参数和私有属性在 ctx 的属性中传递。此示例引用了 extension 参数并确定要统计的文件。

对于返回提供程序,在其上传播宽高比的属性值(来自 attr_aspects 列表)会替换为它们的宽高比结果。例如,如果目标 X 的依赖项包含 Y 和 Z,则 A(X) 的 ctx.rule.attr.deps 将为 [A(Y), A(Z)]。在此示例中,ctx.rule.attr.deps 是目标对象,该对象是将宽高比应用于已应用宽高比的原始目标的 'deps' 的结果。

在此示例中,方面从目标的依赖项中访问 FileCountInfo 提供程序,以累积总传输文件数。

从规则调用宽高比

def _file_count_rule_impl(ctx):
    for dep in ctx.attr.deps:
        print(dep[FileCountInfo].count)

file_count_rule = rule(
    implementation = _file_count_rule_impl,
    attrs = {
        'deps' : attr.label_list(aspects = [file_count_aspect]),
        'extension' : attr.string(default = '*'),
    },
)

该规则实现演示了如何通过 ctx.attr.deps 访问 FileCountInfo

extension

通过目标规则调用某一方面

load('//:file_count.bzl', 'file_count_rule')

cc_binary(
    name = 'app',
...
)

file_count_rule(
    name = 'file_count',
    deps = ['app'],
    extension = 'h',
)

此示例演示了如何通过规则将 extension 参数传递到方面。由于 extension 参数在规则实现中具有默认值,因此 extension 会被视为可选参数。

构建 file_count 目标时,我们会评估自身的宽高比,并通过 deps 以递归方式访问所有目标。