Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

rx is a fast local-CI and task runner for Rust workspaces. Define your pipeline once, run it the same way locally and in CI.

The problem

CI failures you could have caught locally waste round-trips, and every project reinvents the same fmt/clippy/test/build pipeline in shell scripts and YAML. Workspace-aware concerns — which packages did my change affect? what order do tasks run in? — get solved ad hoc, per repo.

What rx does

  • One-command CIrx ci runs your full pipeline locally
  • Task runner — define tasks once in [tasks] with depends-on pipelines, run them anywhere with rx run
  • Affected-only testingrx test --affected tests only packages changed since a base ref
  • Workspace orchestration — dependency-aware parallel execution across members
  • Unified commandsrx test picks nextest when available, rx lint runs clippy strict, rx fmt runs rustfmt
  • Fast builds — auto-detected mold/lld linkers
  • CI generationrx init --ci writes a workflow that mirrors your local pipeline

rx deliberately does not replace Cargo, rustup, or the cargo plugin ecosystem. Compilation scheduling and target/ belong to Cargo; compiler caching belongs to tools like sccache; releases, audits, and scaffolding belong to their dedicated tools. See PRODUCT.md for the full list of non-goals.

Getting started

curl -fsSL https://raw.githubusercontent.com/iPeluwa/rx/master/install.sh | sh
cd my-rust-project
rx init --ci
rx ci

Installation

One-liner

curl -fsSL https://raw.githubusercontent.com/iPeluwa/rx/master/install.sh | sh

Downloads a prebuilt binary for your platform (Linux, macOS, Windows/MSYS), or falls back to cargo install from source.

From source

cargo install --path .

GitHub Action

- uses: iPeluwa/rx@v1
  with:
    command: ci

Shell completions

rx completions bash >> ~/.bashrc
rx completions zsh >> ~/.zshrc
rx completions fish > ~/.config/fish/completions/rx.fish
rx completions powershell >> $PROFILE

Completions are context-aware — they include workspace members, installed targets, and tasks.

Quick Start

Set up a project

cd my-rust-project
rx init                        # generate rx.toml with smart defaults
rx init --ci                   # also generate .github/workflows/ci.yml
rx init --migrate              # detect existing tools and configure
rx config                      # show resolved configuration

Daily workflow

rx check                       # fast type-check feedback
rx build                       # build with fast linker
rx run                         # build and run
rx test                        # run tests (nextest if available)
rx lint                        # clippy with strict defaults
rx fmt                         # rustfmt
rx fix                         # auto-fix everything in one pass
rx ci                          # run full CI pipeline locally

Workspaces

rx graph                       # see the dependency graph
rx ws run build                # build all members in dependency order
rx test --affected             # only test packages your change touched

Tasks

Define project tasks once in rx.toml:

[tasks]
bench = "cargo bench"

[tasks.ci]
depends-on = ["fmt", "lint", "test", "build"]

Then run them locally or in CI:

rx run ci          # runs fmt, lint, test, build through the task graph
rx run bench
rx run             # list all available tasks

fmt, lint, test, build, and check are built-in tasks, so a ci pipeline needs no shell commands at all. Independent tasks run concurrently.

Configuration

rx uses a TOML configuration file called rx.toml to control build, test, lint, format, and watch behavior. Configuration is optional – rx works out of the box with sensible defaults.

Generating rx.toml

rx init              # generate rx.toml with smart defaults
rx init --migrate    # detect existing tools and generate tailored config
rx init --ci         # also generate .github/workflows/ci.yml

rx init inspects your project and applies smart defaults:

  • A default ci task pipeline (fmt, lint, test, build) is defined
  • If mold or lld is detected, it is set as the default linker
  • If cargo-nextest is installed, the test runner is set to "auto"

rx init --migrate goes further by detecting Makefiles, benchmarks, error handling crates, and other project-specific patterns.

File structure

[build]
linker = "auto"            # "auto", "mold", "lld", or "system"
rustflags = []             # extra RUSTFLAGS
cache = false              # opt-in global artifact cache (see PRODUCT.md)
jobs = 0                   # parallel jobs (0 = auto-detect CPU count)
incremental_link = true    # split-debuginfo and --as-needed

[test]
runner = "auto"            # "auto", "nextest", or "cargo"
extra_args = []

[lint]
severity = "deny"          # "deny", "warn", or "allow"
extra_lints = []           # e.g. ["clippy::pedantic"]

[fmt]
extra_args = []

[tasks]
bench = "cargo bench"

[tasks.ci]
depends-on = ["fmt", "lint", "test", "build"]

