本页介绍了使用宏的基础知识,包括典型的用例、调试和惯例。
宏是从 BUILD
文件中调用的函数,可以实例化规则。宏主要用于对现有规则和其他宏进行封装和代码重用。在加载阶段结束时,宏将不再存在,Bazel 只会看到一套具体的实例化规则。
用量
宏的一个典型使用情形就是您想要重复使用规则。
例如,BUILD
文件中的 genrule 会使用 //:generator
生成一个文件,该文件中的 some_arg
参数会在命令中硬编码:
genrule(
name = "file",
outs = ["file.txt"],
cmd = "$(location //:generator) some_arg > $@",
tools = ["//:generator"],
)
如果要生成具有不同参数的更多文件,您可能需要将此代码提取到宏函数中。我们调用宏file_generator
,其中包含name
和arg
参数将 Genrule 替换为以下内容:
load("//path:generator.bzl", "file_generator")
file_generator(
name = "file",
arg = "some_arg",
)
file_generator(
name = "file-two",
arg = "some_arg_two",
)
file_generator(
name = "file-three",
arg = "some_arg_three",
)
在这里,您可以从 //path
软件包中的 .bzl
文件加载 file_generator
符号。通过将宏函数定义放在单独的 .bzl
文件中,您可以使 BUILD
文件保持整洁且声明式,并且可从工作区中的任何软件包加载 .bzl
文件。
最后,在 path/generator.bzl
中编写宏的定义,以封装原始 genrule 定义并对其进行参数化:
def file_generator(name, arg, visibility=None):
native.genrule(
name = name,
outs = [name + ".txt"],
cmd = "$(location //:generator) %s > $@" % arg,
tools = ["//:generator"],
visibility = visibility,
)
您也可以使用宏将规则串联起来。此示例展示了链式 genrule,其中 genrule 使用先前 genrule 的输出作为输入:
def chained_genrules(name, visibility=None):
native.genrule(
name = name + "-one",
outs = [name + ".one"],
cmd = "$(location :tool-one) $@",
tools = [":tool-one"],
visibility = ["//visibility:private"],
)
native.genrule(
name = name + "-two",
srcs = [name + ".one"],
outs = [name + ".two"],
cmd = "$(location :tool-two) $< $@",
tools = [":tool-two"],
visibility = visibility,
)
该示例只为第二个 genrule 分配可见性值。这样,宏作者就可以隐藏工作区中其他目标对中间规则的输出结果的依赖。
扩展宏
如果您想调查宏的用途,请使用 query
命令和 --output=build
来查看展开后的形式:
$ bazel query --output=build :file
# /absolute/path/test/ext.bzl:42:3
genrule(
name = "file",
tools = ["//:generator"],
outs = ["//test:file.txt"],
cmd = "$(location //:generator) some_arg > $@",
)
实例化原生规则
原生规则(不需要 load()
语句的规则)可以从原生模块实例化:
def my_macro(name, visibility=None):
native.cc_library(
name = name,
srcs = ["main.cc"],
visibility = visibility,
)
如果您需要知道软件包名称(例如,哪个 BUILD
文件在调用该宏),请使用 native.package_name() 函数。请注意,native
只能在 .bzl
文件中使用,不能在 WORKSPACE
或 BUILD
文件中使用。
宏中的标签分辨率
由于宏是在加载阶段评估的,因此宏中出现的标签字符串(如 "//foo:bar"
)将根据使用该宏的 BUILD
文件进行解释,而不是相对于用于定义该宏的 .bzl
文件进行解释。此行为通常不适合用于其他代码库的宏,例如,因为它们属于已发布的 Starlark 规则集。
如要获得与 Starlark 规则相同的行为,请使用 Label
构造函数封装标签字符串:
# @my_ruleset//rules:defs.bzl
def my_cc_wrapper(name, deps = [], **kwargs):
native.cc_library(
name = name,
deps = deps + select({
# Due to the use of Label, this label is resolved within @my_ruleset,
# regardless of its site of use.
Label("//config:needs_foo"): [
# Due to the use of Label, this label will resolve to the correct target
# even if the canonical name of @dep_of_my_ruleset should be different
# in the main workspace, such as due to repo mappings.
Label("@dep_of_my_ruleset//tools:foo"),
],
"//conditions:default": [],
}),
**kwargs,
)
调试
bazel query --output=build //my/path:all
会显示BUILD
文件在评估后的样子。所有宏、glob、循环都会展开。已知限制:select
表达式当前未显示在输出中。您可以根据
generator_function
(生成规则的函数)或generator_name
(宏的名称属性)过滤输出:bash $ bazel query --output=build 'attr(generator_function, my_macro, //my/path:all)'
如需查找规则
foo
在BUILD
文件中的生成位置,您可以尝试以下技巧。在BUILD
文件顶部附近插入下面一行代码:cc_library(name = "foo")
。运行 Bazel。创建规则foo
时会出现异常情况(由于名称冲突),该规则会显示完整的堆栈轨迹。您也可以使用 print 进行调试。它会在加载阶段以
DEBUG
日志行的形式显示消息。在极少数情况下,请先移除print
调用,或先在debugging
参数(默认为False
)下使其满足条件,然后再将代码提交到仓库。
错误
如果要抛出错误,请使用 fail 函数。
向用户明确说明哪里出了问题以及如何修正其 BUILD
文件。无法捕获错误。
def my_macro(name, deps, visibility=None):
if len(deps) < 2:
fail("Expected at least two values in deps")
# ...
惯例
所有实例化规则的公共函数(不以下划线开头的函数)都必须具有
name
参数。此参数不应为可选参数(请勿提供默认值)。公共函数应遵循 Python 惯例使用文档字符串。
在
BUILD
文件中,宏的name
参数必须是关键字参数(而不是位置参数)。由宏生成的规则的
name
属性应包含 name 参数作为前缀。例如,macro(name = "foo")
可以生成cc_library
foo
和 Genfoo_gen
。在大多数情况下,可选参数的默认值为
None
。None
可以直接传递给原生规则,就像未传入任何参数一样处理。因此,您无需为此使用0
、False
或[]
替换它。相反,该宏应遵循其创建的规则,因为其默认值可能很复杂,也可能随时间而变化。此外,明确设置为默认值的参数看起来与从查询语言或构建系统内部访问时从未设置(或设置为None
)的参数有所不同。宏应具有可选的
visibility
参数。