BUILD files that can
create targets. By the end of the
loading phase, legacy macros don’t exist
anymore, and Bazel sees only the concrete set of instantiated rules.
Why you shouldn’t use legacy macros (and should use Symbolic macros instead)
Where possible you should use symbolic macros. Symbolic macros- Prevent action at a distance
- Make it possible to hide implementation details through granular visibility
- Take typed attributes, which in turn means automatic label and select conversion.
- Are more readable
- Will soon have lazy evaluation
Usage
The typical use case for a macro is when you want to reuse a rule. For example, genrule in aBUILD file generates a file using //:generator
with a some_arg argument hardcoded in the command:
$@ is a
Make variable that
refers to the execution-time locations of the files in the outs attribute
list. It is equivalent to $(locations :file.txt).
If you want to generate more files with different arguments, you may want to
extract this code to a macro function. To create a macro called
file_generator, which has name and arg parameters, we can replace the
genrule with the following:
file_generator symbol from a .bzl file located in the
//path package. By putting macro function definitions in a separate .bzl
file, you keep your BUILD files clean and declarative, The .bzl file can be
loaded from any package in the workspace.
Finally, in path/generator.bzl, write the definition of the macro to
encapsulate and parameterize the original genrule definition:
$@ for outputs, $< expands to the locations of files in the
srcs attribute list.
Expanding macros
When you want to investigate what a macro does, use thequery command with
--output=build to see the expanded form:
Instantiating native rules
Native rules (rules that don’t need aload() statement) can be instantiated
from the native module:
BUILD file is calling
the macro), use the function
native.package_name(). Note that
native can only be used in .bzl files, and not in BUILD files.
Label resolution in macros
Since legacy macros are evaluated in the loading phase, label strings such as"//foo:bar" that occur in a legacy macro are interpreted relative to the
BUILD file in which the macro is used rather than relative to the .bzl file
in which it is defined. This behavior is generally undesirable for macros that
are meant to be used in other repositories, such as because they are part of a
published Starlark ruleset.
To get the same behavior as for Starlark rules, wrap the label strings with the
Label constructor:
--incompatible_resolve_select_keys_eagerly flag enabled, all keys
that are label strings will be automatically resolved to Label objects
relative to the package of the file that contains the select call. If this is
not chosen, wrap the label string with
native.package_relative_label().
Debugging
-
bazel query --output=build //my/path:allwill show you how theBUILDfile looks after evaluation. All legacy macros, globs, loops are expanded. Known limitation:selectexpressions are 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):bash $ bazel query --output=build 'attr(generator_function, my_macro, //my/path:all)' -
To find out where exactly the rule
foois generated in aBUILDfile, you can try the following trick. Insert this line near the top of theBUILDfile:cc_library(name = "foo"). Run Bazel. You will get an exception when the rulefoois 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 removeprintcalls, or make them conditional under adebuggingparameter that defaults toFalsebefore 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 theirBUILD file. It is not possible to catch an error.
Conventions
-
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
BUILDfiles, thenameargument of the macros must be a keyword argument (not a positional argument). -
The
nameattribute of rules generated by a macro should include the name argument as a prefix. For example,macro(name = "foo")can generate acc_libraryfooand a genrulefoo_gen. -
In most cases, optional parameters should have a default value of
None.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 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
visibilityargument.