Skip to content

aspect-build/aspect-cli

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

464 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Aspect CLI

aspect is a free, open-source, programmable task runner built on top of Bazel. It replaces the pile of bespoke shell scripts and CI YAML that every Bazel monorepo eventually grows β€” format pre-submits, lint enforcement, BUILD-file generation, release-tag delivery β€” with a single command-line tool that behaves identically on a developer laptop and in CI.

$ aspect test //...
β†’ 🎬 Running `test` task

β†’ πŸ”§ Setup Β· Running setup

β†’ πŸ§ͺ Test Β· Spawning bazel test
INFO: Bazel 9.0.1
INFO: Analyzed 147 targets (0 packages loaded, 0 targets configured).
INFO: Found 122 targets and 25 test targets...
INFO: Elapsed time: 0.866s, Critical Path: 0.07s
INFO: 1 process: 31 action cache hit, 1 internal.
INFO: Build completed successfully, 1 total action

Executed 0 out of 25 tests: 25 tests pass.
INFO: Build Event Protocol files produced successfully.

β†’ βœ… Passed `test` task in 1.2s Β· Tests passed (cached)
    πŸ”§ Setup   2ms  Prepare the task environment
    πŸ§ͺ Test   1.2s  Run bazel tests

Every aspect <task> ends with that per-phase breakdown, so the slow part of a CI step is always called out by name. The same content gets posted back to your PR as Buildkite annotations, GitHub Status Checks, and a PR task summary comment (see examples below). The CLI overview shows aspect format, aspect buildifier, and aspect lint runs too β€” including the hold-the-line output (linter surfaces findings in unmodified files; the task still passes because no new violations were introduced).

Configure and extend in AXL

AXL, the Aspect Extension Language, is how you configure built-in tasks (in .aspect/config.axl) and add your own (as .aspect/*.axl files). It's a typed Starlark dialect evaluated by the Rust Starlark interpreter built by the Buck2 team, so .axl files catch type errors at parse time and parse fast even on huge repos.

Here's an example .aspect/config.axl that exercises several built-ins (for a full live config running in CI, see aspect-build/bazel-examples):

"""Aspect CLI configuration."""

load("@aspect//feature/artifacts.axl", "ArtifactUpload")
load("@aspect//format.axl", "format")
load("@aspect//traits.axl", "BazelTrait")

buildifier = format.alias(
    defaults = {
        "formatter_target": "@buildifier_prebuilt//buildifier",
        "formatter_args_for_tree_walk": ["-r", "."],
        "run_in": "cwd",
        "include_patterns": [
            "**/BUILD",
            "**/BUILD.bazel",
            "**/MODULE.bazel",
            "**/*.MODULE.bazel",
            "**/WORKSPACE",
            "**/WORKSPACE.bazel",
            "**/*.axl",
            "**/*.bzl",
            "**/*.star",
        ],
    },
    summary = "Format Starlark files using buildifier.",
)

def config(ctx: ConfigContext):
    # Set --config=ci on all bazel commands on all CI environments.
    if bool(ctx.std.env.var("CI")):
        ctx.traits[BazelTrait].extra_flags.extend([
            "--config=ci",
        ])

    # Register the buildifier alias as a CLI command.
    ctx.tasks.add(buildifier)

    # Lint aspects β€” required by the built-in `aspect lint` task. Same set
    # locally as on CI; CI invocations don't need to repeat the --aspect flags.
    ctx.tasks["lint"].args.aspects = [
        "//tools/lint:linters.bzl%buf",
        "//tools/lint:linters.bzl%checkstyle",
        "//tools/lint:linters.bzl%clippy",
        "//tools/lint:linters.bzl%eslint",
        "//tools/lint:linters.bzl%keep_sorted",
        "//tools/lint:linters.bzl%pmd",
        "//tools/lint:linters.bzl%ruff",
        "//tools/lint:linters.bzl%shellcheck",
    ]

    # Delivery.
    ctx.tasks["delivery"].args.query = "kind(\"oci_push rule\", //...)"
    ctx.tasks["delivery"].args.bazel_flags = [
        "--config=release",
    ]

    # Enable artifact uploads for testlogs, profile, and BEP.
    # upload_test_logs="failed" β€” the logs from passing tests are noise;
    # failing/flaky tests' logs are the ones anyone would actually open.
    ctx.features[ArtifactUpload].args.upload_test_logs = "failed"
    ctx.features[ArtifactUpload].args.upload_profile = True
    ctx.features[ArtifactUpload].args.upload_bep = True

Why another tool on top of Bazel?

bazel is a build system, not a developer-workflow tool. Every Bazel monorepo eventually grows the same scaffolding by hand β€” format pre-submits, lint enforcement, BUILD-file generation, release-tag delivery β€” written in shell or YAML, copy-pasted across teams, behaving differently in CI than on a laptop.

aspect replaces that scaffolding. It's a programmable task-runner layer on top of bazel: built-in tasks you tune in .aspect/config.axl, custom tasks you add as .aspect/*.axl files, and a clean fallthrough to raw bazel for anything aspect doesn't wrap. You're not switching build systems β€” you're extending the one you already have.

