本页面介绍了使用方面的基础知识和好处,并提供了简单和高级示例。
通过方面,您可以使用其他信息和操作来增强构建依赖项图。切面可能有用的一些典型场景:
- 集成 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
文件)的操作,并将信息(例如这些工件的位置和名称)传递给 provider 中这些目标的反向依赖项。
与规则类似,方面具有用于生成操作并返回提供程序的实现函数。不过,它们的强大之处在于依赖项图的构建方式。切面有一个实现及其传播的所有属性的列表。假设有一个沿着名为“deps”的属性传播的方面 A。此方面可以应用于目标 X,从而生成方面应用节点 A(X)。在应用过程中,方面 A 以递归方式应用于 X 的“deps”属性(即 A 的传播列表中的所有属性)中引用的所有目标。
因此,将切面 A 应用于目标 X 的单个操作会生成目标原始依赖关系图的“影子图”,如下图所示:
图 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 []
方面实现函数类似于规则实现函数。它们会返回提供程序、可以生成操作并采用两个参数:
实现函数可以通过 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
用于定义某个方面(aspect)的一组属性。公开的 aspect 属性用于定义参数,并且只能是 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
(取自切面)除外,则会发生错误。这意味着,aspect 实现绝不能返回 DefaultInfo
。
参数和私有属性通过 ctx
的属性进行传递。此示例引用 extension
参数,并确定要统计哪些文件。
对于返回的提供程序,沿着哪些属性传播了此方面(从 attr_aspects
列表中)的值会被替换为将此方面应用于这些属性的结果。例如,如果目标 X 的依赖项中包含 Y 和 Z,则 A(X) 的 ctx.rule.attr.deps
将为 [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 = '*'),
},
)
规则实现演示了如何通过 ctx.attr.deps
访问 FileCountInfo
。
规则定义演示了如何定义参数 (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
递归访问。