blob: 2ac04ebc83be2080e2bf194dea0dbebecc010805 [file] [log] [blame] [view] [edit]
Project: /_project.yaml
Book: /_book.yaml
{# disableFinding("Currently") #}
{# disableFinding(TODO) #}
# Macros
This page covers the basics of using macros and includes typical use cases,
debugging, and conventions.
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.
Macros come in two flavors: symbolic macros, which are described on this page,
and [legacy macros](legacy-macros.md). Where possible, we recommend using
symbolic macros for code clarity.
Symbolic macros offer typed arguments (string to label conversion, relative to
where the macro was called) and the ability to restrict and specify the
visibility of targets created. They are designed to be amenable to lazy
evaluation (which will be added in a future Bazel release). Symbolic macros are
available by default in Bazel 8. Where this document mentions `macros`, it's
referring to **symbolic macros**.
## Usage {:#usage}
Macros are defined in `.bzl` files by calling the `macro()` function with two
parameters: `attrs` and `implementation`.
### Attributes {:#attributes}
`attrs` accepts a dictionary of attribute name to [attribute
types](https://bazel.build/rules/lib/toplevel/attr#members), which represents
the arguments to the macro. Two common attributes - name and visibility - are
implicitly added to all macros and are not included in the dictionary passed to
attrs.
```starlark
# macro/macro.bzl
my_macro = macro(
attrs = {
"deps": attr.label_list(mandatory = True, doc = "The dependencies passed to the inner cc_binary and cc_test targets"),
"create_test": attr.bool(default = False, configurable = False, doc = "If true, creates a test target"),
},
implementation = _my_macro_impl,
)
```
Attribute type declarations accept the
[parameters](https://bazel.build/rules/lib/toplevel/attr#parameters),
`mandatory`, `default`, and `doc`. Most attribute types also accept the
`configurable` parameter, which determines wheher the attribute accepts
`select`s. If an attribute is `configurable`, it will parse non-`select` values
as an unconfigurable `select` - `"foo"` will become
`select({"//conditions:default": "foo"})`. Learn more in [selects](#selects).
### Implementation {:#implementation}
`implementation` accepts a function which contains the logic of the macro.
Implementation functions often create targets by calling one or more rules, and
they are are usually private (named with a leading underscore). Conventionally,
they are named the same as their macro, but prefixed with `_` and suffixed with
`_impl`.
Unlike rule implementation functions, which take a single argument (`ctx`) that
contains a reference to the attributes, macro implementation functions accept a
parameter for each argument.
```starlark
# macro/macro.bzl
def _my_macro_impl(name, deps, create_test):
cc_library(
name = name + "_cc_lib",
deps = deps,
)
if create_test:
cc_test(
name = name + "_test",
srcs = ["my_test.cc"],
deps = deps,
)
```
### Declaration {:#declaration}
Macros are declared by loading and calling their definition in a `BUILD` file.
```starlark
# pkg/BUILD
my_macro(
name = "macro_instance",
deps = ["src.cc"] + select(
{
"//config_setting:special": ["special_source.cc"],
"//conditions:default": [],
},
),
create_tests = True,
)
```
This would create targets
`//pkg:macro_instance_cc_lib` and`//pkg:macro_instance_test`.
## Details {:#usage-details}
### naming conventions for targets created {:#naming}
The names of any targets or submacros created by a symbolic macro must
either match the macro's `name` parameter or must be prefixed by `name` followed
by `_` (preferred), `.` or `-`. For example, `my_macro(name = "foo")` may only
create files or targets named `foo`, or prefixed by `foo_`, `foo-` or `foo.`,
for example, `foo_bar`.
Targets or files that violate macro naming convention can be declared, but
cannot be built and cannot be used as dependencies.
Non-macro files and targets within the same package as a macro instance should
*not* have names that conflict with potential macro target names, though this
exclusivity is not enforced. We are in the progress of implementing
[lazy evaluation](#laziness) as a performance improvement for Symbolic macros,
which will be impaired in packages that violate the naming schema.
### restrictions {:#restrictions}
Symbolic macros have some additional restrictions compared to legacy macros.
Symbolic macros
* must take a `name` argument and a `visibility` argument
* must have an `implementation` function
* may not return values
* may not mutate their `args`
* may not call `native.existing_rules()` unless they are special `finalizer`
macros
* may not call `native.package()`
* may not call `glob()`
* may not call `native.environment_group()`
* must create targets whose names adhere to the [naming schema](#naming)
* can't refer to input files that weren't declared or passed in as an argument
(see [visibility](#visibility) for more details).
### Visibility {:#visibility}
TODO: Expand this section
#### Target visibility {:#target-visibility}
At default, targets created by symbolic macros are visible to the package in
which they are created. They also accept a `visibility` attribute, which can
expand that visibility to the caller of the macro (by passing the `visibility`
attribute directly from the macro call to the target created) and to other
packages (by explicitly specifying them in the target's visibility).
#### Dependency visibility {:#dependency-visibility}
Macros must have visibility to the files and targets they refer to. They can do
so in one of the following ways:
* Explicitly passed in as an `attr` value to the macro
```starlark
# pkg/BUILD
my_macro(... deps = ["//other_package:my_tool"] )
```
* Implicit default of an `attr` value
```starlark
# my_macro:macro.bzl
my_macro = macro(
attrs = {"deps" : attr.label_list(default = ["//other_package:my_tool"])} )
```
* Already visible to the macro definition
```starlark
# other_package/BUILD
cc_binary(
name = "my_tool",
visibility = "//my_macro:\\__pkg__",
)
```
### Selects {:#selects}
If an attribute is `configurable`, then the macro implementation function will
always see the attribute value as `select`-valued. For example, consider the
following macro:
```starlark
my_macro = macro(
attrs = {"deps": attr.label_list()}, # configurable unless specified otherwise
implementation = _my_macro_impl,
)
```
If `my_macro` is invoked with `deps = ["//a"]`, that will cause `_my_macro_impl`
to be invoked with its `deps` parameter set to `select({"//conditions:default":
["//a"]})`.
Rule targets reverse this transformation, and store trivial `select`s as their
unconditional values; in this example, if `_my_macro_impl` declares a rule
target `my_rule(..., deps = deps)`, that rule target's `deps` will be stored as
`["//a"]`.
## Finalizers {:#finalizers}
A rule finalizer is a special symbolic macro which - regardless of its lexical
position in a BUILD file - is evaluated in the final stage of loading a package,
after all non-finalizer targets have been defined. Unlike ordinary symbolic
macros, a finalizer can call `native.existing_rules()`, where it behaves
slightly differently than in legacy macros: it only returns the set of
non-finalizer rule targets. The finalizer may assert on the state of that set or
define new targets.
To declare a finalizer, call `macro()` with `finalizer = True`:
```starlark
def _my_finalizer_impl(name, visibility, tags_filter):
for r in native.existing_rules().values():
for tag in r.get("tags", []):
if tag in tags_filter:
my_test(
name = name + "_" + r["name"] + "_finalizer_test",
deps = [r["name"]],
data = r["srcs"],
...
)
continue
my_finalizer = macro(
attrs = {"tags_filter": attr.string_list(configurable = False)},
implementation = _impl,
finalizer = True,
)
```
## Laziness {:#laziness}
IMPORTANT: We are in the process of implementing lazy macro expansion and
evaluation. This feature is not available yet.
Currently, all macros are evaluated as soon as the BUILD file is loaded, which
can negatively impact performance for targets in packages that also have costly
unrelated macros. In the future, non-finalizer symbolic macros will only be
evaluated if they're required for the build. The prefix naming schema helps
Bazel determine which macro to expand given a requested target.
## Migration troubleshooting {:#troubleshooting}
Here are some common migration headaches and how to fix them.
* Legacy macro calls `glob()`
Move the `glob()` call to your BUILD file (or to a legacy macro called from the
BUILD file), and pass the `glob()` value to the symbolic macro using a
label-list attribute:
```starlark
# BUILD file
my_macro(
...,
deps = glob(...),
)
```
* Legacy macro has a parameter that isn't a valid starlark `attr` type.
Pull as much logic as possible into a nested symbolic macro, but keep the
top level macro a legacy macro.
* Legacy macro calls a rule that creates a target that breaks the naming schema
That's okay, just don't depend on the "offending" target. The naming check will
be quietly ignored.