BUILD and .bzl files are written in a dialect of
Starlark properly known as the “Build Language”, though it is often simply
referred to as “Starlark”, especially when emphasizing that a feature is
expressed in the Build Language as opposed to being a built-in or “native” part
of Bazel. Bazel augments the core language with numerous build-related functions
such as glob, genrule, java_binary, and so on.
See the
Bazel and Starlark documentation for
more details, and the
Rules SIG template as a
starting point for new rulesets.
The empty rule
To create your first rule, create the filefoo.bzl:
rule function, you
must define a callback function. The logic will go there, but you
can leave the function empty for now. The ctx argument
provides information about the target.
You can load the rule and use it from a BUILD file.
Create a BUILD file in the same directory:
visibility, testonly, and
tags.
Evaluation model
Before going further, it’s important to understand how the code is evaluated. Updatefoo.bzl with some print statements:
ctx.label
corresponds to the label of the target being analyzed. The ctx object has
many useful fields and methods; you can find an exhaustive list in the
API reference.
Query the code:
- “bzl file evaluation” is printed first. Before evaluating the
BUILDfile, Bazel evaluates all the files it loads. If multipleBUILDfiles are loading foo.bzl, you would see only one occurrence of “bzl file evaluation” because Bazel caches the result of the evaluation. - The callback function
_foo_binary_implis not called. Bazel query loadsBUILDfiles, but doesn’t analyze targets.
cquery (“configured
query”) or the build command:
_foo_binary_impl is now called twice - once for each target.
Notice that neither “bzl file evaluation” nor “BUILD file” are printed again,
because the evaluation of foo.bzl is cached after the call to bazel query.
Bazel only emits print statements when they are actually executed.
Creating a file
To make your rule more useful, update it to generate a file. First, declare the file and give it a name. In this example, create a file with the same name as the target:bazel build :all now, you will get an error:
ctx.actions.write,
to create a file with the given content.
ctx.actions.write function registered an action, which taught Bazel
how to generate the file. But Bazel won’t create the file until it is
actually requested. So the last thing to do is tell Bazel that the file
is an output of the rule, and not a temporary file used within the rule
implementation.
DefaultInfo and depset functions later. For now,
assume that the last line is the way to choose the outputs of a rule.
Now, run Bazel:
Attributes
To make the rule more useful, add new attributes using theattr module and update the rule definition.
Add a string attribute called username:
BUILD file:
ctx.attr.username. For
example:
attr.string.
You may also use other types of attributes, such as boolean
or list of integers.
Dependencies
Dependency attributes, such asattr.label
and attr.label_list,
declare a dependency from the target that owns the attribute to the target whose
label appears in the attribute’s value. This kind of attribute forms the basis
of the target graph.
In the BUILD file, the target label appears as a string object, such as
//pkg:name. In the implementation function, the target will be accessible as a
Target object. For example, view the files returned
by the target using Target.files.
Multiple files
By default, only targets created by rules may appear as dependencies (such as afoo_library() target). If you want the attribute to accept targets that are
input files (such as source files in the repository), you can do it with
allow_files and specify the list of accepted file extensions (or True to
allow any file extension):
ctx.files.<attribute name>. For
example, the list of files in the srcs attribute can be accessed through
Single file
If you need only one file, useallow_single_file:
ctx.file.<attribute name>:
Create a file with a template
You can create a rule that generates a .cc file based on a template. Also, you can usectx.actions.write to output a string constructed in the rule
implementation function, but this has two problems. First, as the template gets
bigger, it becomes more memory efficient to put it in a separate file and avoid
constructing large strings during the analysis phase. Second, using a separate
file is more convenient for the user. Instead, use
ctx.actions.expand_template,
which performs substitutions on a template file.
Create a template attribute to declare a dependency on the template
file:
BUILD file. The template is now an implicit dependency: Every hello_world
target has a dependency on this file. Don’t forget to make this file visible
to other packages by updating the BUILD file and using
exports_files:
Going further
- Take a look at the reference documentation for rules.
- Get familiar with depsets.
- Check out the examples repository which includes additional examples of rules.