Getting Started

This guide installs Once, runs one cacheable script, and shows how to choose the next path for your repository.

Installation#

Install the current release with mise:

bash
mise use -g "github:tuist/once@$(mise latest github:tuist/once)" mise exec -- once --version

Use mise use github:tuist/once@... without -g when a repository should pin its own Once version in mise.toml.

The remaining examples assume that mise is active in your shell. If it is not, prefix each once command with mise exec --.

Build From Source#

Building the CLI only needs the pinned Rust toolchain, not every ecosystem tool used by Once's development and test suite. Clone Once, then run from the parent directory, outside the checkout so mise does not load its full tool list:

bash
git clone https://github.com/tuist/once.git mise exec rust@1.96.0 -- cargo build --release -p once-cli --manifest-path "$PWD/once/Cargo.toml" ./once/target/release/once --version

On macOS, install Xcode's command-line tools first. On Linux, install the native development libraries libcap-ng-dev and libasound2-dev; the resulting binary also needs libasound2 at runtime.

Try an Existing Project#

If you already have a working Cargo, Swift Package Manager, Xcode, or Bazel project, try it before writing Once configuration. Keep its existing toolchain installed: Once reuses the native project metadata, but does not replace the compiler or SDK.

Start by discovering what Once can build:

bash
cd path/to/project once query targets once build once test

Run once lint when the discovered graph includes a lint target. A project without one reports that no lint targets are available, rather than silently skipping analysis.

Once recognizes the native workspace from its existing files. once build, once test, and once lint each default to the workspace's own targets: the build fans out over every workspace-owned target that exposes build, the test run selects first-party tests, and lint runs every workspace-owned target that exposes lint. No once.toml is created.

Pass --all to any of the three to include targets reached through resolved dependencies; once lint --all in particular will lint vendored third-party code, so pair it with --fail-on error when you want to keep the noise manageable. See Graph guide for the full workspace-owned rule (and its current heuristic limitations) and the per-ecosystem behavior.

Continue with the matching guide for Rust, Swift Packages, Xcode Projects, or Bazel for prerequisites, native dependency behavior, and current boundaries.

Run a Cacheable Script#

For a self-contained example, create an empty directory and a place for the script. You can also follow these steps in an existing repository:

bash
mkdir once-demo cd once-demo mkdir -p scripts

Save the following as scripts/greet.sh:

bash
#!/usr/bin/env -S once exec -- bash # once input "../message.txt" # once output "../build/greeting.txt" # once cwd ".." set -eu mkdir -p build cp message.txt build/greeting.txt cat build/greeting.txt

Create message.txt, make the script executable, and run it:

bash
printf 'hello from Once\n' > message.txt chmod +x scripts/greet.sh ./scripts/greet.sh cat build/greeting.txt

The first invocation ends with a trailer containing cache miss. The local cache works without an account or a remote service. Run the same command again:

bash
./scripts/greet.sh

The second trailer contains cache hit. Once reused the recorded result and restored the declared output without running the script body again.

Prove that Once can restore the output, not just remember that the command succeeded:

bash
rm build/greeting.txt ./scripts/greet.sh cat build/greeting.txt

You should see another cache hit and hello from Once in the restored file. Now change the input:

bash
printf 'hello again\n' > message.txt ./scripts/greet.sh

Once reports a miss and writes hello again. You have now checked both sides of the contract: unchanged inputs reuse the result, and changed inputs run the work again.

What Once Learned#

The three # once lines form the action contract:

  • input tells Once which files affect the result.
  • output tells Once what to capture and restore.
  • cwd chooses the working directory for the script.

The script itself is also part of the cache key. Changing either the script or its declared input causes the work to run again. Paths in these headers are relative to the script's directory, which is why ../message.txt names the file at the repository root.

Correct reuse depends on an honest contract. Declare every file and environment variable that affects the result, and every output you need restored. Once cannot infer a dependency that the script reads but does not declare. Do not cache commands whose purpose is an external side effect, such as publishing a release or sending a notification.

Choose Your Next Path#

  • Continue with Scripted automation when you want to cache existing repository scripts with minimal changes.
  • Continue with the Graph guide when you want typed targets, dependencies, and capabilities that Once and coding agents can query. Its Linting guide turns analyzer reports into cacheable, normalized findings with an explicit failure policy.
  • Continue with the software development kit overview when an application needs direct access to Once cache primitives.
  • Read Connect A Project when you are ready to create a remote project and share cache entries with your team, or Infrastructure to run actions on another machine.