Extending the rules

Important

This is public, but volatile, functionality.

Extending and customizing the rules is supported functionality, but with weakerbackwards compatibility guarantees, and is not fully subject to the normalbackwards compatibility procedures and policies. It’s simply not feasible tosupport every possible customization with strong backwards compatibilityguarantees.

Because of the rich ecosystem of tools and variety of use cases, APIs areprovided to make it easy to create custom rules using the existing rules as abasis. This allows implementing behaviors that aren’t possible usingwrapper macros around the core rules, and can make certain types of changesmuch easier and transparent to implement.

Note

It is not required to extend a core rule. The minimum requirement for a customrule is to return the appropriate provider (e.g.PyInfo etc).Extending the core rules is most useful when you want all or most of thebehavior of a core rule.

Follow or comment on https://github.com/bazel-contrib/rules_python/issues/1647for the development of APIs to support custom derived rules.

Creating custom rules

Custom rules can be created using the core rules as a basis by using their rulebuilder APIs.

These builders createruleb.Rule objects, which are thinwrappers around the keyword arguments eventually passed to therule()function. These builder APIs give access to theentire rule definition andallow arbitrary modifications.

This level of control is powerful but also volatile. A rule definitioncontains many details thatmust change as the implementation changes. Whatis more or less likely to change isn’t known in advance, but some generalrules of thumb are:

  • Additive behavior to public attributes will be less prone to breaking.

  • Internal attributes that directly support a public attribute are likelyreliable.

  • Internal attributes that support an action are more likely to change.

  • Rule toolchains are moderately stable (toolchains are mostly internal tohow a rule works, but custom toolchains are supported).

Example: validating a source file

In this example, we derive a custom rule frompy_library that verifies sourcecode contains the word “snakes”. It does this by:

  • Adding an implicit dependency on a checker program

  • Calling the base implementation function

  • Running the checker on the srcs files

  • Adding the result to the_validation output group (a special outputgroup for validation behaviors).

To users, they can usehas_snakes_library the same aspy_library. The sameis 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.pathforfinctx.files.srcs],)prior_ogi=Nonefori,pinenumerate(providers):iftype(p)=="OutputGroupInfo":prior_ogi=(i,p)breakifprior_ogi:groups={k:getattr(prior_ogi[1],k)forkindir(prior_ogi)}if"_validation"ingroups: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])))returnprovidersdefcreate_has_snakes_rule():r=libraries.py_library_builder()base_impl=r.implementation()r.set_implementation(lambdactx:_has_snakes_impl(ctx,base_impl))r.attrs["_checker"]=attrb.Label(default="//:checker",executable=True,)returnr.build()has_snakes_library=create_has_snakes_rule()

Example: adding transitions

In this example, we derive a custom rule frompy_binary to force building for a particularplatform. We do this by:

  • Adding an additional output to the rule’s cfg

  • Calling the base transition function

  • Returning the new transition outputs

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"]returnsettingsdefcreate_rule():r=executables.py_binary_rule_builder()base_impl=r.cfg.implementation()r.cfg.set_implementation(lambdasettings,attr:_force_linux_impl(settings,attr,base_impl))r.cfg.add_output("//command_line_option:platforms")returnr.build()py_linux_binary=create_rule()

Users can then usepy_linux_binary the same as a regularpy_binary. It willact as if--platforms=//my/platforms:linux was specified when building it.