What's in the box:

  • Same command, every environment. aspect lint on a laptop does the same thing as in a CI pipeline. Cuts an entire class of "works on my machine but not in CI" bugs.
  • The CI scaffolding every Bazel repo eventually re-invents β€” built in: hold-the-line lint (fails only on violations you introduced), selective delivery (re-deploys only services whose Bazel outputs actually changed), smart changed-file detection, bounded retry on transient Bazel errors, native artifact upload, and per-step status checks on GitHub, Buildkite, GitLab, and CircleCI.
  • Custom CLI commands in ~10 lines of Starlark. Drop a .axl file into .aspect/ and aspect <name> is a real CLI command (see below). Tasks can shell out, read/write files, query the build graph, and subscribe to Bazel's Build Event Stream and Compact Execution Log.
  • Per-repo version pin. .aspect/version.axl pins the CLI version; the launcher fetches the matching binary on first invocation, so local and CI stay in sync.
  • Standalone or with Aspect Workflows. The CLI works on its own with any Bazel workspace. Pair it with Aspect Workflows β€” Aspect's managed Bazel-CI runners, deployed in your AWS or GCP account or hosted by Aspect β€” for sub-minute cached builds, 2–3Γ— faster CI, 40–80% cloud-compute savings, plus a Web UI, remote cache, and remote build execution.

Install

curl -fsSL https://install.aspect.build | bash

macOS and Linux. Apache-2.0. No Aspect account required.

The 10-minute Quickstart walks from install to writing a custom task. Full docs: docs.aspect.build/cli.

Built-in tasks

Three tasks work in any Bazel workspace with no extra setup β€” they're bazel build / bazel test / bazel run plus the retry, BES streaming, artifact upload, status checks, and CI-platform reporting described above:

Task What it does
aspect build Build Bazel targets
aspect test Run Bazel tests, with optional LCOV coverage
aspect run Build and run a binary target

The remaining built-ins need both Bazel-graph wiring (a tool dependency in MODULE.bazel, plus a BUILD target or rule wired up to it) and AXL wiring (programmable configuration in .aspect/config.axl) to point the task at your repo's setup. Each task page walks through both:

Task What it does What it needs
aspect format Format files changed in the PR A //tools:format BUILD target (the task's default) that wraps a formatter binary β€” typically via aspect_rules_lint or buildifier_prebuilt, plus the matching bazel_dep. Override the target path with formatter_target in .aspect/config.axl if you put it somewhere else.
aspect lint Run linters with hold-the-line strategy aspect_rules_lint plus the linter rules of choice (eslint, ruff, golangci-lint, …); lint aspects declared in .aspect/config.axl
aspect gazelle Generate and sync BUILD files A //tools:gazelle BUILD target (the task's default) that wraps a Gazelle binary β€” typically via gazelle or aspect_gazelle_prebuilt (for Starlark-defined extensions), plus the matching bazel_dep. Override the target path with gazelle_target in .aspect/config.axl if you put it somewhere else.
aspect delivery Deliver only targets whose Bazel-built outputs changed BUILD targets that implement delivery (e.g. oci_push, helm_push, custom bazel_run-able scripts); a --query (or config.axl equivalent) selecting which targets are deliverables

aspect help lists every task available in your repo (built-ins plus any custom ones you've added).

Custom tasks

Drop a .axl file into .aspect/, define a task, and aspect <name> is a CLI command:

# .aspect/codegen.axl
def _impl(ctx: TaskContext) -> int:
    return ctx.bazel.build(*ctx.args.targets).wait().code

codegen = task(
    summary = "Run the code generator.",
    implementation = _impl,
    args = {
        "targets": args.positional(default = ["//gen/..."]),
    },
)
aspect codegen //gen/services/...

Tasks can shell out to any subprocess, read and write files, query the build graph, subscribe to Bazel's Build Event Stream, and declare typed arguments. How to run and define tasks covers the full walkthrough. The AXL reference documents every type and built-in.

Running in CI

The same aspect <task> command you run locally works identically in CI. Running tasks in CI has ready-to-paste YAML for GitHub Actions, Buildkite, GitLab CI, and CircleCI β€” for both provider-hosted runners and Aspect Workflows CI runners.

On GitHub Actions specifically, aspect-build/setup-aspect handles the install, wires up GHA caching, and exchanges your ASPECT_API_TOKEN for a short-lived JWT β€” in one action step.

See it in action

The aspect-build/bazel-examples repo runs aspect <task> pipelines on every commit across all four supported CI providers. Click through to inspect a current build:

CI provider Live pipeline
GitHub Actions Actions tab
Buildkite Recent builds
GitLab CI/CD Pipelines
CircleCI Pipeline runs

aspect <task> posts task results to three surfaces:

  • PR task summary comment β€” a single comment summarising every task in the pipeline.
  • GitHub Status Checks β€” one check per aspect <task> invocation, named by --task-key.
  • Buildkite annotations (when running on Buildkite) β€” one annotation per aspect <task> invocation, rendered at the top of the build page.

Live links to each, from real runs of this repo's own CI: What aspect <task> reports back.

Comparison to older versions

Note

This is a Rust rewrite, superseding the legacy Go version published as version 2025.41 and earlier. The older implementation is now in maintenance mode at aspect-build/aspect-cli-legacy.

Versions before 2025.42 differed in some notable ways:

  • Older versions shadowed the bazel command in the recommended installation, using homebrew or bazeliskrc to override bazel. Now aspect works alongside bazel.
  • The plugin system used a gRPC client/server protocol. Now aspect uses Starlark via the Aspect Extension Language (AXL).
  • Older versions included a fully pre-compiled Gazelle binary along with some Gazelle extensions, using the configure command. This has moved to a standalone repo: aspect-build/aspect-gazelle.

Community and support

License

Apache License 2.0.

About

{ Correct, Fast, Usable } -- Choose three

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Contributors