[env]
RUST_BACKTRACE = "1"

Global vs project config

rx resolves configuration by merging two files:

  1. Global config at ~/.rx/config.toml – applies to all projects
  2. Project config at ./rx.toml – applies to the current project

Project values override global values. This lets you set personal defaults (like a preferred linker) globally while allowing each project to customize as needed.

rx config    # show the fully resolved configuration

Unknown key warnings

rx validates every key in rx.toml. If you mistype a key name, rx prints a warning rather than silently ignoring it:

warning: unknown key `buld` in rx.toml (did you mean `build`?)

This catches typos that would otherwise lead to confusing behavior.

Environment variables

The [env] section sets environment variables for all rx commands:

[env]
RUST_BACKTRACE = "1"
RUST_LOG = "info"

Profiles

Override any configuration per context with [profile.<name>]. See the Profiles chapter for details.

See also

rx.toml Reference

[build]

KeyTypeDefaultDescription
linkerstring"auto"Linker to use: "auto", "mold", "lld", or "system"
rustflagsstring[][]Extra RUSTFLAGS to append
cacheboolfalseEnable the global artifact cache (opt-in; fingerprint does not yet cover target triple, features, toolchain, or build scripts — see PRODUCT.md)
jobsu320Parallel jobs (0 = auto-detect CPU count)
incremental_linkbooltrueEnable incremental linking (split-debuginfo, –as-needed)

[test]

KeyTypeDefaultDescription
runnerstring"auto"Test runner: "auto", "nextest", or "cargo"
extra_argsstring[][]Extra arguments always passed to the test runner

[lint]

KeyTypeDefaultDescription
severitystring"deny"Clippy severity: "deny", "warn", or "allow"
extra_lintsstring[][]Extra clippy lints (e.g. "clippy::pedantic")

[fmt]

KeyTypeDefaultDescription
extra_argsstring[][]Extra rustfmt arguments

[tasks]

Named tasks for rx run. A task is a shell command (string shorthand), or a table with a command and/or depends-on list. Built-in tasks (fmt, lint, test, build, check, ci) are used for names you don’t define; defining the name overrides the built-in.

[tasks]
deploy = "cargo build --release && scp target/release/myapp server:/opt/"

[tasks.ci]
depends-on = ["fmt", "lint", "test", "build"]

[scripts] (deprecated)

Legacy alias for [tasks]: entries are plain name = "command" pairs and are merged into the task table ([tasks] wins on name collisions).

[env]

Key-value pairs of environment variables set for all rx commands:

[env]
RUST_BACKTRACE = "1"
DATABASE_URL = "postgres://localhost/dev"

[profile.<name>]

Override settings per context. See Profiles.

Profiles

Profiles let you override rx configuration for different contexts – CI, development, release builds, or any custom scenario.

Defining a profile

Add a [profile.<name>] section to your rx.toml. Each profile can override build, lint, test, and env settings:

[profile.ci]
build = { cache = false, jobs = 2 }
lint = { severity = "deny" }
test = { runner = "nextest" }
env = { CI = "true" }

Using a profile

Pass --profile <name> to any rx command:

rx --profile ci build
rx --profile ci test
rx --profile ci lint

The profile flag is a global flag, so it must appear before the subcommand.

How merging works

When a profile is active, rx starts with the base configuration (global + project) and then applies the profile overrides on top. Only the fields explicitly set in the profile are changed; everything else keeps its base value.

For example, given this configuration:

[build]
linker = "mold"
cache = true
jobs = 0

[profile.ci]
build = { cache = false, jobs = 2 }

Running rx --profile ci build uses:

FieldValueSource
linker"mold"base config
cachefalseprofile override
jobs2profile override

Common profiles

CI profile

Disable caching (CI runners are ephemeral) and enforce strict linting:

[profile.ci]
build = { cache = false, jobs = 2 }
lint = { severity = "deny" }
test = { runner = "nextest" }
env = { CI = "true", RUST_BACKTRACE = "1" }

Release profile

Use the system linker for maximum compatibility:

[profile.release]
build = { cache = true }
env = { RUSTFLAGS = "-C target-cpu=native" }

Minimal profile

Fast iteration during development:

[profile.quick]
build = { cache = true }
lint = { severity = "warn" }

Profile environment variables

The env field in a profile merges with (and overrides) the base [env] section:

[env]
RUST_BACKTRACE = "1"
RUST_LOG = "info"

[profile.ci]
env = { RUST_LOG = "warn", CI = "true" }

