| --- | |
| layout: documentation | |
| title: Rules Tutorial | |
| category: extending | |
| --- | |
| # Rules Tutorial | |
| <!-- [TOC] --> | |
| [Starlark](https://github.com/bazelbuild/starlark) is a Python-like | |
| configuration language originally developed for use in Bazel and since adopted | |
| by other tools. Bazel's 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](../getting-started.html) and [Starlark](concepts.html) documentation for | |
| more details. | |
| ## The empty rule | |
| To create your first rule, create the file `foo.bzl`: | |
| ```python | |
| def _foo_binary_impl(ctx): | |
| pass | |
| foo_binary = rule( | |
| implementation = _foo_binary_impl, | |
| ) | |
| ``` | |
| When you call the [`rule`](lib/globals.html#rule) function, you | |
| must define a callback function. The logic will go there, but you | |
| can leave the function empty for now. The [`ctx`](lib/ctx.html) 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: | |
| ```python | |
| load(":foo.bzl", "foo_binary") | |
| foo_binary(name = "bin") | |
| ``` | |
| Now, the target can be built: | |
| ``` | |
| $ bazel build bin | |
| INFO: Analyzed target //:bin (2 packages loaded, 17 targets configured). | |
| INFO: Found 1 target... | |
| Target //:bin up-to-date (nothing to build) | |
| ``` | |
| Even though the rule does nothing, it already behaves like other rules: it has a | |
| mandatory name, it supports common attributes like `visibility`, `testonly`, and | |
| `tags`. | |
| ## Evaluation model | |
| Before going further, it's important to understand how the code is evaluated. | |
| Update `foo.bzl` with some print statements: | |
| ```python | |
| def _foo_binary_impl(ctx): | |
| print("analyzing", ctx.label) | |
| foo_binary = rule( | |
| implementation = _foo_binary_impl, | |
| ) | |
| print("bzl file evaluation") | |
| ``` | |
| and BUILD: | |
| ```python | |
| load(":foo.bzl", "foo_binary") | |
| print("BUILD file") | |
| foo_binary(name = "bin1") | |
| foo_binary(name = "bin2") | |
| ``` | |
| [`ctx.label`](lib/ctx.html#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](lib/ctx.html). | |
| Query the code: | |
| ``` | |
| $ bazel query :all | |
| DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation | |
| DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file | |
| //:bin2 | |
| //:bin1 | |
| ``` | |
| Make a few observations: | |
| * "bzl file evaluation" is printed first. Before evaluating the BUILD file, | |
| Bazel evaluates all the files it loads. If multiple BUILD files 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_impl` is not called. Bazel query loads | |
| BUILD files, but doesn't analyze targets. | |
| To analyze the targets, use the [`cquery`](../cquery.html) ("configured | |
| query") or the `build` command: | |
| ``` | |
| $ bazel build :all | |
| DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation | |
| DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file | |
| DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin1 | |
| DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin2 | |
| INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured). | |
| INFO: Found 2 targets... | |
| ``` | |
| As you can see, `_foo_binary_impl` is now called twice - once for each target. | |
| Some readers will notice that "bzl file evaluation" is printed again, although | |
| the evaluation of foo.bzl is cached after the call to `bazel query`. Bazel | |
| doesn't reevaluate the code, it only replays the print events. Regardless of | |
| the cache state, you get the same output. | |
| ## 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: | |
| ```python | |
| ctx.actions.declare_file(ctx.label.name) | |
| ``` | |
| If you run `bazel build :all` now, you will get an error: | |
| ``` | |
| The following files have no generating action: | |
| bin2 | |
| ``` | |
| Whenever you declare a file, you have to tell Bazel how to generate it by | |
| creating an action. Use [`ctx.actions.write`](lib/actions.html#write), | |
| to create a file with the given content. | |
| ```python | |
| def _foo_binary_impl(ctx): | |
| out = ctx.actions.declare_file(ctx.label.name) | |
| ctx.actions.write( | |
| output = out, | |
| content = "Hello\n", | |
| ) | |
| ``` | |
| The code is valid, but it won't do anything: | |
| ``` | |
| $ bazel build bin1 | |
| Target //:bin1 up-to-date (nothing to build) | |
| ``` | |
| The `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. | |
| ```python | |
| def _foo_binary_impl(ctx): | |
| out = ctx.actions.declare_file(ctx.label.name) | |
| ctx.actions.write( | |
| output = out, | |
| content = "Hello!\n", | |
| ) | |
| return [DefaultInfo(files = depset([out]))] | |
| ``` | |
| Look at the `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: | |
| ``` | |
| $ bazel build bin1 | |
| INFO: Found 1 target... | |
| Target //:bin1 up-to-date: | |
| bazel-bin/bin1 | |
| $ cat bazel-bin/bin1 | |
| Hello! | |
| ``` | |
| You have successfully generated a file! | |
| ## Attributes | |
| To make the rule more useful, add new attributes using | |
| [the `attr` module](lib/attr.html) and update the rule definition. | |
| Add a string attribute called `username`: | |
| ```python | |
| foo_binary = rule( | |
| implementation = _foo_binary_impl, | |
| attrs = { | |
| "username": attr.string(), | |
| }, | |
| ) | |
| ``` | |
| Next, set it in the BUILD file: | |
| ```python | |
| foo_binary( | |
| name = "bin", | |
| username = "Alice", | |
| ) | |
| ``` | |
| To access the value in the callback function, use `ctx.attr.username`. For | |
| example: | |
| ```python | |
| def _foo_binary_impl(ctx): | |
| out = ctx.actions.declare_file(ctx.label.name) | |
| ctx.actions.write( | |
| output = out, | |
| content = "Hello {}!\n".format(ctx.attr.username), | |
| ) | |
| return [DefaultInfo(files = depset([out]))] | |
| ``` | |
| Note that you can make the attribute mandatory or set a default value. Look at | |
| the documentation of [`attr.string`](lib/attr.html#string). | |
| You may also use other types of attributes, such as [boolean](lib/attr.html#bool) | |
| or [list of integers](lib/attr.html#int_list). | |
| ## Dependencies | |
| Dependency attributes, such as [`attr.label`](lib/attr.html#label) | |
| and [`attr.label_list`](lib/attr.html#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`](lib/Target.html) object. For example, view the files returned | |
| by the target using [`Target.files`](lib/Target.html#modules.Target.files). | |
| ### Multiple files | |
| By default, only targets created by rules may appear as dependencies (e.g. a | |
| `foo_library()` target). If you want the attribute to accept targets that are | |
| input files (e.g. 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): | |
| ```python | |
| "srcs": attr.label_list(allow_files = [".java"]), | |
| ``` | |
| The list of files can be accessed with `ctx.files.<attribute name>`. For | |
| example, the list of files in the `srcs` attribute can be accessed through | |
| ```python | |
| ctx.files.srcs | |
| ``` | |
| ### Single file | |
| If you need only one file, use `allow_single_file`: | |
| ```python | |
| "src": attr.label(allow_single_file = [".java"]) | |
| ``` | |
| This file is then accessible under `ctx.file.<attribute name>`: | |
| ```python | |
| ctx.file.src | |
| ``` | |
| ## Create a file with a template | |
| You can create a rule that generates a .cc file based on a template. Also, you | |
| can use `ctx.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`](lib/actions.html#expand_template), | |
| which performs substitutions on a template file. | |
| Create a `template` attribute to declare a dependency on the template | |
| file: | |
| ```python | |
| def _hello_world_impl(ctx): | |
| out = ctx.actions.declare_file(ctx.label.name + ".cc") | |
| ctx.actions.expand_template( | |
| output = out, | |
| template = ctx.file.template, | |
| substitutions = {"{NAME}": ctx.attr.username}, | |
| ) | |
| return [DefaultInfo(files = depset([out]))] | |
| hello_world = rule( | |
| implementation = _hello_world_impl, | |
| attrs = { | |
| "username": attr.string(default = "unknown person"), | |
| "template": attr.label( | |
| allow_single_file = [".cc.tpl"], | |
| mandatory = True, | |
| ), | |
| }, | |
| ) | |
| ``` | |
| Users can use the rule like this: | |
| ```python | |
| hello_world( | |
| name = "hello", | |
| username = "Alice", | |
| template = "file.cc.tpl", | |
| ) | |
| cc_binary( | |
| name = "hello_bin", | |
| srcs = [":hello"], | |
| ) | |
| ``` | |
| If you don't want to expose the template to the end-user and always use the | |
| same one, you can set a default value and make the attribute private: | |
| ```python | |
| "_template": attr.label( | |
| allow_single_file = True, | |
| default = "file.cc.tpl", | |
| ), | |
| ``` | |
| Attributes that start with an underscore are private and cannot be set in a | |
| 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`](../be/functions.html#exports_files): | |
| ```python | |
| exports_files(["file.cc.tpl"]) | |
| ``` | |
| ## Going further | |
| * Take a look at the [reference documentation for rules](rules.html#contents). | |
| * Get familiar with [depsets](depsets.html). | |
| * Check out the [examples repository](https://github.com/bazelbuild/examples/tree/HEAD/rules) | |
| which includes additional examples of rules. |