//python/private:rule_builders.bzl

Builders for creating rules, aspects et al.

When defining rules, Bazel only allows creating immutable objects that can’t be introspected. This makes it difficult to perform arbitrary customizations of how a rule is defined, which makes extending a rule implementation prone to copy/paste issues and version skew.

These builders are, essentially, mutable and inspectable wrappers for those Bazel objects. This allows defining a rule where the values are mutable and callers can customize them to derive their own variant of the rule while still inheriting everything else about the rule.

To that end, the builders are not strict in how they handle values. They generally assume that the values provided are valid and provide ways to override their logic and force particular values to be used when they are eventually converted to the args for calling e.g. rule().

Important

When using builders, most lists, dicts, et al passed into them must be locally created values, otherwise they won’t be mutable. This is due to Bazel’s implicit immutability rules: after evaluating a .bzl file, its global variables are frozen.

Tip

To aid defining reusable pieces, many APIs accept no-arg callable functions that create a builder. For example, common attributes can be stored in a dict[str, lambda], e.g. ATTRS = {"srcs": lambda: LabelList(...)}.

Example usage:


load(":rule_builders.bzl", "ruleb")
load(":attr_builders.bzl", "attrb")

# File: foo_binary.bzl
_COMMON_ATTRS = {
    "srcs": lambda: attrb.LabelList(...),
}

def create_foo_binary_builder():
    foo = ruleb.Rule(
        executable = True,
    )
    foo.implementation.set(_foo_binary_impl)
    foo.attrs.update(COMMON_ATTRS)
    return foo

def create_foo_test_builder():
    foo = create_foo_binary_build()

    binary_impl = foo.implementation.get()
    def foo_test_impl(ctx):
      binary_impl(ctx)
      ...

    foo.implementation.set(foo_test_impl)
    foo.executable.set(False)
    foo.test.test(True)
    foo.attrs.update(
        _coverage = attrb.Label(default="//:coverage")
    )
    return foo

foo_binary = create_foo_binary_builder().build()
foo_test = create_foo_test_builder().build()

# File: custom_foo_binary.bzl
load(":foo_binary.bzl", "create_foo_binary_builder")

def create_custom_foo_binary():
    r = create_foo_binary_builder()
    r.attrs["srcs"].default.append("whatever.txt")
    return r.build()

custom_foo_binary = create_custom_foo_binary()

Added in version 1.3.0.

typedef AttributeBuilder

An abstract base typedef for builder for a Bazel Attribute

Instances of this are a builder for a particular Attribute type, e.g. attr.label, attr.string, etc.

typedef AttrsDict

Builder for the dictionary of rule attributes.

AttrsDict.map: dict[str, AttributeBuilder]

The underlying dict of attributes. Directly accessible so that regular dict operations (e.g. x in y) can be performed, if necessary.

AttrsDict.get(key, default=None)

Get an entry from the dict. Convenience wrapper for .map.get(...)

AttrsDict.items() list[tuple[str, object]]

Returns a list of key-value tuples. Convenience wrapper for .map.items()

AttrsDict.pop(key, default) object

Removes a key from the attr dict

AttrsDict.build()

Build an attribute dict for passing to rule().

Returns:

dict[str, Attribute] where the values are attr.XXX objects

AttrsDict.new(initial)

Creates a builder for the rule.attrs dict.

Args:
Returns:

AttrsDict

AttrsDict.update(other)

Merge other into this object.

Args:
  • other(dict[str, callable | AttributeBuilder])

    the values to merge into this object. If the value a function, it is called with no args and expected to return an attribute builder. This allows defining dicts of common attributes (where the values are functions that create a builder) and merge them into the rule.

typedef ExecGroup

Builder for exec_group

ExecGroup.toolchains() list[ToolchainType]
ExecGroup.exec_compatible_with() list[str | Label]
ExecGroup.kwargs: dict[str, Any]

Additional kwargs to use when building. This is to allow manipulations that aren’t directly supported by the builder’s API. The state of this dict may or may not reflect prior API calls, and subsequent API calls may modify this dict. The general contract is that modifications to this will be respected when build() is called, assuming there were no API calls in between.

ExecGroup.build()
ExecGroup.new(**kwargs)

Creates a builder for exec_group.

Args:
Returns:

ExecGroup

typedef Rule

A builder to accumulate state for constructing a rule object.

