> ## Documentation Index
> Fetch the complete documentation index at: https://site.aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Introducing the Starlark extension API

> Introducing orion, the Aspect CLI Starlark extension API for writing Gazelle generators that register rule kinds and configure extensions for Bazel BUILD files.

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

```python theme={null}
aspect.gazelle_rule_kind("sh_library", {
    "From": "@rules_shell//shell:sh_library.bzl",
    "NonEmptyAttrs": ["srcs"],
    "MergeableAttrs": ["srcs"],
    "ResolveAttrs": ["deps"],
})
```

<Note>Don't register a kind that another enabled language already provides (e.g. <code>js\_library</code>, <code>go\_library</code>). The runner aborts in that case, since Gazelle's last-wins behavior would silently clobber the other language's resolution.</Note>

3. 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:

```python theme={null}
aspect.orion_extension(
    id = "rules_sh",
    prepare = lambda cfg: aspect.PrepareResult(
        sources = aspect.SourceExtensions(".bash", ".sh"),
    ),
    declare = lambda ctx: ctx.targets.add(
        kind = "sh_library",
        name = "shell",
        attrs = {
            "srcs": [s.path for s in ctx.sources],
        },
    ),
)
```

That’s all you need to generate `sh_library` targets for all your shell code!

<Note>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.</Note>

## Loading extensions

If you use [`aspect_gazelle()`](/learning/aspect-150/install), 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):

```python theme={null}
aspect_gazelle(
    name = "gazelle",
    languages = ["js"],
    extensions = ["//tools/gazelle:my_extension.axl"],
)
```

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](https://github.com/aspect-build/aspect-gazelle/blob/main/language/orion/README.md) 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](https://github.com/aspect-starters/shell/blob/main/.aspect/gazelle/shell.axl)
