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:
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:
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.
Bazel analyzes this dependency graph by calling implementations of rules (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. 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).
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.
Aspect definitions are similiar to rule definitions. Let's take a look at the example:
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.
Aspect implementation functions are similiar to the rule implementation functions. They return providers, can generate actions and take two arguments:
target
: the target the aspect is being applied to.ctx
: ctx
object that can be used to access attributes and generate outputs and actions.Example:
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.attr.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.attr.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.attr
. 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.
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 the _metal_proto_aspect_impl
function above, ctx.rule.attr.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.Aspect propagation can be initiated either from a rule or from the command line.
Rules can specify that they want to apply aspects to their dependencies. The aspects to be applied to a particular attribute can be specified using the aspects
parameter to attr.label
or attr.label_list
function:
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 (similar to what happens during aspect propagation). Thus implementation of metal_proto_library
will have access to metal_proto
providers on the target objects representing its proto_deps
attribute values.
Aspects can also be applied on the command line, using the --aspects
flag:
bazel build //java/com/company/example:main \ --aspects path/to/extension.bzl%metal_proto_aspect
--aspects
flag takes one argument, which is a specification of the aspect in the format <extension file path>%<aspect top-level name>
.