Custom Rules (define_rule)¶
Experimental. The API (
define_rule/attr/ the actionctx) may change in a future release. Tracked in #829.
A macro (a plain function in a .bld that calls existing rules
like gen_rule) is enough for simple reuse. When you need a real new rule
type — with typed attributes and a Python action that computes outputs and
emits build commands — use define_rule. It is inspired by
Bazel's rules but intentionally smaller.
Quick example¶
Suppose you've created an awesome language whose compiler, awesomec, generates C++.
//myrules/awesome.bld:
def _impl(ctx):
out = ctx.declare_output(ctx.name + '.cc') # declare an output
if ctx.attrs['gen_header']:
ctx.declare_header(ctx.name + '.h') # a header cc_* can use
ctx.actions.run_shell(command='awesomec $SRCS -o $OUTS', outputs=[out])
awesome_library = define_rule(
name='awesome_library',
attrs={
'srcs': attr.src_list(exts=['awesome']),
'opt': attr.string(default='-O2'),
'gen_header': attr.bool(default=True),
},
provides_cc=True,
action=_impl,
)
//demo/BUILD:
load('//myrules/awesome.bld', 'awesome_library')
awesome_library(name='demo', srcs=['demo.awesome'])
cc_binary(name='app', srcs=['main.cc'], deps=[':demo']) # gets demo.h / demo.cc
define_rule¶
define_rule is a builtin available only in .bld extension files (like
attr; neither is visible in BUILD files). Bind its result to a name so
load can import it: awesome_library = define_rule('awesome_library', ...).
name: str, the rule type / generated BUILD function name.attrs: dict, attribute name →attr.<kind>(...)(see below).name,deps,visibility,tagsare always available and need not be declared.action: callableaction(ctx)— run in the analysis phase to declare outputs and register build edges (see The action). Required.provides_cc: bool, if True the headers/include dirs declared by the action flow to dependentcc_*targets (viagenerated_hdrs/generated_incs).description: str, default progress label for emitted edges.
Attributes (attr)¶
| kind | value | notes |
|---|---|---|
attr.string(default='', mandatory=False) |
str | |
attr.bool(default=False) |
bool | |
attr.int(default=0) |
int | |
attr.string_list(default=()) |
list[str] | |
attr.src_list(default=(), exts=None) |
list[str] | becomes the rule's srcs; exts restricts file extensions |
attr.dep_list(default=()) |
list[str] | merged into the rule's deps |
attr.out_list(default=()) |
list[str] | a list of output names (the action declares them) |
mandatory=True makes an attribute required. Unknown attributes, missing
mandatory ones, and type mismatches are reported as errors.
The action¶
action(ctx) runs in the analysis phase (when the BUILD is loaded): it only
declares outputs and registers build edges — it never runs commands or reads
/ writes files (that happens later, during the build). Declaring outputs in this
phase is what lets dependents see a rule's generated headers.
ctx read state:
ctx.name,ctx.path,ctx.fullname,ctx.build_dir,ctx.target_dirctx.attrs— the validated attribute values (src_listalready path-expanded)ctx.toolchain,ctx.config(section)ctx.deps_outputs()— output files of the rule's depsctx.deps_generated_headers()—(files, dirs)of generated headers from deps
ctx output declaration:
ctx.declare_output(name)→ returns the full path of an output filectx.declare_header(name)→ likedeclare_output, and (whenprovides_cc) exposes the header to dependentcc_*targetsctx.declare_inc_dir(inc)→ expose a generated include directory to dependents
ctx.actions — emit build edges:
ctx.actions.run_shell(command=None, cmd_bash=None, cmd_bat=None, inputs=None, outputs=None, implicit_deps=None, variables=None, description=None)— emit a per-target rule + build edge.commandis the generic form (host shell);cmd_bash/cmd_batare optional platform variants (selected likegen_rule). The command may use$SRCS,$OUTS,$OUTS[i]/$SRCS[i](by index or name),$SRC_DIR,$OUT_DIR,$BUILD_DIR. (The deprecated$FIRST_SRC/$FIRST_OUTare not supported here — use$OUTS[0]/$SRCS[0].)outputsdefaults to all outputs declared so far;inputsto the rule'ssrcs.ctx.actions.run(rule, inputs=None, outputs=None, …)— emit a build edge that references a shared rule registered withshared_rule.ctx.actions.shared_rule(ninja_rule)— register one shared ninja rule (blade.ninja_rule.NinjaRule) once, so many edges/targets can reference it.
Limitations (V1)¶
define_ruleworks only in.bldfiles loaded viaload— not inBUILD.- The per-target build cache keys on the action's source and its
.bldfile, so keep the action self-contained in its.bld; helpers imported from other.pymodules are not tracked. - No general provider mechanism yet — cross-rule data is limited to the
generated_hdrs/generated_incsflow tocc_*targets. - Generated headers are searched after system/SDK include paths, so avoid output names that collide with system headers.