Typically, when writing a Bazel rule, we produce outputs using the DefaultInfo provider. However, there are cases where we want to produce additional outputs only on demand.

Enter output groups

Simply put, output groups are a way to tell Bazel to produce different sets of outputs instead of—or in addition to—the default outputs. For example, we might want to generate debug symbols, but we don’t need them unless explicitly requested.

Smallest possible example

Here is a minimal rule that demonstrates the use of output groups:

def _impl(ctx):
    out1 = ctx.actions.declare_file("main.txt")
    out2 = ctx.actions.declare_file("debug.txt")

    ctx.actions.write(out1, "main output")
    ctx.actions.write(out2, "debug output")

    return [
        DefaultInfo(files = depset([out1])),
        OutputGroupInfo(
            debug = depset([out2]),
        ),
    ]

my_rule = rule(
    implementation = _impl,
)

Notice how easy it is to use output groups. OutputGroupInfo is just another provider—a key-value mapping where, in this case, debug is the key (the output group name), and out2 is the value wrapped in a depset.

Requesting the debug output

If we instantiate this rule in a BUILD file:

my_rule(
    name = "groups",
)

We can build it:

bazel build :groups

This produces:

INFO: Analyzed target //:groups (5 packages loaded, 7 targets configured).
INFO: Found 1 target...
Target //:groups up-to-date:
  bazel-bin/main.txt
INFO: Elapsed time: 0.119s, Critical Path: 0.00s
INFO: 2 processes: 2 internal.
INFO: Build completed successfully, 2 total actions

The important part here is bazel-bin/main.txt. This happens because we did not tell Bazel to include outputs from the debug output group.

To do that, we use the --output_groups flag and specify the group name (in this case, debug):

bazel build :groups --output_groups=debug

Output:

INFO: Analyzed target //:groups (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:groups up-to-date:
  bazel-bin/debug.txt
INFO: Elapsed time: 0.065s, Critical Path: 0.00s
INFO: 2 processes: 2 internal.
INFO: Build completed successfully, 2 total actions

Now the debug file is produced.

An important detail: debug.txt is produced instead of main.txt, not in addition to it. To request both the default outputs and an output group at the same time, use the + prefix:

bazel build :groups --output_groups=+debug

This produces both files:

INFO: Analyzed target //:groups (5 packages loaded, 7 targets configured).
INFO: Found 1 target...
Target //:groups up-to-date:
  bazel-bin/debug.txt
  bazel-bin/main.txt
INFO: Elapsed time: 0.108s, Critical Path: 0.00s
INFO: 3 processes: 3 internal.
INFO: Build completed successfully, 3 total actions

Conclusion

Output groups are simple to use, both when defining rules and when consuming them. They’re a small feature, but an extremely useful one.