切面

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

本页介绍了使用 API 的基础知识和好处 面向提供简单和高级的 示例。

利用切面,您可以使用额外的信息扩充构建依赖关系图 和操作。切面可能有用的一些典型场景:

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

切面基础知识

BUILD 文件提供项目源代码的说明: 文件是项目的一部分,应该基于哪些工件(目标)构建 这些文件之间的依赖关系是什么,等等 使用此信息执行构建,也就是说,它会确定 生成工件(如运行编译器或链接器)所需的 执行这些操作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 文件)并传递位置信息(例如位置信息) 以及这些工件的名称以及与这些目标的反向依赖关系 providers

切面与规则类似,因为它们有一个实现函数, 生成操作和退货提供器。不过,它们的强大力量来自于 为它们构建依赖关系图的方式。切面有一个实现 以及它传播的所有属性的列表。假设某个方面 A 沿名为“deps”的属性传播。此切面可以应用于 目标 X,从而生成切面应用节点 A(X)。在应用过程中 方面 A 以递归方式应用于 X 在其“依赖项”中引用的所有目标 属性(A 传播列表中的所有属性)。

因此,将切面 A 应用到目标 X 的单次操作就产生了一个“影子图”。/ 目标的原始依赖关系图,如下图所示:

使用 Aspect 构建图

图 2. 使用切面构建图。

唯一会阴影的边是 传播集,因此 runtime_deps 边缘在此区域中不会被覆盖 示例。然后,在 影子图,类似于在节点上调用规则实施的方式 原始图。

简单示例

此示例演示了如何以递归方式输出 规则及其所有具有 deps 属性的依赖项。它会显示 切面实现、切面定义,以及如何调用切面 运行容器

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 为切面定义一组属性。公共切面属性 定义参数,并且只能是 boolintstring 类型。 对于规则传播的切面,intstring 参数必须具有 为其指定了 values。此示例中有一个名为 extension 的参数 可以包含“*”、“h”或“cc”作为值。

对于规则传播的切面,参数值取自请求规则的规则 切面,使用名称和类型的规则属性相同。 (请参阅 file_count_rule 的定义)。

对于命令行切面,可以使用 --aspects_parameters 标记。intstring 参数的 values 限制可能为 。

切面还可以具有 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 target 的依赖项以累积总传递文件数。

从规则调用切面

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 = '*'),
    },
)

规则实施演示了如何访问 FileCountInfo 通过 ctx.attr.deps 发送。

规则定义演示了如何定义参数 (extension) 并为其指定默认值 (*)。请注意,如果使用默认值 不是“cc”“h”或“*”之一是由于 对切面定义中的参数施加的限制。

通过目标规则调用切面

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 以递归方式访问的所有目标。

参考