aboutsummaryrefslogtreecommitdiffhomepage
path: root/site/docs/skylark/aspects.md
blob: 4558004535b3326611c5027e73552c826331fdcb (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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
---
layout: documentation
title: Aspects
---

# Aspects

<!-- [TOC] -->

**Status: Experimental**. We may make breaking changes to the API, but we will
announce them.

Aspects allow augmenting build dependency graphs with additional information
and actions. Some typical scenarios when aspects can be useful:

*   IDEs that integrate Bazel can use aspects to collect information about the
    project.
*   Code generation tools can leverage aspects to execute on their inputs in
    "target-agnostic" manner. As an example, BUILD files can specify a hierarchy
    of [protobuf](https://developers.google.com/protocol-buffers/) library
    definitions, and language-specific rules can use aspects to attach
    actions generating protobuf support code for a particular language.

## Aspect basics

Bazel BUILD files provide a description of a project’s source code: what source
files are part of the project, what artifacts (_targets_) should be built from
those files, what the dependencies between those files are, etc. Bazel uses
this information to perform a build, that is, it figures out the set of actions
needed to produce the artifacts (such as running compiler or linker) and
executes those actions. Bazel accomplishes this by constructing a _dependency
graph_ between targets and visiting this graph to collect those actions.

Consider the following BUILD file:

```python
java_library(name = 'W', ...)
java_library(name = 'Y', deps = [':W'], ...)
java_library(name = 'Z', deps = [':W'], ...)
java_library(name = 'Q', ...)
java_library(name = 'T', deps = [':Q'], ...)
java_library(name = 'X', deps = [':Y',':Z'], runtime_deps = [':T'], ...)
```

This BUILD file defines a dependency graph shown in the following figure:

<img src="build-graph.png" alt="Build Graph" width="250px" />

Bazel analyzes this dependency graph by calling an implementation function of
the corresponding [rule](rules.md) (in this case "java_library") for every
target in the above example. Rule implementation functions generate actions that
build artifacts, such as `.jar` files, and pass information, such as locations
and names of those artifacts, to the dependencies of those targets in
[providers](rules.md#providers).

Aspects are similar to rules in that they have an implementation function that
generates actions and returns providers. However, their power comes from
the way the dependency graph is built for them. An aspect has an implementation
and a list of all attributes it propagates along. Consider an aspect A that
propagates along attributes named "deps". This aspect can be applied to
a target X, yielding an aspect application node A(X). During its application,
aspect A is applied recursively to all targets that X refers to in its "deps"
attribute (all attributes in A's propagation list). Thus a single act of
applying aspect A to a target X yields a "shadow graph" of the original
dependency graph of targets (see Fig.2).

![Build Graph with Aspect](build-graph-aspects.png)

The only edges that are shadowed are the edges along the attributes in
the propagation set, thus the `runtime_deps` edge is not shadowed in this
example. An aspect implementation function is then invoked on all nodes in
the shadow graph similar to how rule implementations are invoked on the nodes
of the original graph.

## Simple example

This example demonstrates how to recursively print the source files for a
rule and all of its dependencies that have a `deps` attribute. It shows
an aspect implementation, an aspect definition, and how to invoke the aspect
from the Bazel command line.

```python
def _print_aspect_impl(target, ctx):
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the files that make up the sources and
        # print their paths.
        for src in ctx.rule.attr.srcs:
            for f in src.files:
                print(f.path)
    return []

print_aspect = aspect(
    implementation = _print_aspect_impl,
    attr_aspects = ['deps'],
)
```

Let's break the example up into its parts and examine each one individually.

### Aspect definition

```python
print_aspect = aspect(
    implementation = _print_aspect_impl,
    attr_aspects = ['deps'],
)
```
Aspect definitions are similar to rule definitions, and defined using
the [`aspect`](lib/globals.html#aspect) function.

Just like a rule, an aspect has an implementation function which in this case is
``_print_aspect_impl``.

``attr_aspects`` is a list of rule attributes along which the aspect propagates.
In this case, the aspect will propagate along the ``deps`` attribute of the
rules that it is applied to.

Another common argument for `attr_aspects` is `['*']` which would propagate the
aspect to all attributes of a rule.

### Aspect implementation

```python

def _print_aspect_impl(target, ctx):
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the files that make up the sources and
        # print their paths.
        for src in ctx.rule.attr.srcs:
            for f in src.files:
                print(f.path)
    return []
```

Aspect implementation functions are similar to the rule implementation
functions. They return [providers](rules.md#providers), can generate
[actions](rules.md#actions), and take two arguments:

*  `target`: the [target](lib/Target.html) the aspect is being applied to.
*   `ctx`: [`ctx`](lib/ctx.html) object that can be used to access attributes
    and generate outputs and actions.

The implementation function can access the attributes of the target rule via
[`ctx.rule.attr`](lib/ctx.html#rule). It can examine providers that are
provided by the target to which it is applied (via the `target` argument).

Aspects are required to return a list of providers. In this example, the aspect
does not provide anything, so it returns an empty list.

### Invoking the aspect using the command line

The simplest way to apply an aspect is from the command line using the
[`--aspects`](https://docs.bazel.build/versions/master/command-line-reference.html#flag--aspects)
argument. Assuming the rule above were defined in a file named `print.bzl` this:

```bash
bazel build //MyExample:example --aspects print.bzl%print_aspect
```

would apply the `print_aspect` to the target `example` and all of the
target rules that are accessible recursively via the `deps` attribute.

The `--aspects` flag takes one argument, which is a specification of the aspect
in the format `<extension file label>%<aspect top-level name>`.

## Advanced example

The following example demonstrates using an aspect from a target rule
that counts files in targets, potentially filtering them by extension.
It shows how to use a provider to return values, how to use parameters to pass
an argument into an aspect implementation, and how to invoke an aspect from a rule.

FileCount.bzl file:

```python
FileCount = provider(
    fields = {
        'count' : 'number of files'
    }
)

def _file_count_aspect_impl(target, ctx):
    count = 0
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the sources counting files
        for src in ctx.rule.attr.srcs:
            for f in src.files:
                if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
                    count = count + 1
    # Get the counts from our dependencies.
    for dep in ctx.rule.attr.deps:
        count = count + dep[FileCount].count
    return [FileCount(count = count)]

file_count_aspect = aspect(implementation = _file_count_aspect_impl,
    attr_aspects = ['deps'],
    attrs = {
        'extension' : attr.string(values = ['*', 'h', 'cc']),
    }
)

def _file_count_rule_impl(ctx):
    for dep in ctx.attr.deps:
        print(dep[FileCount].count)

file_count_rule = rule(
    implementation = _file_count_rule_impl,
    attrs = {
        'deps' : attr.label_list(aspects = [file_count_aspect]),
        'extension' : attr.string(default = '*'),
    },
)
```

BUILD.bazel file:

```python
load('//file_count.bzl', 'file_count_rule')

cc_library(
    name = 'lib',
    srcs = [
        'lib.h',
        'lib.cc',
    ],
)

cc_binary(
    name = 'app',
    srcs = [
        'app.h',
        'app.cc',
        'main.cc',
    ],
    deps = ['lib'],
)

file_count_rule(
    name = 'file_count',
    deps = ['app'],
    extension = 'h',
)
```

### Aspect definition

```python
file_count_aspect = aspect(implementation = _file_count_aspect_impl,
    attr_aspects = ['deps'],
    attrs = {
        'extension' : attr.string(values = ['*', 'h', 'cc']),
    }
)
```

In this example, we are again propagating the aspect via the ``deps`` attribute.

``attrs`` defines a set of attributes for an aspect. Public aspect attributes
are of type ``string`` and are called parameters. Parameters must have a``values``
attribute specified on them. In this case we have a parameter called ``extension``
that is allowed to have '``*``', '``h``', or '``cc``' as a value.

Parameter values for the aspect are taken from the string attribute with the same
name of the rule requesting the aspect (see the definition of ``file_count_rule``).
Aspects with parameters cannot be used via the command line because there is no
syntax to define the parameters.

Aspects are also allowed to have private attributes of types ``label`` or
``label_list``. Private label attributes can be used to specify dependencies on
tools or libraries that are needed for actions generated by aspects. There is not
a private attribute defined in this example, but the following code snippet
demonstrates how you could pass in a tool to a aspect:

```python
...
    attrs = {
        '_protoc' : attr.label(
            default = Label('//tools:protoc'),
            executable = True,
            cfg = "host"
        )
    }
...
```

### Aspect implementation

```python
FileCount = provider(
    fields = {
        'count' : 'number of files'
    }
)

def _file_count_aspect_impl(target, ctx):
    count = 0
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the sources counting files
        for src in ctx.rule.attr.srcs:
            for f in src.files:
                if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
                    count = count + 1
    # Get the counts from our dependencies.
    for dep in ctx.rule.attr.deps:
        count = count + dep[FileCount].count
    return [FileCount(count = count)]
```

Just like a rule implementation function, an aspect implementation function
returns a struct of providers that are accessible to its dependencies.

In this example, the ``FileCount`` is defined as a provider that has one field
``count``. It is best practice to explicitly define the fields of a provider
using the ``fields`` attribute.

The set of providers for an aspect application A(X) is the union of providers
that come from the implementation of a rule for target X and from
the implementation of aspect A. It is an error if a target and an aspect that
is applied to it each provide a provider with the same name. The providers that
a rule implementation propagates are created and frozen before aspects are
applied and cannot be modified from an aspect.

The parameters and private attributes are passed in the attributes of the
``ctx``. In this example, we reference the ``extension`` parameter to decide
what files to count.

For returning providers, the values of attributes along which
the aspect is propagated (from the `attr_aspect` list) are replaced with
the results of an application of the aspect to them. For example, if target
X has Y and Z in its deps, `ctx.rule.attr.deps` for A(X) will be [A(Y), A(Z)].
In this example, ``ctx.rule.attr.deps`` are Target objects that are the
results of applying the aspect to the 'deps' of the original target to which
the aspect has been applied.

In the example, the aspect to accesses the ``FileCount`` provider from the
target's dependencies to accumulate the total transitive number of files.

### Invoking the aspect from a rule

```python
def _file_count_rule_impl(ctx):
    for dep in ctx.attr.deps:
        print(dep[FileCount].count)

file_count_rule = rule(
    implementation = _file_count_rule_impl,
    attrs = {
        'deps' : attr.label_list(aspects = [file_count_aspect]),
        'extension' : attr.string(default = '*'),
    },
)
```

The rule implementation demonstrates how to access the ``FileCount`` via
the ``ctx.attr.deps``.

The rule definition demonstrates how to define a parameter (``extension``)
and give it a default value (``*``). Note that having a default value that
was not one of '``cc``', '``h``', or '``*``' would be an error due to the
restrictions placed on the parameter in the aspect definition.

### Invoking an aspect through a target rule

```python
load('//file_count.bzl', 'file_count_rule')

cc_binary(
    name = 'app',
...
)

file_count_rule(
    name = 'file_count',
    deps = ['app'],
    extension = 'h',
)
```

This demonstrates how to pass the ``extension`` parameter into the aspect
via the rule. Since the ``extension`` parameter has a default value in the
rule implementation, ``extension`` would be considered an optional parameter.

When the ``file_count`` target is built, our aspect will be evaluated for
itself, and all of the targets accessible recursively via ``deps``.