With --profile ci, RUST_BACKTRACE remains "1", RUST_LOG becomes "warn", and CI is set to "true".

Overridable fields

SectionFields
buildcache, jobs
lintseverity
testrunner
envAny key-value pair

Commands

rx has a deliberately small command surface. All commands support the global flags --quiet (-q), --verbose (-v), and --profile <name>.

Project setup

CommandDescription
rx initGenerate rx.toml with smart defaults
rx init --migrateAuto-detect project settings from existing tools
rx init --ciAlso generate .github/workflows/ci.yml
rx configShow resolved configuration

Build and run

CommandDescription
rx buildBuild with fast linker
rx build --releaseRelease build
rx build --target <triple>Cross-compile for a target triple
rx run [-- args...]Build and run the binary
rx checkType-check without codegen (fast feedback)
rx cleanClean build artifacts
rx clean --gcClean local target/ and GC global cache
rx clean --allClean all workspace members

Testing and code quality

CommandDescription
rx testRun tests (nextest if available)
rx test --affectedOnly test changed packages + their dependents (single invocation, repeated -p)
rx test --affected --base mainChanged since a specific branch
rx lintLint with clippy (strict defaults)
rx fmtFormat code with rustfmt
rx fixAuto-fix everything (compiler + clippy + fmt)
rx ciRun full pipeline: fmt, clippy, test, build
rx ci --affectedRun the pipeline against affected packages only

Tasks

CommandDescription
rx run <task>Run a task and its depends-on graph (built-ins: fmt, lint, test, build, check, ci)
rx run <task> --affectedScope the task to affected packages (RX_AFFECTED_PACKAGES for shell tasks)
rx run <task> -- <args>Append extra args to the task’s shell command
rx runList available tasks

Workspace

CommandDescription
rx graphShow the workspace dependency graph
rx ws listList all workspace members
rx ws run <cmd>Run one cargo <cmd> --workspace from the root
rx ws exec <cmd>Run a shell command in each member directory

Maintenance

CommandDescription
rx cache status/gc/purgeManage the opt-in global artifact cache
rx doctorCheck your development environment
rx stats show/clearView or clear build time statistics
rx completions <shell>Generate shell completions

Removed commands

The 0.2 scope reset removed the toolchain-manager surface (new, pkg, toolchain, release, publish, audit, outdated, tree, deps, doc, size, bloat, coverage, bench, expand, compat, upgrade, self-update, sbom, telemetry, plugin, registry, lockfile, env, insights, explain, manpage, sandbox, watch, daemon, worker, test-smart, test-advanced). rx script was folded into rx run, and rx run no longer builds-and-executes your binary — use cargo run or define a task. Use the dedicated tools directly (rustup, cargo and its plugins, sccache, cargo-release, …) or invoke them from a configured rx task. See PRODUCT.md for the reasoning.

Build & Run

The rx build and rx run commands compile your project with automatic fast linker detection, artifact caching, and cross-compilation support.

Basic usage

rx build                        # debug build
rx build --release              # release build
rx run                          # build and run the binary
rx run -- --port 8080           # pass arguments to the binary
rx check                        # type-check only (no codegen)

Fast linker

rx auto-detects the fastest available linker on your system:

  1. mold – fastest, preferred when available
  2. lld – fast, widely available
  3. system – default fallback (cc/ld)

The detected linker is cached at ~/.rx/env.lock so detection only runs once. Override it in rx.toml:

[build]
linker = "mold"    # or "lld", "system", "auto"

Cross-compilation

rx build --target aarch64-unknown-linux-gnu
rx build --target wasm32-unknown-unknown
rx build --target x86_64-pc-windows-msvc

rx passes the --target flag through to cargo. Ensure the target is installed:

rustup target add aarch64-unknown-linux-gnu

Note: cross-compiled builds always invoke cargo directly — the artifact cache never answers --target or --package builds.

Caching

When cache = true (opt-in, off by default — see PRODUCT.md), rx maintains a global content-addressed cache at ~/.rx/cache:

  1. Mtime check – if no source file has changed, skip everything
  2. Fingerprint – compute xxh3-128 hash of sources, Cargo.toml, Cargo.lock, profile, and RUSTFLAGS
  3. Cache hit – restore artifacts via reflink/hardlink into target/
  4. Cache miss – build normally, then store artifacts for next time

Disable caching per-command or in config:

rx build --no-cache             # skip cache for this build
[build]
cache = false

Incremental linking

When incremental_link = true (the default), rx enables:

  • split-debuginfo – keeps debug info separate from the binary
  • --as-needed – only links referenced libraries