Rule.attrs: AttrsDict
Rule.cfg: RuleCfg
Rule.doc() str
Rule.exec_groups() dict[str, ExecGroup]
Rule.executable() bool
Rule.kwargs: dict[str, Any]

Additional kwargs to use when building. This is to allow manipulations that aren’t directly supported by the builder’s API. The state of this dict may or may not reflect prior API calls, and subsequent API calls may modify this dict. The general contract is that modifications to this will be respected when build() is called, assuming there were no API calls in between.

Rule.fragments() list[str]
Rule.implementation() callable | None
Rule.provides() list[provider | list[provider]]
Rule.set_doc(v)
Rule.set_executable(v)
Rule.set_implementation(v)
Rule.set_test(v)
Rule.test() bool
Rule.toolchains() list[ToolchainType]
Rule.build(debug='')

Builds a rule object

Args:
  • debug(str) (default “”)

    If set, prints the args used to create the rule.

Returns:

rule

Rule.new(**kwargs)

Builder for creating rules.

Args:
  • kwargs – The same as the rule() function, but using builders or dicts to specify sub-objects instead of the immutable Bazel objects.

Rule.to_kwargs()

Builds the arguments for calling rule().

This is added as an escape hatch to construct the final values rule() kwarg values in case callers want to manually change them.

Returns:

dict

ruleb.ExecGroup(**kwargs)

Creates a builder for exec_group.

Args:
Returns:

ExecGroup

ruleb.Rule(**kwargs)

Builder for creating rules.

Args:
  • kwargs – The same as the rule() function, but using builders or dicts to specify sub-objects instead of the immutable Bazel objects.

ruleb.ToolchainType(name=None, **kwargs)

Creates a builder for config_common.toolchain_type.

Args:
Returns:

ToolchainType

typedef RuleCfg

Wrapper for rule.cfg arg.

RuleCfg.implementation() str | callable | None | config.target | config.none
RuleCfg.inputs() list[Label]

See also

The add_inputs() and update_inputs methods for adding unique values.

RuleCfg.outputs() list[Label]

See also

The add_outputs() and update_outputs methods for adding unique values.

RuleCfg.set_implementation(v)

The string values “target” and “none” are supported.

RuleCfg.add_inputs(*inputs)

Adds an input to the list of inputs, if not present already.

See also

The update_inputs() method for adding a collection of values.

Args:
  • inputs(Label)

    the inputs to add. Note that a Label, not str, should be passed to ensure different apparent labels can be properly de-duplicated.

RuleCfg.add_outputs(*outputs)

Adds an output to the list of outputs, if not present already.

See also

The update_outputs() method for adding a collection of values.

Args:
  • outputs(Label)

    the outputs to add. Note that a Label, not str, should be passed to ensure different apparent labels can be properly de-duplicated.

RuleCfg.build()

Builds the rule cfg into the value rule.cfg arg value.

Returns:

transition the transition object to apply to the rule.

RuleCfg.new(rule_cfg_arg)

Creates a builder for the rule.cfg arg.

Args:
Returns:

RuleCfg

RuleCfg.update_inputs(*others)

Add a collection of values to inputs.

Args:
  • others(list[Label])

    collection of labels to add to inputs. Only values not already present are added. Note that a Label, not str, should be passed to ensure different apparent labels can be properly de-duplicated.

RuleCfg.update_outputs(*others)

Add a collection of values to outputs.

Args:
  • others(list[Label])

    collection of labels to add to outputs. Only values not already present are added. Note that a Label, not str, should be passed to ensure different apparent labels can be properly de-duplicated.

typedef ToolchainType

Builder for config_common.toolchain_type

ToolchainType.kwargs: dict[str, Any]

Additional kwargs to use when building. This is to allow manipulations that aren’t directly supported by the builder’s API. The state of this dict may or may not reflect prior API calls, and subsequent API calls may modify this dict. The general contract is that modifications to this will be respected when build() is called, assuming there were no API calls in between.

ToolchainType.mandatory() bool
ToolchainType.name() str | Label | None
ToolchainType.set_name(v)
ToolchainType.set_mandatory(v)
ToolchainType.build()

Builds a config_common.toolchain_type

Returns:

toolchain_type

ToolchainType.new(name=None, **kwargs)

Creates a builder for config_common.toolchain_type.

Args:
Returns:

ToolchainType