Skip to main content
Aspect CLI includes a special Gazelle extension (nicknamed “orion”) which wraps a Starlark interpreter, and provides an API for you to write your own extensions. Writing extensions in Starlark fixes a bunch of the problems we’ve seen.
  • It’s the same language you’d need to learn to write Macros or Rules
  • Macros are like dynamic rule generators, and Gazelle extensions are like static rule generators. Logic implemented in a macro that provides a user experience like my_abstraction can be ported to a generator which writes the equivalent targets into the BUILD file (imagine this as “inline macro” refactoring) - and vice versa.
  • You can share logic between a rule implementation and the BUILD generator for that rule
  • Starlark is an interpreted and fast language. It’s also highly parallel, parsing and querying the AST in many threads automatically.
  • There’s no problem having many .axl files in your repo. One big user has 29 extensions already. Each extension can be small since there’s little boilerplate.
  • Don’t have to rely on directive comments, since you’re free to special-case as needed in your extension.
  • Rulesets can easily distribute these .axl files in their Bazel module.

Basics

  1. Write a Starlark source file (anywhere in your repo with a .axl extension for GitHub code highlighting).
  2. Register rule(s) that you want to manage using gazelle_rule_kind . We provide three attribute lists for the underlying Gazelle machinery to merge the results we return:
  • NonEmptyAttrs: a set of attributes that, if present, disqualify a rule from being deleted after merge.
  • MergeableAttrs: a set of attributes that should be merged before dependency resolution
  • ResolveAttrs: a set of attributes that should be merged after dependency resolution
Don’t register a kind that another enabled language already provides (e.g. js_library, go_library). The runner aborts in that case, since Gazelle’s last-wins behavior would silently clobber the other language’s resolution.
  1. Register an extension to the configure command with orion_extension . The stage arguments are functions, all optional:
    1. prepare takes the configuration as an argument and returns a PrepareResult declaring which files to process and what queries to run
    2. analyze (optional) inspects query results and declares importable symbols via ctx.add_symbol(...)
    3. declare takes a context object as an argument and creates targets as a side-effect
Here’s a simple example:
That’s all you need to generate sh_library targets for all your shell code!
The older register_configure_extension and register_rule_kind names still work but are deprecated — they print a deprecation warning and forward to orion_extension and gazelle_rule_kind respectively.

Loading extensions

If you use aspect_gazelle(), pass your extensions to its extensions attribute and the macro wires them up for you (it sets ORION_EXTENSIONS on the binary under the hood):
When you run the gazelle binary directly instead — not through aspect_gazelle() — point orion at your extensions with environment variables:
  • ORION_EXTENSIONS_DIR — a directory; every *.axl file in it is loaded.
  • ORION_EXTENSIONS — a comma-separated list of individual extension file paths.

Stages

Each BUILD file is generated by running the extension’s stages in sequence (extensions run in parallel within a stage). All three are optional:
  1. Prepare — declares which source files the extension processes and any queries to run on them, by returning a PrepareResult(sources, queries).
  2. Analyze — inspects per-file query results and calls ctx.add_symbol(id, provider_type, label) to register symbols that other rules can import.
  3. Declare — calls ctx.targets.add(...) / ctx.targets.remove(...) to write rules into the BUILD file. Attribute values of type aspect.Import are resolved to Bazel labels after this stage.

Beyond the basics

A few capabilities you’ll reach for once the simple case works — see the orion README for the full API:
  • Symbols & imports — ctx.add_symbol(...) in analyze publishes a symbol; aspect.Import(id, provider) as an attribute value resolves to the target that provides it. Use aspect.Import(multiple = True) for “collect all” patterns, or ancestor = True to walk up the tree (e.g. finding the nearest tsconfig.json).
  • Extension properties — declare properties with aspect.Property(type, default), then let users set them per-directory with # gazelle:{name} {value} directives. Read them in any stage via ctx.properties, and use ctx.properties.is_local(name) to detect where a directive is declared.
  • Inherited data — ctx.data is a plugin-private key/value store written during prepare and inherited by sub-packages (nearest-ancestor-wins), useful for anchoring a scope at a marker directory.
  • Directory files — ctx.has_file("tsconfig.json") reports whether the current directory contains a file.
A full-featured example: shell.axl