diff options
Diffstat (limited to 'site/docs/skylark/aspects.md')
-rw-r--r-- | site/docs/skylark/aspects.md | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/site/docs/skylark/aspects.md b/site/docs/skylark/aspects.md new file mode 100644 index 0000000000..36d9d7b660 --- /dev/null +++ b/site/docs/skylark/aspects.md @@ -0,0 +1,187 @@ +--- +layout: documentation +title: Skylark Aspects +--- +# Aspects + +**Status: Experimental**. We may make breaking changes to the API, but we will + help you update your code. + +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 Fig 1. + +![Build Graph](build-graph.svg) + +Bazel analyzes this dependency graph by calling implementations of +[rules](rules.md) (in this case "java_library" starting from leaves of +the dependency graph). These implementations generate actions that build +artifacts (such as Jar files), and provide information (such as locations +and names of those artifacts) to their dependencies in providers that +they return. Their dependencies can access those providers through the +[Target object](lib/Target.html). In other words, every target +defined in the BUILD file generates a node in the dependency graph, and +the appropriate rule implementation function is called for every node. + +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-aspect.svg) + +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. + +## Defining aspects + +Aspect defintions are similiar to rule definitions. Let's take a look at +the example: + +```python +metal_proto_aspect = aspect(implementation = _metal_proto_aspect_impl, + attr_aspects = ["deps"], + attrs = { + "_protoc" : attr.label( + default=Label("//tools:metal_protoc"), + executable = True + ) + } +) +``` + +Just like a rule, an aspect has an implementation function. ``attr_aspects`` +specify the aspect's propagation set: a list of attributes of rules along which +the aspect propagates. + +``attrs`` defines a set of attributes for aspects. Aspects are only allowed +to have private attributes of types ``label`` or ``label_list``. Attributes +can be used to specify dependencies on tools or libraries that are needed +for actions generated by aspects. + +### Implementation functions + +Aspect implementation functions are similiar to the rule implementation +functions. They return [providers](rule.md#providers), can generate +[actions](rule.md#actions) and take two arguments: +* `target`: the target the aspect is being applied to. +* [`ctx`](lib/ctx.html): `ctx` object that can be used to access attributes and + generate outputs and actions. + +Example: +```python +def _metal_proto_aspect_impl(target, ctx): + # For every `src` in proto_library, generate an output file + proto_sources = [f for src in ctx.rule.attrs.src + for f in src.files] + outputs = [ctx.new_file(f.short_path + ".metal") + for f in proto_sources] + ctx.action( + executable = ctx.executable._protoc, + argument = ... + inputs = proto_sources + outputs = outputs) + transitive_outputs = set(outputs) + for dep in ctx.attrs.deps: + transitive_outputs = transitive_outputs | dep.metal_proto.transitive_outputs + return struct( + metal_proto = struct(direct_outputs = outputs, + transitive_outputs = transitive_outputs)) +``` + +The implementation function can access the attributes of the target rule via +[`ctx.rule.attrs`](lib/ctx.html#rule). It can examine providers that are +provided by the target to which it is applied (via the `target` argument). + +Just like a rule implementation function, an aspect implementation function +returns a struct of providers that are accessible to its dependencies. +* 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. +* For the aspect implementation, 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.attrs.deps` for A(X) will be [A(Y), A(Z)]. + In the `_metal_proto_aspect_impl` function above, ctx.rule.attrs.deps will be + Target objects that are the results of applying the aspect to the 'deps' + of the original target to which the aspect has been applied. + That allows the aspect to examine `metal_proto` provider on them. + + +## Applying aspects + +Aspect propagation can be initiated either from a rule or from the command line. + +### Applying aspects to rule attributes + +Rules can specify that they want to apply aspects to their depenedencies. +The aspects to be applied to a particular attribute can be specified +using the `aspects` parameter to `attr.label` or `attr.label_list` function: + +```python +metal_proto_library = rule(implementation = _impl, + attrs = { + 'proto_deps' : attr.label_list(aspects = [metal_proto_aspect]), + }, +) +``` + +If a rule specifies an aspect on its attributes, the values of that attribute +will be replaced by the result of aspect application to them (similiar to +what happens during aspect propagation). Thus implementaion of +`metal_proto_library` will have access to `metal_proto` providers +on the target objects representing its `proto_deps` attribute values. + +### Applying aspects from command line. + +Aspects can also be applied on the command line, using the `--aspect` flag: + + +``` +bazel build //java/com/company/example:main \ + --aspect path/to/extension.bzl%metal_proto_aspect +``` + +`--aspect` flag takes one argument, which is a specification of the aspect in +the format `<extension file path>%<aspect top-level name>`. + + |