Communicating between Bazel rules: how to use Skylark providers

Rules in Bazel often need information from their dependencies. My previous post touched on a special case of this: figuring out what a dependency’s runfiles are. However, Skylark is actually capable of passing arbitrary information between rules using a system known as providers.

Suppose we have a rule, analyze_flavors, that figures out what all of the flavors are in a dish. Our build file looks like:

load(":food.bzl", "analyze_flavors")
 
analyze_flavors(
    name = "burger",
    ingredients = [
        ":beef",
        ":ketchup",
    ],
)
 
analyze_flavors(
    name = "beef",
    tastes_like = "umame",
)
 
analyze_flavors(
    name = "ketchup",
    tastes_like = "sweet",
)

We want to build up a flavor profile for :burger, based on its ingredients.

To do this, food.bzl looks like:

def _flavor_impl(ctx):
  # Build up a flavor profile from this rule & its ingredients.
  flavor_profile = []
  for ingredient in ctx.attr.ingredients:
    if ingredient.flavor != None:
      flavor_profile += ingredient.flavor
 
  if ctx.attr.tastes_like != "":
    flavor_profile += [ctx.attr.tastes_like]
 
  # Write the list of flavors to a file.
  ctx.file_action(
    output = ctx.outputs.out,
    content = "%s tastes like %s\n" % (
        ctx.label.name, " and ".join(flavor_profile))
  )
 
  # Return the list of flavors so it can be used by rules that depend on this.  return struct(flavor = flavor_profile) 
analyze_flavors = rule(
    attrs = {
        "ingredients": attr.label_list(),
        "tastes_like": attr.string(),
    },
    outputs = {"out": "flavors-of-%{name}"},
    implementation = _flavor_impl,
)

The highlighted lines are where the rule returns a provider, flavor, to be consumed by its reverse dependencies (the targets depending on it).

Our BUILD file gives us the following build graph:

graph

:burger depends on :beef and :ketchup. :beef and :ketchup each provide :burger with a flavor. Thus, if we build :burger and check its output file, we get:

$ bazel build :burger
 
INFO: Found 1 target...
Target //:burger up-to-date:
  bazel-bin/flavors-of-burger
 
INFO: Elapsed time: 0.270s, Critical Path: 0.00s
INFO: Build completed successfully, 2 total actions
$ cat bazel-bin/flavors-of-burger
burger tastes like umame and sweet

This can be used to communicate rich information from rule-to-rule in Skylark. See the Skylark cookbook for another example of providers.

kristina chodorow's blog