This significantly reduces link times for iterative debug builds.

RUSTFLAGS

Add extra RUSTFLAGS in rx.toml:

[build]
rustflags = ["-C", "target-cpu=native"]

These are appended to any existing RUSTFLAGS environment variable.

Parallel jobs

[build]
jobs = 0    # auto-detect (default)
jobs = 4    # use exactly 4 parallel jobs

Flags reference

FlagDescription
--releaseBuild in release mode
--target <triple>Cross-compile for a target
--no-cacheSkip artifact cache
--verboseShow cache paths and timing
--quietSuppress non-error output
--profile <name>Use a config profile
  • rx check – type-check without building (faster feedback loop)
  • rx clean – remove build artifacts
  • rx clean --gc – clean and garbage-collect the global cache

Test

rx test runs your tests with the best available runner (nextest when installed) and can restrict the run to packages affected by a change.

Basic usage

rx test                          # run all tests
rx test --release                # test in release mode
rx test -- --nocapture           # pass flags to the test harness
rx test -- test_name             # run a specific test

Test runner selection

rx selects the test runner based on your rx.toml configuration:

[test]
runner = "auto"      # use nextest if installed, else cargo test
runner = "nextest"   # always use cargo-nextest
runner = "cargo"     # always use cargo test
extra_args = []      # extra args passed to every test run

With "auto", rx checks for cargo-nextest on the PATH and uses it when available. nextest provides better output formatting, per-test timeouts, and parallel execution.

Affected-only testing

Only test packages that have changed since a base ref:

rx test --affected                # changed since HEAD~1
rx test --affected --base main    # changed since main branch
rx test --affected --base v1.0    # changed since a tag

rx maps changed files from git diff to workspace members, expands the set to transitive dependents (a change in core also affects everything that depends on it), and runs one test invocation with repeated -p selections. If nothing relevant changed, the run is skipped.

--affected also works on rx ci and any task: rx ci --affected, rx run lint --affected. Shell tasks receive the selection as RX_AFFECTED_PACKAGES.

  • rx ci – run the full pipeline (includes tests)

Lint & Format

rx wraps clippy and rustfmt into unified rx lint and rx fmt commands with configurable severity and a combined rx fix that applies all auto-fixes in one step.

Linting

rx lint                          # lint with clippy
rx lint --release                # lint in release mode

Severity configuration

Control how clippy warnings are treated:

[lint]
severity = "deny"     # treat all warnings as errors (default)
severity = "warn"     # show warnings but don't fail
severity = "allow"    # suppress warnings entirely

Extra lints

Add additional clippy lint groups or specific lints:

[lint]
extra_lints = ["clippy::pedantic", "clippy::nursery"]

These are passed as additional -W flags to clippy.

Formatting

rx fmt                           # format all code
rx fmt --check                   # check formatting without modifying files

Extra arguments

Pass additional arguments to rustfmt:

[fmt]
extra_args = ["--edition", "2021"]

Auto-fix everything

rx fix combines all auto-fix capabilities in a single command:

rx fix

This runs, in order:

  1. Compiler suggestions – applies rustc fix suggestions
  2. Clippy fixes – applies clippy auto-fix suggestions
  3. Formatting – runs rustfmt on all files

After running rx fix, your code should be free of all auto-fixable issues.

CI pipeline

rx ci runs the full quality pipeline in order:

rx ci

This executes:

  1. rx fmt --check – verify formatting
  2. rx lint – run clippy
  3. rx test – run tests
  4. rx build – verify build succeeds

Use rx ci locally before pushing to catch issues before CI runs.

Profile overrides

Override lint severity per profile:

[lint]
severity = "warn"           # relaxed defaults for local dev

[profile.ci]
lint = { severity = "deny" } # strict in CI
rx --profile ci lint         # fails on any warning

Flags reference

rx lint

FlagDescription
--releaseLint in release mode
--verboseShow detailed clippy output
--quietSuppress non-error output

rx fmt

FlagDescription
--checkCheck formatting without modifying files
--verboseShow formatted file paths
--quietSuppress non-error output

Workspace

The rx ws commands provide workspace-level orchestration for Cargo workspaces. Cargo commands run as a single --workspace invocation; shell commands run per member directory.

Listing members

rx ws list

Lists all workspace members with their paths and versions.

Dependency graph

rx ws graph

Displays the inter-package dependency graph of the workspace, showing which packages depend on which.

Running commands

rx ws run build                  # build all members
rx ws run test                   # test all members
rx ws run test --release         # test all members in release mode
rx ws run check                  # check all members

