aboutsummaryrefslogtreecommitdiffhomepage
path: root/site/docs/skylark/macros.md
blob: d91bb7eb635adb7bbd61db48356b98f9717bf9a7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
---
layout: documentation
title: Macros
---

# Macros

<!-- [TOC] -->

## 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](concepts.md#evaluation-model),
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](lib/native.html) module, e.g.

```python
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()](lib/native.html#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) or `generator_name` (the name attribute of the macro),
    e.g.
    ```bash
    $ 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 rule `foo` is created (due to a name conflict), which will show you the
    full stack trace.

*   You can also use [print](lib/globals.html#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 a `debugging`
    parameter that defaults to `False` before submitting the code to the depot.

## Errors

If you want to throw an error, use the [fail](lib/globals.html#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](https://www.python.org/dev/peps/pep-0257/#one-line-docstrings).

*   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 a
    `cc_library` `foo` and a genrule `foo_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
    with `0`, `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 to `None`) 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 > $@",
)
```