3.7.3 Aspect Extension Language (AXL)
extraAXL (Aspect Extension Language) is a Starlark task layer for workflows Bazel intentionally leaves outside the build graph: chaining multiple Bazel invocations, reacting to Build Event Protocol output as it streams, or driving repo-wide tools that do not fit cleanly into a BUILD target.1,2
Where AXL Fits
3.7.1 Bazel's Output Boundary drew the boundary first: Bazel inspects graphs and populates output trees, but it does not want to own every repo workflow. 3.7.2 Workflows Outside Bazel then showed the jobs that fall outside that boundary: source-tree updates, query-driven collectors, or multi-phase reporting. AXL is one answer to that gap. It gives the outer workflow a real language instead of a pile of shell wrappers.1,2
That does not make AXL the default answer for every side effect. If all you need is "build this thing, then let me run a checked-in publish or accept script", the simpler bazel run custom-verb pattern is still a better fit.3 The workflow-orchestration example shows that simpler pattern in shell-script form — a .publish launcher plus a query-driven release workflow — so it is the kind of repo-around-Bazel sketch AXL would re-express in Starlark once the orchestration grows beyond a few scripts. AXL becomes interesting when the workflow has to coordinate multiple Bazel calls, consume build events, or combine Bazel with tools that act on the repo rather than on declared outputs.
A Task Is Not a Target
Current Aspect CLI loads shared configuration from .aspect/config.axl and custom tasks from .aspect/*.axl files. After installing and pinning aspect as shown in 3.5.4 Aspect CLI Extensibility, run aspect help to see the tasks already available in the repository. Add a task file only when the workflow needs logic beyond an existing built-in.
An AXL task may look rule-like, but it has a different contract. A rule declares attributes and becomes a node in Bazel's analysis graph. An AXL task declares command-line arguments and becomes a CLI entry point. From that entry point, it can invoke Bazel with ctx.bazel.build(), wait for the result, and optionally subscribe to build events while the build is still running.1
A minimal task has this shape:
def impl(ctx: TaskContext) -> int:
build = ctx.bazel.build(
events = True,
inherit_stderr = False,
*ctx.args.target_pattern,
)
build_status = build.wait()
return build_status.code
mycmd = task(
implementation = impl,
args = {
"target_pattern": args.positional(default = ["..."]),
},
)
That boundary matters. If you need new actions, providers, or output groups inside Bazel's graph, that is <a class="cross-ref" href="/book/4~2" title="4.2 Custom Rules, Providers & Actions"><span class="cross-ref-id">4.2</span> Custom Rules, Providers & Actions</a> territory. AXL lives one layer higher: it orchestrates Bazel, rather than extending Bazel's rule semantics.1,3
What AXL Buys You
-
Interactive output instead of post-processing. Linting can be implemented as a task that asks Bazel for build events, watches for named sets of files in the Build Event Protocol, and prints lint findings as they arrive. Bazel still does the analysis and actions. The task owns the user experience around them.1
-
Portable orchestration around repo tools. WASM bindings let a task call Go or Rust helpers from Starlark. One example wraps
<a class="cross-ref" href="/book/3~4~3" title="3.4.3 Buildozer"><span class="cross-ref-id">3.4.3</span> Buildozer</a>, so a migration or fix-up command can editBUILDorMODULE.bazelfiles without growing another platform-specific shell script.1 -
Workflows that never fit a collector target. Multi-target
run, coverage post-processing, or query-driven "find targets, then build only those" flows are exactly the kind of outer workflow that tends to become brittle when modeled as fake graph edges. AXL keeps those steps outside the graph while still letting them speak Bazel fluently.1,2
How It Shows Up in a Real Repo
The clearest adoption example in the sources is rules_lint 2.0. Its aspect lint and aspect format commands were backed by AXL and wired through a MODULE.aspect file beside MODULE.bazel.4 Current Aspect CLI makes that relationship direct: .aspect/config.axl configures built-ins and .aspect/*.axl defines custom commands.5 AXL does not replace Bazel's build graph or <a class="cross-ref" href="/book/3~5~4" title="3.5.4 Aspect CLI Extensibility"><span class="cross-ref-id">3.5.4</span> Aspect CLI Extensibility</a>. The CLI provides the task surface and AXL provides its program and configuration.
That also keeps the layering honest. The linting logic itself still depends on outputs produced by Bazel and, in the rules_lint design, on aspect-driven analysis over existing targets.1,6 AXL sits above that machinery and decides how to sequence the build, how to render the results, and when to offer an outer-graph side effect such as applying a fix.
Reach for AXL when the workflow is around Bazel rather than inside Bazel: multiple invocations, repo-wide coordination, BEP-aware terminal UX, or structured side effects. If the job is just "produce outputs in the build graph", use <a class="cross-ref" href="/book/4~2" title="4.2 Custom Rules, Providers & Actions"><span class="cross-ref-id">4.2</span> Custom Rules, Providers & Actions</a>. If it is just a thin extra verb over one target, the bazel run macro pattern is usually simpler. AXL earns its keep when the outer workflow has become real software and you want that software in Starlark instead of shell glue.
Check your understanding · 3 questions
1.An AXL task calls ctx.bazel.build() and subscribes to build events. How does this differ from a regular Bazel rule or a genrule?
Select one answer
2.True or false about AXL's role and adoption:
Choose True or False for each sentence
3.When does AXL become the right choice over shell scripts for outer-workflow automation?
Select one answer
Footnotes
-
Sponsored Lightning Talk: Beyond Make Serve: Starlarkification for Tasks - Alex Eagle, Aspect Build — AXL as a Starlark task layer,
ctx.bazel.build(), BEP streaming, WASM helpers, PTY multi-run, and coverage/lint examples ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
The 'outside of Bazel' pattern — why repo-wide archives, query-driven collectors, Gazelle, and coverage often belong in a task runner layer outside the build graph ↩1 ↩2 ↩3
-
Using Macros to Create Custom Verbs — the simpler
bazel run+ macro pattern for.publish/.acceptstyle side-effect verbs ↩1 ↩2 -
Aspect's rules_lint Reaches 2.0 — concrete AXL adoption via
aspect lint/aspect formatandMODULE.aspectnext toMODULE.bazel↩ -
Aspect CLI upstream README — current
.aspect/config.axland.aspect/*.axlloading model, custom task example, andaspect helpdiscovery command ↩ -
Rules_lint: Formatting and Linting All Languages - Alex Eagle, Aspect Build Systems — linting architecture where Bazel/aspects do the graph work and the outer workflow decides how results are consumed ↩