Skip to content

Continuous Integration (CI)

To run Tuist commands in your continuous integration workflows, you'll need to install it in your CI environment.

Authentication is optional but required if you want to use server-side features like cache.

The following sections provide examples of how to do this on different CI platforms.

Examples#

GitHub Actions#

On GitHub Actions you can use OIDC authentication for secure, secretless authentication:

yaml
name: Build Application
on:
pull_request:
branches:
- main
push:
branches:
- main
permissions:
id-token: write
contents: read
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v2
- run: tuist auth login
- run: tuist setup cache
yaml
name: Build Application
on:
pull_request:
branches:
- main
push:
branches:
- main
permissions:
id-token: write
contents: read
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- run: brew install --formula [email protected]
- run: tuist auth login
- run: tuist setup cache
yaml
name: Build Application
on:
pull_request:
branches:
- main
push:
branches:
- main
env:
TUIST_TOKEN: ${{ secrets.TUIST_TOKEN }}
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v2
- run: tuist setup cache
yaml
name: Build Application
on:
pull_request:
branches:
- main
push:
branches:
- main
env:
TUIST_TOKEN: ${{ secrets.TUIST_TOKEN }}
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- run: brew install --formula [email protected]
- run: tuist setup cache
Oidc Setup

Before using OIDC authentication, you need to connect your GitHub repository to your Tuist project. The permissions: id-token: write is required for OIDC to work. Alternatively, you can use an account token with the TUIST_TOKEN secret.

Tip

We recommend using mise use --pin in your Tuist projects to pin the version of Tuist across environments. The command will create a .tool-versions file containing the version of Tuist.

Xcode Cloud#

In Xcode Cloud, which uses Xcode projects as the source of truth, you'll need to add a post-clone script to install Tuist and run the commands you need, for example tuist generate:

bash
#!/bin/sh
# Mise installation taken from https://mise.jdx.dev/continuous-integration.html#xcode-cloud
curl https://mise.run | sh # Install Mise
export PATH="$HOME/.local/bin:$PATH"
mise install # Installs the version from .mise.toml
# Runs the version of Tuist indicated in the .mise.toml file
mise exec -- tuist install --path ../ # `--path` needed as this is run from within the `ci_scripts` directory
mise exec -- tuist generate -p ../ --no-open # `-p` needed as this is run from within the `ci_scripts` directory
bash
#!/bin/sh
brew install --formula [email protected]
tuist generate
Authentication

Use an account token by setting the TUIST_TOKEN environment variable in your Xcode Cloud workflow settings.

CircleCI#

On CircleCI you can use OIDC authentication for secure, secretless authentication:

yaml
version: 2.1
jobs:
build:
macos:
xcode: "15.0.1"
steps:
- checkout
- run:
name: Install Mise
command: |
curl https://mise.jdx.dev/install.sh | sh
echo 'export PATH="$HOME/.local/bin:$PATH"' >> $BASH_ENV
- run:
name: Install Tuist
command: mise install
- run:
name: Authenticate
command: mise exec -- tuist auth login
- run:
name: Build
command: mise exec -- tuist setup cache
yaml
version: 2.1
jobs:
build:
macos:
xcode: "15.0.1"
environment:
TUIST_TOKEN: $TUIST_TOKEN
steps:
- checkout
- run:
name: Install Mise
command: |
curl https://mise.jdx.dev/install.sh | sh
echo 'export PATH="$HOME/.local/bin:$PATH"' >> $BASH_ENV
- run:
name: Install Tuist
command: mise install
- run:
name: Build
command: mise exec -- tuist setup cache
Authentication

Before using OIDC authentication, you need to connect your GitHub repository to your Tuist project. CircleCI OIDC tokens include your connected GitHub repository, which Tuist uses to authorize access to your projects. Alternatively, you can use an account token with the TUIST_TOKEN environment variable.

Bitrise#

On Bitrise you can use OIDC authentication for secure, secretless authentication:

