Skip to main content
rbs is SDK-first: the language rules that ship with it are written in the same .rbs-language API you use. A rule is defined with native.define_rule(), usually in a module that build files load().

native.define_rule()

Registers a rule and returns the rule function, so it can be assigned to a module-level name and exported through load(). For kind = "binary" and "library" the target’s output is its directory under .rbs/bin/<platform>/<package>/<name> — the same tree ctx.bin points at — so caching and rbs run work without per-rule wiring.

The attr module

attr.* constructors describe a rule’s attributes inside attrs = {...}. Shared parameters (all optional, all keyword):

How targets instantiate

When a build file calls your rule:
  • name is required, and srcs / deps get first-class handling (deps become graph edges; entries starting with @external:// are filtered out of the graph and left for the implementation to resolve).
  • Any attribute declared with is_dep = True also contributes :name and //pkg:name labels as graph edges.
  • env and target_compatible_with are accepted by every rule automatically.
  • All keyword arguments — declared or not — are recorded on the target and visible to the implementation as ctx.attr.<key>.

The implementation function

The implementation receives one argument, ctx, and runs at build time (not while the build file loads), only when the target is requested and its action cache key misses.
Read optional attributes defensively — hasattr(ctx.attr, "srcs") — since an attribute the caller never set may be absent from ctx.attr.

The ctx API

Identity and attributes

Output paths: ctx.bin

ctx.bin is the hermetic, package-isolated output layout for the current target — the recommended place for everything the target produces:

Source paths: ctx.src

ctx.actions

Commands run inside the action’s hermetic environment when one is active: exactly the declared env vars (the ambient process environment is not inherited), with the exec root as working directory.

ctx.file

ctx.dir

ctx.json, ctx.archive, ctx.http

Tools and toolchains

External dependencies

Text and HTML helpers

Specialized modules

Outputs and caching

  • For kind = "binary" / "library", the target’s recorded output is its ctx.bin output directory; the action cache stores and restores that tree, and rbs run finds the executable there.
  • ctx.file.read, ctx.file.copy, ctx.file.copy_tree and ctx.dir.link_entries register what they touch as cache inputs, so editing a source file invalidates the target even though your implementation is a build-language function rather than a command line.
  • Treat staged trees (runfiles, node_modules, dist bundles) as derived state: ctx.dir.remove then rebuild, or deleted sources live on in the old tree and keep getting compiled.

Sharing data between rules

rbs has no Bazel-style provider objects. The convention — used by the embedded language rules — is to write a metadata file into the target’s output directory and have dependents read it:

Legacy form: outputs + actions

Instead of implementation, a rule may declare two functions evaluated at load time:
Each action dict may carry: name, command (argv list) or fn (a build-language callable), inputs, outputs, working_dir, description, mnemonic, toolchain. Prefer implementation for new rules.

Testing rules

Unit tests: rbs rules test

Test files use the assert functions:

Integration tests: rbs rules integration

Integration tests spin up a real temporary workspace and run rbs against it: