> ## 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.

# aspect build & test

> Run aspect build and aspect test to wrap Bazel with auto-configured flags, status checks, artifact upload, coverage, and CI integration examples.

`aspect build` and `aspect test` are the core CI tasks. They wrap `bazel build` and `bazel test` with automatic environment configuration, live build progress in your CI platform, artifact upload, and reproducible local invocations.

The same command you run in CI works identically on a developer's machine — no wrappers, no CI-only flag soup. If a build breaks, running `aspect build --task:name build //...` locally reproduces it exactly.

## What they do

**`aspect build`** builds Bazel targets. Under the hood it:

* Streams all Build Event Protocol (BEP) events so features can observe individual target outcomes, timing, and outputs
* Injects remote cache / BES flags via the `BazelDefaults` feature when running on an Aspect Workflows CI runner (or any runner with the relevant env vars)
* Retries automatically on `BLAZE_INTERNAL_ERROR` and `LOCAL_ENVIRONMENTAL_ERROR` — transient failures that don't reflect your code

**`aspect test`** is the same but runs `bazel test`. It adds:

* Code coverage support with LCOV output and optional external tool integration
* Test log upload (via `ArtifactUpload`) so failing test output is one click away in CI

## Automatic environment configuration

On [Aspect Workflows](/aspect-workflows) runners, `BazelDefaults` automatically injects the right BES endpoint, remote cache/execution flags, and runner-specific optimizations. You don't declare them in CI YAML or commit them to `.bazelrc` — the task reads the runner environment and does the right thing.

On standard runners, `BazelDefaults` is a no-op: the same invocation runs against your local output base with no remote connections.

This is the key insight: **the same `aspect build //...` command is fully portable across every CI system and every developer machine**.

## Configuration

Configure in `.aspect/config.axl` (applies to every invocation) or via CLI flags (per-invocation overrides).

### Targets

Default target pattern is `...` (all targets in the workspace). Override with positional args:

```shell theme={null}
aspect build //services/... //libs/...
aspect test //backend/...
```

Or lock them in `config.axl`:

```python theme={null}
def config(ctx: ConfigContext):
    ctx.tasks["build"].args.targets = ["//services/...", "//libs/..."]
```

### Additional Bazel flags

Pass extra Bazel flags without editing `.bazelrc`:

```shell theme={null}
aspect build --bazel-flag=--config=remote --bazel-flag=--jobs=100 //...
aspect build --bazel-startup-flag=--host_jvm_args=-Xmx4g //...
```

Or in `config.axl` with conditional logic:

```python theme={null}
def config(ctx: ConfigContext):
    is_ci = bool(ctx.std.env.var("CI"))
    if is_ci:
        ctx.tasks["build"].args.bazel_flags = ["--config=remote", "--jobs=100"]
```

### Test filters and coverage

```shell theme={null}
# Run only integration tests (exclude slow ones)
aspect test --bazel-flag=--test_tag_filters=integration,-slow //...

# Collect LCOV coverage and run genhtml
aspect test \
  --coverage \
  --coverage-report=coverage.dat \
  --coverage-tool=genhtml \
  --coverage-tool-arg=--output-directory=coverage-html \
  --coverage-tool-arg={report} \
  //...
```

Coverage support resolves the LCOV report from Bazel's `$(bazel info output_path)/_coverage/_coverage_report.dat`. The `{report}` and `{lcov}` placeholders in `--coverage-tool-arg` are substituted with the actual report path for compatibility with different tools (genhtml, codecov, etc.).

## GitHub Status Checks

The `GithubStatusChecks` feature posts a check run to the commit the moment `aspect build` starts:

* Creates the check run immediately (CI platform and developers can see the build is in progress)
* Streams live progress: target counts, current failures
* Completes with pass/fail and a build summary with artifact download links

This requires `ASPECT_API_TOKEN` in the job environment and the [Aspect Workflows GitHub App](/docs/cli/authentication-github) installed. Without those, builds and tests work normally — the check is silently skipped with a single log line explaining what's missing.

Status check names derive from `--task:name`. Set a stable, distinct key on each invocation:

```shell theme={null}
aspect build --task:name build //...
aspect test --task:name test //...
```

## GitLab MR signal

Two features post results back to GitLab from MR pipelines, mirroring the GitHub Status Checks design:

