A macro is a function called from the BUILD file that can instantiate rules. Macros are mainly used for encapsulation and code reuse of existing rules and other macros. By the end of the loading phase, macros don’t exist anymore, and Bazel sees only the concrete set of instantiated rules.
The typical use-case for a macro is when you want to reuse a rule.
For example, we have a genrule in a BUILD file that generates a file
//:generator with a
some_arg argument hardcoded in the command:
genrule( name = "file", outs = ["file.txt"], cmd = "$(location //:generator) some_arg > $@", tools = ["//:generator"], )
$@is a Make variable that refers to the execution-time locations of the files in the
outsattribute list. It is equivalent to
If you want to generate more files with different arguments, you may want to
extract this code to a macro function. Let’s call the macro
arg parameters. Replace the genrule with the following:
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", )
Here, we are loading the
file_generator symbol from a
.bzl file located
//path package. By putting macro function definitions in a separate
.bzl file, we can keep our BUILD files clean and declarative, The
file can be loaded from any package in the workspace.
path/generator.bzl, let’s write the definition of the macro to
encapsulate and parameterize our original genrule definition:
def file_generator(name, arg, visibility=None): native.genrule( name = name, outs = [name + ".txt"], cmd = "$(location //:generator) %s > $@" % arg, tools = ["//:generator"], visibility = visibility, )
You can also use macros to chain rules together. This example shows chained genrules, where a genrule uses the outputs of a previous genrule as inputs:
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, )
Note that we only assigned the value of
visibility to the second genrule.
This enables macro authors hide outputs of intermediate rules from being
depended upon by other targets in the workspace.
Tip: Similar to
$<expands to the locations of files in the
When you want to investigate what a macro does, use the
query command with
--output=build to see the expanded form:
$ 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 > $@", )
Instantiating native rules
Native rules (i.e. rules that don’t need a
load() statement) can be
instantiated from the native module, e.g.
def my_macro(name, visibility=None): native.cc_library( name = name, srcs = ["main.cc"], visibility = visibility, )
If you need to know the package name (i.e. which BUILD file is calling the macro), use the function native.package_name().
bazel query --output=build //my/path:allwill show you how the BUILD file looks after evaluation. All macros, globs, loops are expanded. Known limitation:
selectexpressions are currently not shown in the output.
- You may filter the output based on
generator_function(which function generated the rules) or
generator_name(the name attribute of the macro), e.g.
$ bazel query --output=build 'attr(generator_function, my_macro, //my/path:all)'
To find out where exactly the rule
foois generated in a BUILD file, you can try the following trick. Insert this line near the top of the BUILD file:
cc_library(name = "foo"). Run Bazel. You will get an exception when the rule
foois created (due to a name conflict), which will show you the full stack trace.
- You can also use print for debugging. It displays
the message as a
DEBUGlog line during the loading phase. Except in rare cases, either remove
debuggingparameter that defaults to
Falsebefore submitting the code to the depot.
If you want to throw an error, use the fail function. Explain clearly to the user what went wrong and how to fix their BUILD file. It is not possible to catch an error.
def my_macro(name, deps, visibility=None): if len(deps) < 2: fail("Expected at least two values in deps") # ...
All public functions (functions that don’t start with underscore) that instantiate rules must have a
nameargument. This argument should not be optional (don’t give a default value).
Public functions should use a docstring following Python conventions.
In BUILD files, the
nameargument of the macros must be a keyword argument (not a positional argument).
nameattribute of rules generated by a macro should include the name argument as a prefix. For example,
macro(name = "foo")can generate a
fooand a genrule
In most cases, optional parameters should have a default value of
Nonecan be passed directly to native rules, which treat it the same as if you had not passed in any argument. Thus, there is no need to replace it with
for this purpose. Instead, the macro should defer to the rules it creates, as their defaults may be complex or may change over time. Additionally, a parameter that is explicitly set to its default value looks different than one that is never set (or set to
None) when accessed through the query language or build-system internals.
Macros should have an optional