2022 年 BazelCon 将于 11 月 16 日至 17 日在纽约和线上举办。
立即报名!

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

本页介绍了使用宏的基础知识,包括典型的用例、调试和惯例。

宏是在 BUILD 文件中调用的函数,可用于实例化规则。宏主要用于对现有规则和其他宏进行封装和代码重复使用。在加载阶段结束时,宏将不再存在,Bazel 只会看到一组具体的实例化规则。

使用情况

宏的一个典型用例就是要重复使用规则。

例如,BUILD 文件中的 Genrule 使用 //:generator 生成一个文件,该文件在命令中硬编码了 some_arg 参数:

genrule(
    name = "file",
    outs = ["file.txt"],
    cmd = "$(location //:generator) some_arg > $@",
    tools = ["//:generator"],
)

如果要生成具有不同参数的更多文件,您可能需要将此代码提取到宏函数中。我们调用宏 file_generator,其中包含 namearg 参数。将 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 文件中使用,而不能在 WORKSPACEBUILD 文件中使用。

宏中的标签分辨率

由于宏是在加载阶段评估的,因此宏中出现的标签字符串(如 "//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)'

  • 要了解规则 fooBUILD 文件中的生成位置,您可以尝试以下技巧。在 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 属性应包含名称参数作为前缀。例如,macro(name = "foo") 可以生成 cc_library foo 和 Genrule foo_gen

  • 在大多数情况下,可选参数的默认值为 NoneNone 可以直接传递给原生规则,这与未传入任何参数相同。因此,无需将其替换为 0False[]。相反,该宏应遵循其所创建的规则,因为其默认值可能很复杂,也可能随时间而变化。此外,显式设置为默认值的参数看起来与通过查询语言或构建系统内部构件访问时未设置(或设置为 None)的参数不同。

  • 宏应包含可选的 visibility 参数。