Macros
Macro creation
A macro is a function called from the BUILD file that can instantiate rules. Macros don’t give additional power, they are just used for encapsulation and code reuse. By the end of the loading phase, macros don’t exist anymore, and Bazel sees only the set of rules they created.
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().
Debugging
-
bazel query --output=build //my/path:all
will show you how the BUILD file looks after evaluation. All macros, globs, loops are expanded. Known limitation:select
expressions are currently not shown in the output. - You may filter the output based on
generator_function
(which function generated the rules) orgenerator_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
foo
is 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 rulefoo
is 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 warning during the loading phase. Except in rare cases,
either remove
print
calls, or make them conditional under adebugging
parameter that defaults toFalse
before submitting the code to the depot.
Errors
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")
# ...
Conventions
-
All public functions (functions that don’t start with underscore) that instantiate rules must have a
name
argument. 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
name
argument of the macros must be a keyword argument (not a positional argument). -
The
name
attribute of rules generated by a macro should include the name argument as a prefix. For example,macro(name = "foo")
can generate acc_library
foo
and a genrulefoo_gen
. -
In most cases, optional parameters should have a default value of
None
.None
can 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 with0
,False
, or[]
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 toNone
) when accessed through the query language or build-system internals. -
Macros should have an optional
visibility
argument.
Full example
The typical use-case for a macro is when you want to reuse a genrule, e.g.
genrule(
name = "file",
outs = ["file.txt"],
cmd = "$(location generator) some_arg > $@",
tools = [":generator"],
)
If you want to generate another file with different arguments, you may want to extract this code to a function.
The BUILD file will become simply:
load("//path:generator.bzl", "file_generator")
file_generator(
name = "file",
arg = "some_arg",
)
In order to keep BUILD files clean and declarative, you must put the function in
a separate .bzl
file. For example, write the definition of the macro in
path/generator.bzl
:
def file_generator(name, arg, visibility=None):
native.genrule(
name = name,
outs = [name + ".txt"],
cmd = "$(location generator) %s > $@" % arg,
tools = ["//test:generator"],
visibility = visibility,
)
When you want to investigate what a macro does, use the following command to see the expanded form:
$ bazel query --output=build :file
# /absolute/path/test/ext.bzl:42:3
genrule(
name = "file",
tools = ["//test:generator"],
outs = ["//test:file.txt"],
cmd = "$(location generator) some_arg > $@",
)