rx ws run <cmd> runs one cargo <cmd> --workspace from the workspace root. Cargo already builds independent crates in parallel — rx does not spawn one Cargo process per member.

Parallel waves

rx uses Kahn’s algorithm for topological sorting to group packages into parallel “waves”:

Wave 1: [core, utils]           # no inter-dependencies
Wave 2: [api, cli]              # depend on core/utils
Wave 3: [integration-tests]     # depends on api/cli

All packages in a wave run concurrently. The next wave starts only after the current wave completes.

Running shell commands

rx ws exec "wc -l src/*.rs"      # count lines in each member
rx ws exec "cargo doc"           # run arbitrary cargo commands
rx ws exec "echo \$PWD"         # show each member's directory

rx ws exec runs an arbitrary shell command in each member’s directory, in dependency order (topological sort).

Example workflow

A typical CI setup for a workspace:

# Check all members in dependency order
rx ws run check

# Test only affected packages
rx test --affected --base main

# Build everything
rx ws run build --release

Or use the rx ci command which handles the full pipeline automatically.

Cycle detection

rx detects dependency cycles in the workspace graph and reports them as errors. Cargo workspaces should not have cycles, but rx provides a clear error message if one is found.

Cache

rx maintains a global content-addressed artifact cache at ~/.rx/cache. The cache stores compiled artifacts keyed by a fingerprint of your source code, configuration, and build profile.

Commands

rx cache status                  # show cache size and artifact count
rx cache gc                      # remove artifacts older than 30 days
rx cache purge                   # delete the entire cache

How the cache works

Every rx build follows this flow:

1. Mtime fast-path

rx records the modification time of every source file after each build. On the next build, it checks whether any file has a newer mtime. If nothing changed, the cached fingerprint is reused instantly without reading any file contents.

2. Content fingerprinting

If an mtime mismatch is detected, rx computes a full xxHash (xxh3-128) fingerprint from:

  • Cargo.toml contents
  • Cargo.lock contents
  • All source files
  • Build profile (debug or release)
  • Active RUSTFLAGS

3. Cache hit

If a cached build matches the fingerprint, artifacts are restored into target/ using the fastest available method:

  1. Reflink (copy-on-write) – instant on APFS and btrfs
  2. Hardlink – instant, shares the inode
  3. Regular copy – fallback, always works

The build is skipped entirely.

4. Cache miss

The build runs normally via cargo. After completion, artifacts are stored in the cache for future use.

Integrity guarantees

The cache is designed for correctness under concurrent access:

  • Atomic writes – the cache index and mtime snapshots are written to a temp file and atomically renamed
  • File locking – a lock file at ~/.rx/cache/lock prevents concurrent rx processes from racing on the index
  • Staging directory – new artifacts are written to a staging directory, then renamed into place once complete
  • Parallel I/Orayon is used for parallel file copies during store and restore

Cache layout

~/.rx/cache/
├── index.json              # fingerprint -> artifact metadata
├── mtime-snapshot.json     # per-file modification times
├── lock                    # file lock for concurrent access
└── artifacts/
    └── <fingerprint>/      # one directory per unique build
        ├── deps/
        ├── build/
        └── ...

Garbage collection

rx cache gc                      # remove entries older than 30 days

Garbage collection removes cache entries that have not been accessed within 30 days. The cache index is updated atomically.

Cleaning

rx clean                         # clean local target/ directory
rx clean --gc                    # clean target/ and GC global cache
rx clean --all                   # clean all workspace member target/ dirs
rx cache purge                   # delete the entire global cache

Disabling the cache

Disable caching globally or per-profile:

[build]
cache = false

# or per-profile:
[profile.ci]
build = { cache = false }

CI Integration

rx is designed to work seamlessly in continuous integration environments. This section covers the official GitHub Action, VS Code extension, and general CI best practices.

One-command CI

The simplest way to run your full CI pipeline is:

rx ci

This executes, in order:

  1. rx fmt --check – verify formatting
  2. rx lint – run clippy with configured severity
  3. rx test – run the test suite
  4. rx build – verify the build succeeds

CI profiles

Use a profile to customize behavior for CI:

[profile.ci]
build = { cache = false, jobs = 2 }
lint = { severity = "deny" }
test = { runner = "nextest" }
env = { CI = "true", RUST_BACKTRACE = "1" }
rx --profile ci ci

Affected-only execution

In large workspaces, only check what changed (plus its dependents):

rx ci --affected --base main
rx test --affected --base main
rx --profile ci ci

Or as separate steps for better CI reporting:

