Bazel Rules: Source

2021-12-28 • edited 2021-12-30

The critical job of a build system is to convert source files into artifacts. In this part, we will use a template file as a source code example and convert it into an executable. Conceptually, it is the same process we would use to compile a Go or Rust binary except for the complexities of dealing with platforms and languages.

As a disclaimer, please be aware that sh_binary native rule is a better solution for using shell script in production-quality builds.

Source Attribute #

We will start with adding a source attribute to our rule:

demo_binary = rule(
    implementation = _demo_binary_impl,
    executable = True,
    attrs = {
        "message": attr.string(mandatory = True),
        "out": attr.output(mandatory = True),
        "src": attr.label(
            mandatory = True,
            allow_single_file = [".tpl"],
        ),
    },
)

The attr.label type tells Bazel to treat this attribute as a target. We provide a list of allowed extensions. The allow_single_file parameter prevents users from passing a list of multiple source files.

The BUILD file will refer to a source file:

demo_binary(
    name = "source",
    src = "hello.tpl",
    out = "hello",
    message = "Hello, World!",
)

Template #

The content of the template file is almost identical to the _TEMPLATE variable we used before

#!/bin/sh
echo "{MESSAGE}"

The only difference is the placeholder. We will use Bazel’s expand_template function that allows passing any string as a substitution token; it makes sense to make it a bit clearer than just a pair of brackets.

Finally, we can replace write action in the implementation function:

def _demo_binary_impl(ctx):
    out = ctx.outputs.out
    ctx.actions.expand_template(
        output = out,
        template = ctx.file.src,
        substitutions = {
            "{MESSAGE}": ctx.attr.message,
        },
    )
    return [DefaultInfo(
        files = depset([out]),
        executable = out,
    )]

We use ctx.file to refer to the src attribute and provide a list of substitutions. Building and running the example will produce the familiar message:

$ bazel run //source
..
Target //source:source up-to-date:
  bazel-bin/source/hello
...
Hello, World!

The complete example is in the repo.

bazel

Bazel Rules: Output Bazel Rules: Multiple Source Files