旧版宏是从 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 分配了可见性值。这样,宏作者就可以隐藏中间规则的输出,使其不被 工作区中的其他目标所依赖 。
展开宏
如果您想了解宏的作用,请使用带有
--output=build 的 query 命令查看展开形式:
$ 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 文件中使用,而不能在 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 repo, 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调用,或使其在默认值为False的debugging参数下有条件地执行。
错误
如果您想抛出错误,请使用 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属性应包含名称 实参作为前缀。例如,macro(name = "foo")可以生成cc_libraryfoo和 genrulefoo_gen。在大多数情况下,可选参数的默认值应为
None。None可以直接传递给原生规则,原生规则会将其视为 您未传入任何实参。因此,无需为此目的将其替换为0、False或[]。相反,宏应遵循 其创建的规则,因为它们的默认值可能很复杂或可能会随时间而变化 。此外,通过查询语言或构建系统内部访问时,显式设置为其默认值的参数 与从未设置(或设置为None)的参数看起来不同。宏应具有可选的
visibility参数。