steps:
  - run: rx fmt --check
  - run: rx lint
  - run: rx test --affected --base main
  - run: rx build --release

Dedicated tools slot in as extra steps where you need them (e.g. cargo audit, cargo deny).

Integrations

GitHub Action

rx provides an official GitHub Action at iPeluwa/rx@v1 for easy CI integration.

Basic usage

- uses: iPeluwa/rx@v1
  with:
    command: ci

This installs rx, sets up the Rust toolchain, caches build artifacts, and runs rx ci.

Inputs

InputDefaultDescription
versionlatestrx version to install. Use latest for the newest release or a specific version like 0.5.0.
commandciThe rx command to run. Can be any valid rx command, e.g. build --release, test, lint.
working-directory.Working directory for rx commands. Set this if your Rust project is in a subdirectory.
profile(empty)rx config profile to use. Maps to the --profile flag.
cachetrueEnable build artifact caching. Caches the Cargo registry, git checkouts, rx cache, and target/ directory.
rust-toolchainstableRust toolchain to install via rustup. Examples: stable, nightly, 1.75.0.

Examples

Full CI pipeline

name: CI
on: [push, pull_request]

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: iPeluwa/rx@v1
        with:
          command: ci

Multiple steps

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: iPeluwa/rx@v1
        with:
          command: fmt --check
      - uses: iPeluwa/rx@v1
        with:
          command: lint
      - uses: iPeluwa/rx@v1
        with:
          command: test

With CI profile

- uses: iPeluwa/rx@v1
  with:
    command: ci
    profile: ci

Cross-platform matrix

jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: iPeluwa/rx@v1
        with:
          command: test

MSRV verification

- uses: iPeluwa/rx@v1
  with:
    command: compat
    rust-toolchain: stable

Release build

- uses: iPeluwa/rx@v1
  with:
    command: build --release
    cache: true

Subdirectory project

- uses: iPeluwa/rx@v1
  with:
    command: ci
    working-directory: backend/

How it works

The action performs these steps:

  1. Install Rust toolchain – uses dtolnay/rust-toolchain with the specified toolchain and adds clippy and rustfmt components
  2. Cache artifacts – uses actions/cache@v4 to cache ~/.cargo/registry, ~/.cargo/git, ~/.rx/cache, and target/
  3. Install rx – downloads the prebuilt binary for the runner’s platform, or falls back to cargo install from source
  4. Run command – executes rx [--profile <profile>] <command> in the specified working directory

Cache key

The cache key is based on the runner OS and Cargo.lock hash:

rx-<os>-<hash of Cargo.lock>

A restore key of rx-<os>- ensures partial cache hits when dependencies change.

VS Code Extension

rx includes a VS Code extension located in the editors/vscode/ directory of the repository. It provides editor integration for all major rx commands.

Installation

Build and install the extension from source:

cd editors/vscode
npm install
npm run compile
# Then install the .vsix file, or use "Developer: Install Extension from Location"

The extension activates automatically when a workspace contains Cargo.toml or rx.toml.

Commands

The extension provides commands accessible from the Command Palette (Ctrl+Shift+P / Cmd+Shift+P):

CommandDescription
rx: BuildRun rx build
rx: Build (Release)Run rx build --release
rx: TestRun rx test
rx: FormatRun rx fmt
rx: LintRun rx lint
rx: CheckRun rx check
rx: FixRun rx fix
rx: Run CIRun rx ci
rx: CleanRun rx clean
rx: DoctorRun rx doctor
rx: Workspace GraphRun rx graph
rx: List TasksRun rx run

Task provider

The extension includes a VS Code task provider. Define tasks in .vscode/tasks.json:

{
  "version": "2.0.0",
  "tasks": [
    {
      "type": "rx",
      "command": "build --release",
      "label": "rx: Release Build"
    },
    {
      "type": "rx",
      "command": "test",
      "profile": "ci",
      "label": "rx: Test (CI profile)"
    }
  ]
}

Task properties:

PropertyRequiredDescription
commandYesThe rx command to run
profileNoConfig profile to use

Auto-check on save

When rx.autoCheck is enabled (the default), the extension runs rx check automatically every time you save a Rust file. Errors and warnings appear in the Problems panel.

Disable it in VS Code settings:

{
  "rx.autoCheck": false
}

Problem matchers

The extension includes a problem matcher that parses Rust compiler errors and warnings from rx output. Errors appear as squiggly underlines in the editor and in the Problems panel, with clickable file/line references.

Settings

SettingDefaultDescription
rx.path"rx"Path to the rx binary. Set this if rx is not on your PATH.
rx.autoChecktrueRun rx check automatically on save.
rx.profile""Default config profile. If set, all commands use this profile.

