本页面介绍了使用 切面的基础知识和优势,并提供了简单示例和高级 示例。
切面允许使用其他信息 和操作来扩充 build 依赖关系图。以下是一些切面可能很有用的典型场景:
- 集成 Bazel 的 IDE 可以使用切面来收集有关项目的信息。
- 代码生成工具可以利用切面以
与目标无关的方式对其输入执行操作。例如,
BUILD文件可以指定 protobuf 库定义的层次结构 ,而特定于语言的规则可以使用切面来附加 为特定语言生成 protobuf 支持代码的操作。
切面基础知识
BUILD 文件提供了项目源代码的说明:哪些源
文件是项目的一部分,应从
这些文件构建哪些工件(目标),这些文件之间的依赖关系是什么等。Bazel 使用
此信息执行 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 文件)的操作,并将信息(例如这些工件的位置
和名称)传递给这些目标的反向依赖项(在
提供程序中)。
切面与规则类似,因为它们都有一个实现函数,用于 生成操作并返回提供程序。不过,它们的强大之处在于 为其构建依赖关系图的方式。切面具有实现 和它沿途传播的所有属性的列表。假设切面 A 沿途传播名为“deps”的属性。此切面可以应用于 目标 X,从而生成切面应用节点 A(X)。在应用期间, 切面 A 会以递归方式应用于 X 在其“deps” 属性(A 的传播列表中的所有属性)中引用的所有目标。
因此,将切面 A 应用于目标 X 的单个操作会生成 下图所示的目标的原始依赖关系图的“影子图”:

图 2.包含切面的 build 图。
被遮盖的边仅是沿传播集中的属性的边,因此在此
示例中,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 []
切面实现函数与规则实现 函数类似。它们会返回 提供程序,可以生成 操作,并接受两个实参:
实现函数可以通过
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 定义了切面的一组属性。公共切面属性
定义了参数,并且只能是 bool、int 或 string 类型。
对于规则传播的切面,int 和 string 参数必须指定
values。此示例有一个名为 extension
的参数,该参数允许将“*”“h”或“cc”作为值。
对于规则传播的切面,参数值取自请求
切面的规则,使用具有相同名称和类型的规则的属性。
(请参阅 file_count_rule 的定义)。
对于命令行切面,可以使用
--aspects_parameters
标志传递参数值。可以省略 int 和 string 参数的 values 限制。
切面还允许具有 label 或
label_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,则 ctx.rule.attr.deps 的 A(X) 将为 [A(Y), A(Z)]。
在此示例中,ctx.rule.attr.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 = '*'),
},
)
规则实现演示了如何访问 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 以递归方式访问的所有目标评估我们的切面。