* **`GitlabCommitStatuses`** — per-task commit status on the SHA under review (the rows that show up in the MR's "Pipelines" / commit status column). Transitions `pending → running → success/failed/skipped/canceled` as the task progresses.
* **`GitlabStatusComments`** — a single rolling MR note aggregating every task's live status into one comment, updated as jobs complete. The two features complement each other: the commit status surfaces the headline verdict, and the rolling note carries the detailed per-task breakdown.

Both require `ASPECT_API_TOKEN` in the job environment and the [Aspect Workflows GitLab App](/docs/cli/authentication-gitlab) linked to your Aspect account. The features gate on `detect_gitlab_mr()` returning an MR context (`CI_MERGE_REQUEST_IID` present), so they only fire on merge-request pipelines — branch and scheduled pipelines skip silently.

Commit status names derive from `--task:name`. Set a stable, distinct key on each invocation:

```shell theme={null}
aspect build --task:name build //...
aspect test --task:name test //...
```

For the full setup walkthrough — App install, token roles, `.gitlab-ci.yml` example, troubleshooting — see [Authenticating the Aspect CLI to GitLab](/docs/cli/authentication-gitlab).

## Artifact upload

`ArtifactUpload` uploads build outputs to your CI platform's native storage. All kinds are **opt-in** (nothing is uploaded by default) because artifacts are accessible to anyone with repo access.

```python title=".aspect/config.axl" theme={null}
load("@aspect//feature/artifacts.axl", "ArtifactUpload")

def config(ctx: ConfigContext):
    ctx.features[ArtifactUpload].args.upload_test_logs = "failed"  # only failing tests
    ctx.features[ArtifactUpload].args.upload_profile   = True      # Bazel perf profile
    ctx.features[ArtifactUpload].args.upload_bep       = True      # Build Event Protocol log
```

| Artifact              | Flag                                                  | When to use                                                                 |
| --------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------- |
| Test logs             | `upload_test_logs` (`none`/`failed`/`executed`/`all`) | Debug flaky tests; start with `failed`                                      |
| Bazel profile         | `upload_profile`                                      | Performance analysis with `bazel analyze-profile`                           |
| Build Event Protocol  | `upload_bep`                                          | BES-based tooling; env-var values are redacted before upload                |
| Compact execution log | `upload_exec_log`                                     | Deep action investigation; contains raw env vars — keep off on public repos |

`ArtifactUpload` uses the runner's native credentials (`permissions: id-token: write` on GitHub Actions, agent token on Buildkite, `CI_JOB_TOKEN` on GitLab). It does **not** require `ASPECT_API_TOKEN`.

## How it works under the hood

Both tasks use the same BES event streaming infrastructure:

1. **BES tap** — a local Build Event Service listener captures every BEP event in a streaming loop, processing up to 10,000 events per tick with a minimum 500ms heartbeat between ticks.
2. **Feature lifecycle** — at `task_started`, features subscribe to the event stream. `GithubStatusChecks` and `GitlabCommitStatuses` create the per-task check / commit status; `GithubStatusComments` and `GitlabStatusComments` open or update the rolling PR / MR comment; `ArtifactUpload` sets up upload queues; `BazelDefaults` injects flags.
3. **Retry budget** — if Bazel exits with `BLAZE_INTERNAL_ERROR` or `LOCAL_ENVIRONMENTAL_ERROR`, the task retries automatically. Default: up to 3 additional attempts. Configurable with `--bazel-retry-attempts`.
4. **Graceful teardown** — on cancellation, the task calls `bazel cancel` before exiting so orphaned Bazel servers don't accumulate on CI runners.

## CI examples

<CodeGroup>
  ```yaml GitHub Actions theme={null}
  name: CI
  on:
    pull_request:
      branches: [main]
    push:
      branches: [main]

  jobs:
    build:
      runs-on: [self-hosted, aspect-workflows, aspect-default]
      permissions:
        id-token: write
      steps:
        - uses: actions/checkout@v6
        - uses: aspect-build/setup-aspect@2306377a61c45954ab2df7c7311698b109364352 # v2026.26.9
          with:
            aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
        - run: aspect build --task:name build //...

    test:
      runs-on: [self-hosted, aspect-workflows, aspect-default]
      permissions:
        id-token: write
      steps:
        - uses: actions/checkout@v6
        - uses: aspect-build/setup-aspect@2306377a61c45954ab2df7c7311698b109364352 # v2026.26.9
          with:
            aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
        - run: aspect test --task:name test //...
  ```

  ```yaml Buildkite theme={null}
  steps:
    - label: ":bazel: Build"
      plugins:
        - aspect-build/setup-aspect#1d5768c5d28b72bf523b4722fc9177d2cc2d85c7: ~ # v2026.26.8
      command: aspect build --task:name build //...
      agents:
        queue: aspect-default

    - label: ":bazel: Test"
      plugins:
        - aspect-build/setup-aspect#1d5768c5d28b72bf523b4722fc9177d2cc2d85c7: ~ # v2026.26.8
      command: aspect test --task:name test //...
      agents:
        queue: aspect-default
  ```

  ```yaml GitLab theme={null}
  include:
    - component: $CI_SERVER_FQDN/aspect-build/setup-aspect-gitlab-component/setup@2026.26.8

  build:
    extends: .setup-aspect
    tags: [aspect-workflows, aspect-default]
    script: aspect build --task:name build //...

  test:
    extends: .setup-aspect
    tags: [aspect-workflows, aspect-default]
    script: aspect test --task:name test //...
  # Set ASPECT_API_TOKEN as a masked CI/CD variable.
  ```

  ```yaml CircleCI theme={null}
  version: 2.1

  orbs:
    setup-aspect: aspect-build/setup-aspect@2026.26.10

  jobs:
    build:
      machine: true
      resource_class: circleci-org/aspect-default
      steps:
        - checkout
        - setup-aspect/setup
        - run: aspect build --task:name build //...

    test:
      machine: true
      resource_class: circleci-org/aspect-default
      steps:
        - checkout
        - setup-aspect/setup
        - run: aspect test --task:name test //...
  # Set ASPECT_API_TOKEN as a project environment variable.
  ```
</CodeGroup>