Tips

  • Use rx: Watch to start continuous rebuilding in a terminal panel
  • The rx: Fix command is useful as a keyboard shortcut for quick auto-fixing
  • Set rx.profile to switch between development and CI configurations without editing rx.toml
  • Task definitions support the profile property, so you can create tasks for different profiles

Performance

rx is designed to minimize build and iteration time at every layer. This page describes each performance feature and how it works.

xxHash fingerprinting

rx uses xxHash (xxh3-128) for all content hashing – fingerprinting source files, cache keys, and artifact identity. xxh3-128 is approximately 10x faster than SHA-256 while providing sufficient collision resistance for build caching.

The fingerprint covers:

  • Cargo.toml and Cargo.lock contents
  • All source files in the crate
  • The build profile (debug/release)
  • Active RUSTFLAGS

Mtime fast-path

Before computing any content hash, rx checks the mtime (modification time) of every source file against a stored snapshot. If no file has a newer mtime than the last recorded build, the cached fingerprint is reused instantly without reading any file contents.

This makes repeated rx build invocations with no changes effectively free.

When restoring cached artifacts into target/, rx uses reflink (copy-on-write) on filesystems that support it (APFS on macOS, btrfs on Linux). A reflink creates an instant zero-copy clone that only allocates new disk blocks when modified.

Fallback order:

  1. Reflink (instant, zero-copy)
  2. Hardlink (instant, shares inode)
  3. Regular copy (byte-by-byte, always works)

Parallel cache operations

Cache store and restore operations use rayon for parallel file copies. When storing or restoring many artifacts, multiple files are processed concurrently across all available CPU cores.

Pipelined builds

In workspace builds, rx overlaps type-checking of downstream crates with code generation of upstream crates. Instead of waiting for a full build of a dependency before starting the dependent crate, downstream cargo check begins as soon as metadata is available.

This is especially effective in large workspaces where independent packages can make progress in parallel.

Fast linker detection

rx auto-detects mold and lld linkers at first run and caches the detection result persistently at ~/.rx/env.lock. Subsequent runs skip the detection entirely. Run rx doctor to refresh the cached environment.

The linker priority is:

  1. mold (fastest)
  2. lld (fast)
  3. System linker (default fallback)

Incremental linking

When incremental_link = true (the default), rx configures:

  • split-debuginfo – separates debug info from the binary, reducing link input size
  • --as-needed – only links libraries actually referenced by the binary

These reduce link times for iterative debug builds.

PGO release builds

The rx binary itself is built with Profile-Guided Optimization (PGO) in CI:

  1. Build an instrumented binary
  2. Run the test suite to generate profile data
  3. Rebuild with the profile data for optimized branch prediction and inlining

Optimized release binary

The distributed rx binary is compiled with:

  • Thin LTO (link-time optimization across crates)
  • Single codegen unit (maximum optimization)
  • Stripped symbols (smaller binary)
  • panic = abort (no unwind tables)

Lazy config loading

Commands that do not need project configuration (like rx doctor or rx completions) skip loading and parsing rx.toml entirely, reducing startup latency.

Persistent env cache

Linker detection, toolchain paths, and other environment probes are cached at ~/.rx/env.lock. This avoids repeated which mold, which lld, and similar subprocess calls on every invocation.

Architecture

rx is a single Rust binary (MSRV 1.85.0) organized into focused modules. This page describes the high-level design.

Module layout

