# Tuist Module Cache

import { Aside } from '@astrojs/starlight/components';

Avrea's Tuist integration stores binary module artifacts in the colocated build
cache. A warm artifact can be reused by later jobs and workflow runs for the
same repository instead of rebuilding that module from source.

Available on macOS runners only. The integration is off by default.

## Enable the cache

Turn on **Tuist Module Cache** at the organization or repository level under
**Settings** in the [Avrea console](https://console.avrea.com). You can also use
the CLI:

```bash
avr settings set cache.tuist-module.enabled true
```

The setting applies to new jobs. A job that is already running keeps its
current environment. It authorizes the cache and configures new Avrea jobs; it
does not enable remote-cache usage inside the Tuist project.

## Configure the project

Add a stable `fullHandle` to `Tuist.swift`. It namespaces the cache artifacts
and does not need to identify a project registered with Avrea or Tuist Cloud.
If the project already has a `fullHandle`, preserve it.

```swift title="Tuist.swift"
import ProjectDescription

let tuist = Tuist(
    fullHandle: "my-organization/my-project"
)
```

Cacheable targets include frameworks, libraries, bundles, and Swift macros.
Application and test targets are not stored as module-cache artifacts, but the
frameworks and libraries they depend on can be. See the [Tuist module cache
guide](https://tuist.dev/en/docs/guides/features/cache/module-cache) for the
complete eligibility and project-configuration rules.

## On Avrea runners

Avrea injects `TUIST_CACHE_ENDPOINT` and `TUIST_TOKEN` before workflow steps
run. Do not add a cache token secret or override the endpoint. If your workflow
already defines `TUIST_TOKEN`, Avrea preserves it.

Tuist itself must still be installed in the job. The examples below use
[`jdx/mise-action`](https://github.com/jdx/mise-action) and assume the Tuist
version is pinned in `.mise.toml` or `.tool-versions`. Adapt that step if the
project installs Tuist another way.

## Warm the cache from the main branch

[Tuist recommends](https://tuist.dev/en/docs/guides/features/cache/module-cache#recommended-setup)
a dedicated cache-warming job for every commit to the main branch. The job
builds eligible targets and uploads complete module artifacts that later
development and CI jobs can substitute.

If the project declares Swift package dependencies in `Tuist/Package.swift`,
add the optional dependency-installation steps shown below before warming.

```yaml title="warm-tuist-cache.yml"
name: Warm Tuist caches

on:
  push:
    branches: [main]

jobs:
  warm:
    runs-on: avrea-macos-latest
    steps:
      - uses: actions/checkout@v7

      - name: Install Tuist
        uses: jdx/mise-action@v4

      - name: Warm Tuist module cache
        run: tuist cache warm
```

Keep the warming command at the end of its dedicated job. Tuist modifies the
generated workspace while warming; a later step that needs the workspace must
run `tuist generate` again first.

The cache profile controls which targets Tuist warms and substitutes. Omitting
`--cache-profile` uses the project's configured profile or Tuist's default.
Use `--cache-profile only-external` to limit the cache to external packages, or
`--cache-profile all-possible` to include as many eligible internal targets as
possible. Preserve the profile already selected by the project unless you
intend to change that behavior.

## Consume the cache in pull requests

Pull-request jobs install Tuist and run their normal generation, build, and
test commands without warming the remote module cache. Add the same optional
dependency-installation steps before generation when the project uses
`Tuist/Package.swift`.

```yaml title="pull-request.yml"
name: Pull request

on:
  pull_request:

jobs:
  build:
    runs-on: avrea-macos-latest
    steps:
      - uses: actions/checkout@v7

      - name: Install Tuist
        uses: jdx/mise-action@v4

      - name: Generate project
        run: tuist generate --no-open

      - name: Run tests
        run: tuist test
```

Adapt the final generation, build, and test commands to the project. The
important division is that main warms the module cache and pull requests
consume the latest compatible artifacts.

## Optional: accelerate package installation

The Tuist module cache works independently from Swift package source caching.
Projects without dependencies in `Tuist/Package.swift` do not need this
section. For projects that have them, add the following steps after installing
Tuist in both the warming and pull-request jobs.

Avrea automatically configures its [Swift Package
Registry](/cache/packages/swift/) as the default and associates the reserved
`avrea-com` scope used by eligible private packages. The same managed
configuration is available to Tuist, so normal workflows do not need to
associate public scopes. Restore Tuist's installed dependency state with
GitHub Actions Cache:

```yaml title="Tuist dependency steps"
- name: Restore Tuist dependencies
  id: tuist-dependencies
  uses: actions/cache@v6
  with:
    path: |
      Tuist/.build
      Tuist/Package.resolved
    key: tuist-install-v2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Tuist/Package.swift', 'Tuist/Package.resolved', 'Tuist.swift', '.mise.toml', '.tool-versions', '.xcode-version') }}

- name: Install Tuist dependencies
  if: steps.tuist-dependencies.outputs.cache-hit != 'true'
  run: >-
    tuist install
    --replace-scm-with-registry
    --force-resolved-versions
```

This assumes `Tuist/Package.resolved` is committed. Direct registry identities
and eligible semantic-version GitHub URL dependencies use the registry. Branch
and commit dependencies continue to use Git. The [Swift Package Registry
guide](/cache/packages/swift/#tuist-and-swiftpm-cli) contains the complete
identity, scope, and fallback matrix.

## How the cache layers work together

Each layer avoids a different kind of work:

| Layer | What it stores | When it is used | Isolation | Avrea storage |
|---|---|---|---|---|
| GitHub Actions cache | `Tuist/.build` and `Tuist/Package.resolved` dependency state | Restores installed dependencies before `tuist install` | Repository-scoped, subject to GitHub branch and ref visibility rules | Private GitHub Actions cache |
| Swift package registry | Package source archives, manifests, versions, and checksums | Supplies eligible semantic-version public and private packages during `tuist install` | Public packages are shared; private packages are organization-isolated | Public package storage or private artifact storage |
| Tuist module cache | Complete prebuilt binaries for eligible project and dependency targets | Substitutes an entire target during project generation and builds | Repository-isolated | Private `tuist-module-build` cache |
| Xcode compilation cache | Reusable compiler outputs for project source, directly built packages, and project package dependencies on Xcode 26.5 | Speeds up eligible source compilation when a complete Tuist module is unavailable | Repository-isolated | Private `xcode-build` cache |

The Tuist module cache and [Xcode compilation
cache](/cache/build-cache/xcode/) are complementary. A Tuist hit replaces the
complete target before compilation. On a Tuist miss, Xcode builds the target
from source and can reuse lower-level compiler outputs for eligible project
source. Targets that Tuist cannot store, including the application target, can
still benefit from Xcode compilation caching. On Xcode 26.5 or later, this also
covers Swift package dependencies built as part of an application target; see
[Xcode Compilation Cache](/cache/build-cache/xcode/#what-it-caches).

`tuist install` uses the GitHub Actions and Swift registry layers, not the two
compilation caches. `tuist cache warm` builds missing targets through Xcode, so
eligible compilation work can also reuse the Xcode compilation cache.

An image, Xcode, or Tuist upgrade can change target hashes. A temporary miss
spike and cache re-warm are expected after those rollouts.

<Aside type="caution">
[Tuist recommends](https://tuist.dev/en/docs/guides/features/cache/module-cache#usage)
using module-cache binaries for development and test workflows, not release
builds. When archiving, generate the project from source with
`tuist generate --cache-profile none`.
</Aside>

Tuist entries appear with type `tuist-module-build` on the **Caches** page and
in `avr cache` output. Xcode entries appear as `xcode-build`. Both are isolated
to the Avrea repository, count toward its cache quota, and follow normal cache
eviction.

## Troubleshooting

### The environment variables are missing

Confirm that **Tuist Module Cache** is enabled for the organization or
repository, then start a new macOS job. A repository-level override takes
precedence over the organization setting.

### Every run is a miss

- Warm the cache before expecting a download on a fresh runner.
- Keep `fullHandle` stable across jobs.
- Use the same repository and compatible runner image for warm and build jobs.
- Check for absolute paths, timestamps, or other non-deterministic inputs in
  the target hash.

### No artifacts are uploaded

- Confirm that the branch being built contains the active `fullHandle`.
- Run `tuist cache warm`; normal generation consumes warmed artifacts but does
  not populate an empty remote cache.
- Do not pass `--no-upload`, and confirm that the selected cache profile
  contains eligible targets.

### A target is not substituted

Use a focused `tuist generate`, confirm the target product is cacheable, and
check that its dependency graph does not contain an uncacheable target. Tuist
can identify implicit dependencies with:

```bash
tuist inspect dependencies --only implicit
```

### SwiftPM reports that no registry is configured for a scope

Avrea's managed default registry handles public scopes without workflow
configuration. Check whether the workflow or runner image creates its own
SwiftPM `registries.json`, which takes precedence over the managed
configuration. Remove the custom file or follow the [custom registry
configuration guidance](/cache/packages/swift/#custom-registry-configuration).