yaml
workflows:
build:
steps:
- git-clone@8: {}
- script@1:
title: Install Mise
inputs:
- content: |
curl https://mise.jdx.dev/install.sh | sh
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
- script@1:
title: Install Tuist
inputs:
- content: mise install
- get-identity-token@0:
inputs:
- audience: tuist
- script@1:
title: Authenticate
inputs:
- content: mise exec -- tuist auth login
- script@1:
title: Build
inputs:
- content: mise exec -- tuist setup cache
yaml
workflows:
build:
steps:
- git-clone@8: {}
- script@1:
title: Install Mise
inputs:
- content: |
curl https://mise.jdx.dev/install.sh | sh
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
- script@1:
title: Install Tuist
inputs:
- content: mise install
- script@1:
title: Build
inputs:
- content: mise exec -- tuist setup cache
Authentication

Before using OIDC authentication, you need to connect your GitHub repository to your Tuist project. Bitrise OIDC tokens include your connected GitHub repository, which Tuist uses to authorize access to your projects. Alternatively, you can use an account token with the TUIST_TOKEN environment variable.

Codemagic#

In Codemagic, you can add an additional step to your workflow to install Tuist:

yaml
workflows:
build:
name: Build
max_build_duration: 30
environment:
xcode: 15.0.1
vars:
TUIST_TOKEN: ${{ secrets.TUIST_TOKEN }}
scripts:
- name: Install Mise
script: |
curl https://mise.jdx.dev/install.sh | sh
mise install # Installs the version from .mise.toml
- name: Build
script: mise exec -- tuist setup cache
yaml
workflows:
build:
name: Build
max_build_duration: 30
environment:
xcode: 15.0.1
vars:
TUIST_TOKEN: ${{ secrets.TUIST_TOKEN }}
scripts:
- name: Install Tuist
script: |
brew install --formula [email protected]
- name: Build
script: tuist setup cache
Authentication

Create an account token and add it as a secret environment variable named TUIST_TOKEN.

Run report#

When Tuist uploads a run, the dashboard URLs for it are printed to the logs. To get them without scraping the logs — for example to post the test report link to Slack when a job fails, or to feed a run into your own tooling — pass --run-report-path (or set TUIST_RUN_REPORT_PATH) to tuist test, tuist xcodebuild test, or tuist xcodebuild build. Tuist writes a JSON report to that path once the run has been uploaded:

json
{
"runId": "0193f8c1-...",
"status": "success",
"runURL": "https://tuist.dev/acme/app/runs/0193f8c1-...",
"testRunURL": "https://tuist.dev/acme/app/tests/test-runs/...",
"buildRunURL": "https://tuist.dev/acme/app/builds/build-runs/...",
"testRuns": [
{
"scheme": "App",
"succeeded": true,
"totalTests": 42,
"skippedTests": 0,
"ranTests": 42,
"failedTestNames": []
}
],
"buildRuns": [
{ "scheme": "App", "succeeded": true, "durationInSeconds": 12.3 }
]
}
FieldTypeDescription
runIdstringThe run's unique identifier.
statusstring"success" or "failure".
runURLstringThe dashboard URL for the run. Always present.
testRunURLstring?The dashboard URL for the test run, present when the command ran tests.
buildRunURLstring?The dashboard URL for the build run, present when the command built.
testRunsarrayPer-scheme test results: scheme, succeeded, totalTests, skippedTests, ranTests, failedTestNames.
buildRunsarrayPer-scheme build results: scheme, succeeded, durationInSeconds.

testRunURL and buildRunURL are independent: a command that both builds and tests writes both, which the logs can't do — they only print one URL per run.

When the report is written

The report is written only for runs that are uploaded to the server, which requires authentication and happens automatically on CI. The path is cleared at the start of every run, so a run that produces no report leaves nothing behind rather than a stale one from a previous run.

On GitLab CI, a common use is to expose a URL to later jobs through a dotenv report:

yaml
test:
script:
- tuist test --run-report-path tuist-run.json
- echo "TUIST_TEST_RUN_URL=$(jq -r '.testRunURL // empty' tuist-run.json)" >> run.env
artifacts:
reports:
dotenv: run.env