rx
├── cli/               CLI definition (clap derive), lazy config loading, profiles
├── config/            rx.toml parsing, global/project merge, profile resolution, validation
├── build/             cargo build orchestration, fast linker, cross-compilation
├── cache/             Opt-in content-addressed artifact store (xxHash, atomic writes, reflink)
├── cargo_output/      Cargo JSON output parser with error hints
├── workspace/         Dependency graph via cargo metadata, topological sort (Kahn's)
├── affected/          Git-diff-based affected package detection
├── ci/ + ci_gen/      Local CI pipeline and CI workflow generation
├── task/              task graph + runner: [tasks], depends-on, wave concurrency
├── completions/       Shell completions with context-aware dynamic values
├── output/            Colored output, timing, verbosity
├── stats/             Build time tracking and statistics
├── hints/             Error code hints surfaced next to cargo output
├── migrate/           Auto-detection of existing project settings
├── doctor/            Development environment checks
└── install.sh         Self-installer script

How a command runs

  1. CLI parsingclap parses arguments and flags. The --profile flag is captured as a global option.
  2. Config loading (lazy) – if the command needs configuration, rx loads ~/.rx/config.toml and ./rx.toml, merges them (project overrides global), and applies the active profile.
  3. Environment setup – environment variables from [env] are set. The cached env at ~/.rx/env.lock provides linker paths and toolchain info.
  4. Command execution – pipelines (rx run, rx ci) resolve a task graph and execute it wave by wave; each task is a shell command or a built-in module (fmt/lint/test/build/check) that calls cargo as a subprocess. Output is parsed from cargo’s JSON stream for error hints and progress display.
  5. Cache update – if the opt-in artifact cache is enabled, the fingerprint and artifacts are stored in ~/.rx/cache.

Config resolution

Configuration is resolved in layers:

defaults  <  ~/.rx/config.toml  <  ./rx.toml  <  [profile.<name>]  <  CLI flags

Each layer overrides the previous. Unknown keys produce warnings.

Cache design

The global cache at ~/.rx/cache is content-addressed:

~/.rx/cache/
├── index.json          # fingerprint -> artifact metadata
├── mtime-snapshot.json # per-file modification times
├── lock                # file lock for concurrent access
└── artifacts/
    └── <fingerprint>/  # one directory per unique build
        ├── deps/
        ├── build/
        └── ...

Correctness guarantees:

  • Atomic writes – the index and mtime snapshots are written to a temp file and atomically renamed
  • File locking – a lock file prevents concurrent rx processes from corrupting the index
  • Staging directory – new artifacts are written to a staging directory, then renamed into place
  • Parallel I/O – rayon is used for parallel file copies during store/restore

Workspace execution model

rx reads the workspace graph from cargo metadata. Cargo commands run as a single invocation (--workspace, or repeated -p selections for --affected) — Cargo schedules independent crates in parallel itself. rx’s own wave concurrency applies at the task level ([tasks] with depends-on), not inside Cargo’s compilation graph. ws exec uses a topological sort (Kahn’s algorithm) to visit member directories in dependency order.

For --affected, changed files are mapped to members, expanded to transitive dependents, and resolved once into a -p selection.

Error handling

rx wraps all errors with context using anyhow. Every user-facing error includes:

  • A clear description of what went wrong
  • The underlying cause (if available)
  • A hint on how to fix it (common error codes get one-line practical hints next to cargo output)

Comparison

How rx compares to other Rust build tools and task runners.

rx vs raw Cargo

FeatureCargorx
Build, test, fmt, clippyYes (separate commands)Yes (unified + rx ci)
One-command local CINorx ci
Affected-package detectionNorx test --affected
Workspace task orchestrationBasic (--workspace)Task graphs + affected-package selection
Fast linker detectionNoYes (auto-detects mold/lld)
Config fileCargo.toml onlyrx.toml with profiles, tasks, env
Shell completionsNoYes (bash, zsh, fish, PowerShell)

rx wraps Cargo — it doesn’t replace it. Every rx command runs standard Cargo under the hood, and Cargo owns compilation scheduling and target/.

rx vs cargo-make

cargo-make is a task runner with a Makefile.toml format.

Featurecargo-makerx
Task definitionsMakefile.toml (verbose)rx.toml [tasks] with depends-on (concise)
Built-in Rust commandsNo (shell tasks)Yes (build, test, lint, fmt, ci)
Workspace awarenessPlugin-basedBuilt-in (cargo metadata)
Affected-package detectionNoBuilt-in

rx vs just

just is a command runner (like make but simpler).

Featurejustrx
PurposeGeneral task runnerRust-specific local CI / task runner
Rust integrationNone (runs shell commands)Deep (understands Cargo workspaces)
Affected-package detectionNoBuilt-in
Configjustfilerx.toml

rx vs cargo-xtask

cargo-xtask is a pattern for writing build scripts in Rust.

Featurecargo-xtaskrx
SetupWrite Rust code per-projectZero config, works out of the box
MaintenanceYou maintain the xtask craterx maintains the tooling
Cross-project reuseCopy-pasteSame rx binary everywhere

rx vs sccache

sccache is a shared compilation cache. They are complementary, not competing: rx orchestrates tasks; sccache caches compilation. The Cargo documentation recommends sccache for sharing compiled dependencies, and rx defers compiler-level caching to it.

Summary

Use rx if you want your CI pipeline to be defined once and runnable identically on your machine and in CI, with workspace-aware task execution and affected-package detection. Use a general-purpose task runner (just, cargo-make) if your tasks aren’t Rust-shaped, and sccache for compilation caching either way.