Bazel Rules: Multiple Outputs

2022-01-06

A typical use case for Bazel macros that we discussed previously is producing several dependent targets. For example, we might want to generate a checksum file for our binary as a part of the build. These artifacts are tightly coupled yet different, and a macro provides a convenient way to represent that.

Implementation #

Our BUILD file will load and call the macro as usual:

load(":rules.bzl", "demo_outputs")

demo_outputs(
    name = "multiple_outputs",
    srcs = [
        "english.sh",
        "french.sh",
    ],
)

The macro will now instantiate two rules, one to build the script and another to build the SHA256 checksum file. The checksum target is the default as we expect it to be required in most cases; however, this is an assumption and we could have easily made it an additional optional target instead.

def demo_outputs(name, srcs, out = None, **kwargs):
    out = "{}.sh".format(name) if out == None else out
    _demo_binary(
        name = "{}.script".format(name),
        srcs = srcs,
        out = out,
        **kwargs
    )
    _demo_sha256(
        name = name,
        out = "{}.sha256".format(out),
        src = out,
        **kwargs
    )

_demo_sha256 is a simple regular rule:

_demo_sha256 = rule(
    implementation = _demo_sha256_impl,
    attrs = {
        "out": attr.output(mandatory = True),
        "src": attr.label(
            mandatory = True,
            allow_single_file = True,
        ),
    },
)

Its implementation function is more interesting because it produces a non-executable predeclared output so we don’t have to use ctx.actions.declare_file or returning a provider explicitly:

_demo_sha256 = rule(
    implementation = _demo_sha256_impl,
    attrs = {
        "out": attr.output(mandatory = True),
        "src": attr.label(
            mandatory = True,
            allow_single_file = True,
        ),
    },
)

Building the example is straightforward:

bazel build //multiple_outputs
...
Target //multiple_outputs:multiple_outputs up-to-date:
  bazel-bin/multiple_outputs/multiple_outputs.sh.sha256
...

Running the script demonstrates the custom verbs pattern where the target label name is in the object.verb format:

bazel run //multiple_outputs:multiple_outputs.script
...
Target //multiple_outputs:multiple_outputs.script up-to-date:
  bazel-bin/multiple_outputs/multiple_outputs.sh
...
Hello, World!
Bonjour monde!

If necessary, we could extend the list of verbs to implement any number of actions that we could use with bazel run. K8s rules are a well-known example of this approach.

The complete code for this example is in the repo.

bazel

Bazel Rules: Macros How to Merge Git Repositories