:::{important} This is public, but volatile, functionality.
Extending and customizing the rules is supported functionality, but with weaker backwards compatibility guarantees, and is not fully subject to the normal backwards compatibility procedures and policies. It's simply not feasible to support every possible customization with strong backwards compatibility guarantees. :::
Because of the rich ecosystem of tools and variety of use cases, APIs are provided to make it easy to create custom rules using the existing rules as a basis. This allows implementing behaviors that aren't possible using wrapper macros around the core rules, and can make certain types of changes much easier and transparent to implement.
:::{note} It is not required to extend a core rule. The minimum requirement for a custom rule is to return the appropriate provider (e.g. {bzl:obj}PyInfo
etc). Extending the core rules is most useful when you want all or most of the behavior of a core rule. :::
Follow or comment on https://github.com/bazel-contrib/rules_python/issues/1647 for the development of APIs to support custom derived rules.
Custom rules can be created using the core rules as a basis by using their rule builder APIs.
//python/apis:executables.bzl
: builders for executables.//python/apis:libraries.bzl
: builders for libraries.These builders create {bzl:obj}ruleb.Rule
objects, which are thin wrappers around the keyword arguments eventually passed to the rule()
function. These builder APIs give access to the entire rule definition and allow arbitrary modifications.
This level of control is powerful but also volatile. A rule definition contains many details that must change as the implementation changes. What is more or less likely to change isn't known in advance, but some general rules of thumb are:
In this example, we derive a custom rule from py_library
that verifies source code contains the word “snakes”. It does this by:
_validation
output group (a special output group for validation behaviors).To users, they can use has_snakes_library
the same as py_library
. The same is true for other targets that might consume the rule.
load("@rules_python//python/api:libraries.bzl", "libraries") load("@rules_python//python/api:attr_builders.bzl", "attrb") def _has_snakes_impl(ctx, base): providers = base(ctx) out = ctx.actions.declare_file(ctx.label.name + "_snakes.check") ctx.actions.run( inputs = ctx.files.srcs, outputs = [out], executable = ctx.attr._checker[DefaultInfo].files_to_run, args = [out.path] + [f.path for f in ctx.files.srcs], ) prior_ogi = None for i, p in enumerate(providers): if type(p) == "OutputGroupInfo": prior_ogi = (i, p) break if prior_ogi: groups = {k: getattr(prior_ogi[1], k) for k in dir(prior_ogi)} if "_validation" in groups: groups["_validation"] = depset([out], transitive=groups["_validation"]) else: groups["_validation"] = depset([out]) providers[prior_ogi[0]] = OutputGroupInfo(**groups) else: providers.append(OutputGroupInfo(_validation=depset([out]))) return providers def create_has_snakes_rule(): r = libraries.py_library_builder() base_impl = r.implementation() r.set_implementation(lambda ctx: _has_snakes_impl(ctx, base_impl)) r.attrs["_checker"] = attrb.Label( default="//:checker", executable = True, ) return r.build() has_snakes_library = create_has_snakes_rule()
In this example, we derive a custom rule from py_binary
to force building for a particular platform. We do this by:
load("@rules_python//python/api:executables.bzl", "executables") def _force_linux_impl(settings, attr, base_impl): settings = base_impl(settings, attr) settings["//command_line_option:platforms"] = ["//my/platforms:linux"] return settings def create_rule(): r = executables.py_binary_rule_builder() base_impl = r.cfg.implementation() r.cfg.set_implementation( lambda settings, attr: _force_linux_impl(settings, attr, base_impl) ) r.cfg.add_output("//command_line_option:platforms") return r.build() py_linux_binary = create_rule()
Users can then use py_linux_binary
the same as a regular py_binary
. It will act as if --platforms=//my/platforms:linux
was specified when building it.