soldr API Reference

August 13, 2026 · View on GitHub

This file is the CLI reference for soldr.

For the product-level support contract about what counts as a supported external API, see API_BOUNDARY.md.

Overview

soldr is a single front door for Rust tool execution and Rust builds.

It has four invocation modes:

  1. soldr cargo ... Delegates to the real Cargo binary while routing cacheable compiler work to the zccache service embedded in soldr-daemon.
  2. soldr <tool> [args...] Fetches and runs a Rust CLI tool binary.
  3. soldr rustc ... Low-level passthrough wrapper mode for explicit RUSTC_WRAPPER=soldr usage.
  4. soldr cc ... / soldr c++ ... Compile native source through a catalogue-backed compiler and sysroot.

The primary user experience is soldr cargo ....

Machine-Facing Support Level

Current support policy:

  • the supported external integration surface is invoking the soldr executable through documented commands and flags
  • the internal Rust crates are not a supported public API
  • wrapper mode and internal environment variables are operational mechanics, not a general-purpose API contract
  • human-oriented command output is not the stable machine-facing protocol
  • the first stable machine-facing protocol is the JSON mode on selected commands documented below

Invocation Modes

Mode 1: Cargo Front Door

soldr cargo build --release
soldr cargo test
soldr cargo run -- --help

Behavior:

  • Resolve Cargo and Rust tools through Soldr's front door. Host-owned binaries retain the caller's CARGO_HOME / RUSTUP_HOME; binaries from Soldr's managed toolchain receive the matching managed context.
  • Fall back to rustup which <tool> when no matching binary is present in the selected toolchain location.
  • Set RUSTC_WRAPPER to a compiler-named Soldr shim when caching is enabled.
  • Register the exact soldr-daemon image and selected root with the singleton broker before Cargo starts. Only the broker places and starts that image.
  • Route cacheable compiler work through the broker SESSION service to the zccache service compiled into soldr-daemon; no separate zccache binary or daemon is fetched.
  • Start a per-build correlation session while keeping cache artifacts under the exact selected Soldr root.
  • Delegate to Cargo with the exact flags the user passed.

Current cache behavior:

  • caching is enabled by default for soldr cargo ...
  • --zccache=managed and the legacy --zccache=system spelling are retained for CLI compatibility; both use the embedded runtime. To deliberately put an external compiler cache in Cargo's wrapper slot, use SOLDR_RUSTC_WRAPPER=/path/to/zccache soldr cargo ....
  • zccache integration currently targets Rust builds through the cargo front door
  • session logs and archived reports live under <soldr-root>/cache/zccache/history/; the embedded service's versioned state and compile journal live under <soldr-root>/cache/zccache/daemon-state/embedded-v1/v<VERSION>/. SOLDR_CACHE_DIR moves this compiler-store boundary; ZCCACHE_CACHE_DIR affects auxiliary front-door/session, rust-plan, and rustfmt state, not the service hosted by soldr-daemon. Soldr neither uses nor sweeps the standalone ~/.zccache root
  • toolchain binaries (rustc, rustfmt, clippy-driver, etc.) are resolved directly from RUSTUP_HOME / CARGO_HOME / PATH before any rustup call; rustup which is only used as a fallback when the direct probe fails. The sole exception is when RUSTUP_TOOLCHAIN is explicitly set to a non-empty value — in that case soldr skips the direct probe and asks rustup for the matching toolchain binary so the pinned channel always wins
  • once a concrete Cargo or Rust tool has been resolved, Soldr propagates its managed CARGO_HOME / RUSTUP_HOME only when that binary came from the Soldr-managed toolchain. Host-owned binaries keep the caller's host toolchain context instead of being paired with Soldr's default-less managed Rustup home

This is the normal build entry point.

Prefer a newer global Soldr

Projects can opt in to using an installed global Soldr when it is newer than the checkout-local executable. Add this to the root manifest:

[workspace.metadata.soldr]
prefer_newer_global = true

Soldr resolves the first different soldr executable on PATH, probes its --version, and delegates only when its SemVer version is strictly higher. Failures to locate or probe a global executable leave the local invocation unchanged. Explicit soldr --as <version> / SOLDR_AS pins remain authoritative.

Mode 2: Tool Fetcher

soldr <tool>[@<version>] [tool-args...]

Examples:

soldr maturin build --release
soldr cargo-dylint check
soldr rustfmt src/main.rs
soldr maturin@1.7.0 build

Rustfmt remains routed through Soldr. Because rustfmt recursively discovers child modules that are absent from Cargo's explicit rustfmt argv, normal recursive invocations always execute the real formatter. The embedded marker shortcut is used only when the invocation explicitly sets skip_children=true, making the source-file set complete and safe to cache.

Resolution order:

  1. Cargo verb shorthand — if <tool> (with no @<version> suffix) is either a cargo subcommand soldr already prebuilds (nextest, deny, audit, ...) OR one of cargo's own first-party verbs (build, test, clippy, ...), the invocation is rewritten as soldr cargo <tool> [args...] and dispatched through the cargo front door. See Cargo Verb Shorthand below for the full list.
  2. Local cache in ~/.soldr/bin/
  3. crates.io repository lookup
  4. GitHub Releases for that repository

Current implementation note:

  • the broader binstall/QuickInstall/cargo install fallback chain is planned behavior, not the current shipped fetch path

Cargo Verb Shorthand

When soldr <verb> [args...] is invoked and <verb> is not a frozen soldr built-in, the External arm rewrites the invocation as soldr cargo <verb> [args...] whenever <verb> matches one of two lists. The long form soldr cargo <verb> continues to work — the shorthand is purely additive.

Routed cargo subcommands (soldr prebuilds these via KNOWN_TOOLS):

nextest, deny, audit, llvm-cov, udeps, semver-checks, expand, watch, chef, zigbuild, xwin, binstall, machete

Routed cargo built-in verbs (cargo's first-party commands):

build, test, check, run, bench, doc, fmt, clippy, tree, update, fix, add, remove, metadata, pkgid, search, vendor, yank, owner, login, logout, init, new, generate-lockfile, verify-project, locate-project, report, install, uninstall, publish

Collision policy. These three verbs MUST stay anchored to their soldr-native meaning and are explicitly excluded from the shorthand:

Verbsoldr-native meaningCargo equivalent (use long form)
cleanClear the embedded zccache build cachesoldr cargo clean
configShow or set soldr configurationsoldr cargo config (unstable)
versionPrint soldr's versionsoldr cargo --version

The borderline case install: bare soldr install <crate> routes to cargo install <crate>. The former standalone soldr install-zccache surface was removed when zccache became a compiled-in service.

Version pinning skips the shorthand. Cargo built-in verbs and registered cargo subcommands cannot be version-pinned via the bare @<version> form — the cargo front door has no per-invocation version knob. soldr build@1.0 keeps the existing External fetch path (and errors with "no crate named build"), and so do the registered subcommands (soldr nextest@0.9.x falls through to External). For pinned cargo-subcommand versions, use the soldr registry (KNOWN_TOOLS::pinned_version in source); for pinned tool fetches, use the External path for crates that actually exist.

# Shorthand
soldr build --release          # == soldr cargo build --release
soldr test --workspace         # == soldr cargo test --workspace
soldr clippy -- -D warnings    # == soldr cargo clippy -- -D warnings
soldr fmt --all -- --check     # == soldr cargo fmt --all -- --check
soldr nextest run              # == soldr cargo nextest run
soldr zigbuild build --target ...  # == soldr cargo zigbuild build --target ...

# Long form (always works as the escape hatch)
soldr cargo clean              # explicitly route `clean` to cargo (NOT soldr's cache clean)
soldr cargo config get profile.dev

PEP 517 Build Backend

soldr ships a PEP 517 build backend (src/soldr/__init__.py in the wheel), so Rust+Python packages can build through soldr instead of raw maturin:

[build-system]
requires = ["soldr"]
build-backend = "soldr"

The existing [tool.maturin] configuration is honored unchanged — the backend delegates to soldr maturin pep517 <hook> with a pinned maturin. The dispatch pins the child's toolchain before exec:

PinEffect
CARGO / RUSTCrustup-resolved cargo + its sibling rustc — rust-toolchain.toml wins over PATH-shadowing standalones (chocolatey/scoop GNU installs).
CARGO_BUILD_TARGETruntime MSVC-default triple on Windows (same policy as the cargo front door).
CMAKE / CMAKE_GENERATOR=Ninjamanaged cmake + ninja from the soldr toolchain archive for cmake-based *-sys crates.
RUSTC_WRAPPER=soldrcompilation caching. The Python backend sets this before calling soldr maturin pep517; direct soldr maturin build / develop auto-inject it when cache is enabled and RUSTC_WRAPPER is unset.

For a project whose packaging logic is not maturin, soldr can wrap another PEP 517/660 backend while keeping the same managed environment:

[build-system]
requires = ["soldr", "setuptools>=64"]
build-backend = "soldr"

[tool.soldr.pep517]
delegate-backend = "setuptools.build_meta"

All standard wheel, editable, metadata, sdist, and dynamic-requirements hooks are forwarded to the delegate. The delegate runs with soldr's target/profile/ linker/cache environment, and the caller environment is restored after each hook. Maturin remains the default when delegate-backend is absent; recursive delegation back to soldr is rejected.

The profile, --profile, and (for editable hooks) editable-profile PEP config settings are also applied to delegated builds. For example, pip install . --config-settings profile=release selects an explicit release profile; without an explicit setting, the fast local dev policy remains in effect.

For local PEP 517 wheel and editable builds, the backend defaults to Cargo's dev profile and sets opt-level = 0, codegen-units = 256, debug = "line-tables-only", lto = false, and incremental = true for every field the project has not explicitly configured in profile.dev. This keeps manual installs fast; release pipelines should continue to select an explicit release profile. A project's [tool.maturin] profile / editable-profile, PEP config settings, or the SOLDR_PEP517_PROFILE environment variable are authoritative. Set SOLDR_PEP517_PROFILE=none to preserve maturin's profile selection entirely.

On every successful wheel or editable build, the backend writes a concise timing and cache summary to stderr. SOLDR_PEP517_STATS=off disables it; SOLDR_PEP517_STATS=full prints the complete cache-session payload as a second stderr line. Soldr also selects full when PIP_VERBOSE or UV_VERBOSE is set by the frontend or caller.

For wheel and editable hooks, soldr keeps the last successful artifact under <effective-soldr-root>/pep517/wheels/. It performs a metadata-only recursive scan of the project (relative path, size, and modification time), staged files included, and also hashes supplied prepared metadata by content. If that fingerprint and the build settings match, soldr hardlinks the cached wheel into the frontend's output directory (copying only when hardlinks are unavailable) and skips the backend's packaging/compression work. Set SOLDR_PEP517_WHEEL_CACHE=off to disable reuse.

The local PEP 517 path also sets SOLDR_PEP517_LINKER=auto by default. Soldr tries the fastest supported linker for the active target, retries once with the platform linker only for a linker-availability failure, and records that successful fallback in versioned state under the soldr cache. Equivalent later builds skip the failed candidate and warn that the standard linker was previously verified. Set SOLDR_PEP517_LINKER=none to disable the automatic policy. A user-specified SOLDR_LINKER=fast remains explicit and never silently falls back; it emits an actionable warning instead. Target-specific linker or rustflags settings from the command environment or project .cargo/config.toml retain precedence. Every pin defers to a pre-set user env var. Maturin acquisition is a ladder controlled by SOLDR_MATURIN_PROVISIONER (auto default: pinned prebuilt binary from GitHub Releases, falling back to the PyPI maturin wheel provisioned into an isolated uv-managed env under ~/.soldr/bin/maturin-uv-<ver>/; binary and uv force one rung). Direct soldr maturin ... preserves caller-provided CARGO, RUSTC, and RUSTC_WRAPPER; SOLDR_RUSTC_WRAPPER only controls Soldr's auto-injected wrapper when RUSTC_WRAPPER is unset.

Target resolution is shared by direct maturin and the PEP 517 backend, in this precedence order: an explicit --target argument (including PEP 517 config settings named target, --target, or build-target), CARGO_BUILD_TARGET, [tool.maturin].target, then the host triple. Before maturin starts, soldr applies the same target OS SDK preparation used by soldr build, target clippy, and target test compilation. The Python backend and the child build also share the same CARGO_TARGET_DIR.

soldr env --target <alias-or-triple> --json includes an additive target_plan object (schema_version: 1) for setup-soldr and other tooling. It reports the canonical target and alias, stable toolchain family, concrete compiler/linker/archiver programs, SDK/sysroot provider and cache identity, sorted environment-key contract, and supported operations. Consumers should invoke the soldr operation rather than reconstructing those environment values themselves.

PyO3 configuration is resolved separately from the target OS SDK. Soldr reads workspace dependency metadata and only injects PYO3_NO_PYTHON=1 for a proven cross-compiled ABI3 extension. PyO3-free projects receive no Python variables; modern PyO3 Windows extensions use raw-dylib without a Python import library; Unix/macOS extensions keep PyO3/maturin's normal dynamic extension behavior. Embedding, legacy, ambiguous, and non-ABI3 builds are never guessed to be ABI3. Caller-provided PYO3_* values always win. soldr env --target ... --json includes the resolved pyo3_plan.

Set SOLDR_PYO3_COMPATIBILITY=sysroot to opt an older PyO3 or embedded Python cross-build into managed target-Python assets. Only this plan mode consults the Python rows in the toolchain catalogue. Soldr selects the newest published version for the target, verifies its catalogue SHA-256, and exports PYO3_CROSS, PYO3_CROSS_LIB_DIR, PYO3_CROSS_PYTHON_VERSION, and PYO3_CROSS_PYTHON_IMPLEMENTATION. SOLDR_PYTHON_VERSION=X.Y.Z requests an exact published version. SOLDR_SYSLIB_ASSET_ORIGIN overrides the asset origin for mirrors and controlled integration tests; the default remains the soldr-toolchain assets branch.

This split follows PyO3's own cross-compilation contract: target Python is optional for ABI3 extensions, PYO3_CROSS_LIB_DIR is only needed when the output must link libpython or consume target interpreter configuration, and Windows uses Rust raw-dylib linking without an import library. See the PyO3 0.29 cross-compilation guide and raw-dylib configuration.

The backend also pins CARGO_TARGET_DIR to a stable per-project path under <effective-soldr-root>/cargo-target/pep517/<project-id> so PEP 517 isolated builds (pip/uv copy the sdist to a throwaway temp dir, discarding target/ after every build) keep Cargo's incremental cache hot across invocations. The project ID is a content identity over build configuration (not Rust source); Cargo remains authoritative for source freshness. This keeps temporary source directories warm without sharing target state between unrelated projects. SOLDR_PEP517_PROJECT_ID exposes the identity to soldr diagnostics and cache keys. A caller-provided CARGO_TARGET_DIR always wins, and SOLDR_PEP517_STABLE_TARGET_DIR=0 disables the pin entirely. The effective root is SOLDR_CACHE_DIR when set; otherwise the backend queries the selected soldr version --json binary for its provenance-aware production (.soldr) or development (.soldr-dev) root, then forwards that exact root to child hooks. The owning daemon removes both PEP517 target namespaces and wheel-cache namespaces after 30 days, or once they are older than four days while the root/volume is under pressure. Active build leases defer the pass.

Mode 3: Internal Wrapper Mode

Wrapper mode is entered when Cargo invokes soldr as the configured RUSTC_WRAPPER.

Typical shape:

soldr /path/to/rustc --crate-name foo ...

In this mode, soldr should act as the transparent build-assistance layer around rustc.

Current implementation status:

  • Wrapper mode still transparently resolves the real rustc, preferring direct binaries before rustup
  • The normal cache-enabled build path now runs through soldr wrapper mode and delegates into managed zccache
  • If caching is disabled, wrapper mode falls through to real rustc without zccache involvement

Mode Detection

When soldr starts, it decides its mode in this order:

  1. If argv[1] looks like rustc or a path to rustc, enter wrapper mode.
  2. Otherwise, parse CLI commands with Clap.
  3. cargo is a first-class built-in subcommand.
  4. Any other first argument falls through to the External arm, which itself tries dispatch in order: cargo verb shorthand (see Cargo Verb Shorthand) first; if no match, treat the argument as a tool name to fetch and run.

Built-in Commands

soldr cc / soldr c++ (soldr#2335)

Compile and link C or C++ with a verified compiler/sysroot bundle from the soldr-toolchain catalogue:

soldr cc --target x86_64-linux-gnu.2.17 hello.c -o hello
soldr c++ --target linux-x64 main.cpp -o app

--target accepts the regular friendly aliases and Rust triples and defaults to host. The supported standalone targets are GNU/Linux, musl/Linux, and x86_64-pc-windows-gnu; unsupported host/target pairs fail explicitly. GNU/Linux uses the catalogue's glibc 2.17 sysroot whether the suffix is explicit or omitted.

Arguments not consumed by the front door are forwarded verbatim to the compiler. Exactly one of these query flags may be used instead of compiler arguments:

  • --print-cc
  • --print-cxx
  • --print-ar
  • --print-linker

The query output is only the prepared executable path. Invoke the soldr cc or soldr c++ wrapper when the target's required sysroot arguments must be applied. CMake supports that shape through its CC and CXX environment variables:

CC="soldr cc --target x86_64-linux-gnu.2.17" \
CXX="soldr c++ --target x86_64-linux-gnu.2.17" \
  cmake -S . -B build
cmake --build build

soldr wheel (soldr#2139)

Build an abi3 Python wheel through soldr's blessed toolchain:

soldr wheel                                        # quick DEV wheel, host target
soldr wheel --release                              # release wheel, host target
soldr wheel --release --target aarch64-unknown-linux-gnu   # release cross wheel
soldr wheel --release --target linux-arm64         # friendly aliases resolve identically
soldr wheel --release --target mac-arm64 --out dist # later arguments reach maturin

--release is opt-in, matching cargo and soldr build: a bare soldr wheel builds the dev profile, which is what you want while iterating. --target is optional and defaults to the host triple.

soldr wheel resolves the target (same alias table as soldr build), prepares the sysroot and toolchain environment, provisions maturin, and delegates to the existing soldr maturin build execution path. Wheel naming is unchanged; that contract is maturin's.

Platform tags: soldr only claims a floor it enforced

invocation--compatibility passed to maturin
soldr wheel --release --target <cross *-linux-gnu>manylinux_2_17
soldr wheel --release --target <cross *-linux-musl>musllinux_1_2
any host-target build (--target omitted or equal to the host)pypi
any dev-profile build (no --release)pypi
any non-Linux targetpypi

pypi is maturin's pseudo-option for "derive the platform tag from the bytes, then validate the filename for PyPI" — a description of the artifact rather than a promise about it.

The distinction is not cosmetic. Target preparation (target_lifecycle::prepare_for_invocation), which is what mounts the catalogue sysroot that creates the 2.17 floor, runs on the maturin path only when the target differs from the host. A host-target *-linux-gnu build links against the machine's own glibc — 2.39 on ubuntu-24.04 — so a manylinux_2_17 tag there would be a claim nothing backed, and pip acts on tags: it installs such a wheel on an old host and the program then dies with version `GLIBC_2.39' not found. .github/scripts/verify_wheel_glibc.py exists to catch exactly that, and the wheel-cross-verify CI lane runs the whole path end to end on a cross target.

(maturin does not silently downgrade an explicit tag: with --compatibility manylinux_2_17 and an ELF needing GLIBC_2.39 its auditwheel implementation fails the build with "Error ensuring manylinux_2_17 compliance". It auto-selects the highest satisfied policy only when no tag was requested. So the previous unconditional claim did not ship broken wheels — it broke soldr wheel for host-target Linux builds on any modern distro.)

Scope notes:

  • abi3 only. A non-abi3 extension module needs a CPython built for the target, not just a sysroot. When soldr cannot place the build in an interpreter-free mode it refuses and names soldr maturin build as the escape hatch, rather than quietly building against the host's Python.
  • No glibc-floor suffix. --target <triple>.<major>.<minor> (soldr#2202) is rejected here. A floor is a request to zig, never a guarantee — the effective floor is also bounded by every symbol the vendored C dependencies reference — so folding it into a manylinux tag would publish a promise soldr cannot keep.
  • Pass --target once, before any forwarded maturin arguments. A second --target in the passthrough is refused rather than silently disagreeing with the sysroot soldr prepared.
  • --release together with a forwarded --debug is refused: those are two different profiles, and picking one would build something you did not ask for.

soldr cargo

Run Cargo through soldr's front door.

Stable -Zthreads fallback

When a stable compiler rejects exactly -Zthreads=<N> with rustc's nightly-only diagnostic, the Cargo front door retries once without that flag. The retry is a fresh soldr cargo invocation, so its effective flags and cache/session plan are rebuilt; artifacts from the failed flag set are never treated as equivalent. Soldr emits a warning containing the removed value and notes that the build may be slower. GitHub Actions receives the warning in ::warning:: form, while supported local terminals render it in yellow.

This fallback is intentionally limited to one removable flag. It applies only when -Zthreads=<N> comes from RUSTFLAGS, CARGO_ENCODED_RUSTFLAGS, or a supported CARGO_TARGET_<TRIPLE>_RUSTFLAGS environment variable, with no other -Z flags present. It never sets or changes RUSTC_BOOTSTRAP, never retries a normal compilation failure, and never retries a nightly invocation. If the flag comes only from Cargo configuration, Soldr leaves the original failure in place and asks the caller to remove it or provide it through one of the supported environment variables.

soldr cargo build --release
soldr cargo test --workspace
soldr cargo check -p soldr-cli

For cross-target builds (soldr cargo --target ...), the target's Rust standard library must be provisioned separately — see the native vs cross targets section of the README.

soldr lint

Run the repository validation suites through Soldr. The default and rust suites run formatting, Clippy, and Dylint with one canonical workspace scope:

soldr lint
soldr lint rust --package soldr-cli
soldr lint deps
soldr lint ci
soldr lint ci --format json
soldr lint all

deps runs deny check, audit, and machete concurrently as cache-disabled children because they do not compile Rust. ci runs the CI/build-surface policy suite (see soldr lint ci below). all runs the ci suite first, then adds --all-features, udeps, and semver-checks after the standard Rust and dependency suites. Compiler-bearing steps stay on the regular Soldr cache lifecycle; cargo-dylint is fetched from its Linux GNU release asset or source-built from the pinned registry version on Windows and macOS.

soldr lint ci

Statically validate the repository's executable CI/build surfaces against Soldr's build policies (soldr#2038). This suite is a pure filesystem scan: it requires no Cargo.toml, no Rust toolchain, and never starts the compiler cache, so it runs in any repository — including non-Rust ones.

soldr lint ci                 # human-readable diagnostics (default)
soldr lint ci --format json   # stable machine-readable report

ci accepts only --format json|human; it does not take Cargo scope flags. It exits 0 when there are no error-severity findings (warnings alone still exit 0) and non-zero when any error-severity finding remains after suppressions.

Scanned surfaces: .github/workflows/**/*.yml|yaml, .github/actions/**/action.yml|yaml, everything under .github/scripts/**, and any *.sh / *.py / *.ps1 helper referenced from a run:/uses: line that exists on disk. Full-line and inline # comments are ignored, so prose that mentions a legacy command is never flagged.

The suite is an extensible registry of independent rules. Adding a new CI policy is a bounded module + one registration entry; the public command surface does not change.

Rule: cross-compile-surface

Enforces that Apple Darwin (*-apple-darwin) and Windows MSVC (*-pc-windows-msvc) builds go through the blessed soldr build --target ... surface (soldr prepare --target ... is also accepted). It flags direct use of a legacy cross wrapper or raw cross compiler for those targets:

  • cargo xwin / cargo-xwin (and cargo install cargo-xwin),
  • cargo zigbuild / cargo-zigbuild,
  • raw zig cc / zig c++ / zig build-exe,
  • *-w64-mingw32-* (mingw), osxcross o64-clang, or a clang/gcc/cc carrying an Apple/Windows --target.

Target-aware Zig exception: Zig and cargo-zigbuild remain allowed for *-unknown-linux-* and manylinux targets. The rule resolves each invocation's --target (including matrix placeholders such as ${{ matrix.target }} against the workflow's declared matrix targets) and evaluates per target, so a legitimate Linux-Zig matrix row never masks an invalid Apple/Windows row. Soldr's own managed xwin / Apple-SDK / LLVM assets are allowed implementation details; the rule governs the repository's public build entrypoint, not soldr internals.

A target that cannot be statically resolved on a surface that is otherwise capable of Apple/Windows builds is reported as a lower-severity warning (non-failing) rather than silently passing.

Inline suppression

A finding can be suppressed with a comment on the offending line or the line immediately above it:

soldr cargo xwin build --target x86_64-pc-windows-msvc  # soldr-lint-ci: allow cross-compile-surface -- intentional legacy-path regression test

allow all suppresses every rule on that line; allow <rule-id>[,<id>...] suppresses only the named rules. Any text after -- is a free-form reason.

JSON schema (schema_version: 1)

--format json emits a single object:

{
  "schema_version": 1,
  "ok": true,
  "findings": [
    {
      "rule": "cross-compile-surface",
      "severity": "error",
      "file": ".github/workflows/release.yml",
      "line": 42,
      "tool": "cargo zigbuild",
      "target": "aarch64-apple-darwin",
      "recommendation": "use `soldr build --target aarch64-apple-darwin` (or `soldr prepare --target aarch64-apple-darwin`) instead of `cargo zigbuild`"
    }
  ]
}
FieldTypeMeaning
schema_versionintegerSchema version; bumped only on a breaking shape change.
okbooleantrue when there are zero error-severity findings.
findings[]arrayEvery finding (error and warning) after suppressions.
findings[].rulestringStable rule id, e.g. cross-compile-surface.
findings[].severitystring"error" or "warning".
findings[].filestringRepo-root-relative path, /-separated.
findings[].lineinteger1-based line where the command begins.
findings[].toolstringDetected non-blessed tool, e.g. cargo xwin.
findings[].targetstringResolved triple, a *-representative, or <unresolved>.
findings[].recommendationstringThe exact blessed replacement command.

For Dylint builds, Soldr installs an absolute soldr-dylint compiler shim. Ordinary dependency and lint-library compilation follows soldr-dylint -> rustc; workspace analysis follows soldr-dylint -> dylint-driver -> rustc. The compiler cache keys the driver, loaded lint-library contents, and Dylint configuration. It caches individual compiler outputs and diagnostics, never a command-level lint verdict: the real Dylint pass executes on every invocation, so changed source is always analyzed. Cargo incremental state accelerates repeated work in the same target tree, while Soldr's object cache enables clean-target and sibling-worktree reuse.

soldr dylint cook

Prepare external dependencies for a real Dylint pass without mixing its nightly artifacts into the repository's ordinary stable build tree:

soldr dylint cook --workspace --all-targets
soldr dylint cook --plan-only --json

The command resolves one exact Dylint nightly from the verified soldr-toolchain catalogue (or an explicit --toolchain nightly-YYYY-MM-DD), then verifies the installed compiler's full release and commit identity. It reconstructs a dependency skeleton and runs a check-shaped pass through Soldr's normal compilation cache. RUSTC_WORKSPACE_WRAPPER and every DYLINT_* library variable are removed for this phase, so custom lint libraries are loaded only by the later real Dylint invocation.

Outputs live under target/dylint/target/<nightly>/, matching Dylint 6's own workspace-check directory. The warm marker includes the observed compiler commit, manifests, lockfile, selected target/profile/features/packages, configuration, and wrapper identity. Workspace source contents are excluded, so editing only a local source file preserves the external-dependency layer. Conflicting nightly requirements from configured lint-library paths fail instead of selecting one heuristically.

--plan-only --json does not install a missing toolchain. Its stable schema_version: 1 result includes compiler, target_directory, build_shape, cache_key, and outcome. A plan-only result can report a verified hit only when the installed compiler identity, marker, and target payload all match. A normal invocation verifies the compiler again after any restore/install and reports miss after cooking or skip when the complete layer is already warm.

Shape options are --target, --release / --profile, --workspace, repeatable --package, --features, --all-features, --no-default-features, --all-targets, --tests, --benches, --examples, repeatable --config, --locked, --frozen, and --offline. Ordinary soldr cook behavior is unchanged.

soldr cook

Content-addressable dependency pre-build (issue #359). cook is a shim around cargo-chef — soldr ships no Rust reimplementation of the recipe logic; it fetches the pinned cargo-chef binary (currently v0.1.73), drives cargo chef prepare to synthesise a recipe.json derived only from Cargo.toml + Cargo.lock, then drives cargo chef cook to compile a stub project containing those deps. The output lands in target/ with no project source code touched, so any subsequent soldr cargo build reuses the compiled deps.

Both phases route through soldr cargo so they automatically pick up zccache (RUSTC_WRAPPER), ZCCACHE_PATH_REMAP=auto, the soldr linker selection, and the soldr-managed CARGO_HOME / RUSTUP_HOME.

soldr cook                                  # debug cook, ephemeral recipe
soldr cook --release                        # release cook, ephemeral recipe
soldr cook --release --target x86_64-unknown-linux-musl
soldr cook --release --recipe-path recipe.json --prepare-only   # Docker phase 1
soldr cook --release --recipe-path recipe.json --cook-only      # Docker phase 2
soldr cook --release --keep-recipe          # cook + leave recipe.json behind
soldr cook --release -- --features extra,fast --no-default-features

Recognised flags:

  • --release — forwarded to cargo chef cook --release.
  • --target <triple> — forwarded to cargo chef cook --target <triple>.
  • --workspace (alias --all) — forwarded to cargo chef cook --workspace.
  • --profile <name> — forwarded to cargo chef cook --profile <name>.
  • -p / --package <name> — repeatable; forwarded as --package <name>.
  • --recipe-path <path> — write/read the recipe at this absolute or manifest-relative path. Without this flag the recipe lives in a temp dir and is deleted on exit.
  • --keep-recipe — retain the recipe on disk (at --recipe-path if supplied, else <cwd>/recipe.json) so you can inspect it.
  • --prepare-only — run cargo chef prepare only and exit. Used for the Docker recipe-layer pattern below.
  • --cook-only — skip prepare; requires --recipe-path. Used for the Docker cook-layer pattern below.
  • --no-trim — skip the post-cook target/ trim. By default (issue #459) cook removes cargo-recreatable noise — incremental state, the synthetic stub binary, build-script binaries, large stderr blobs, debug sidecars, and examples//doc//tests/ — so the downstream tarball ships dramatically fewer bytes (~30–40% drop on a typical cook output). Use --no-trim only if you genuinely need the full raw target/ tree.
  • Anything after -- is forwarded verbatim to cargo chef cook (e.g. --features, --no-default-features, --all-features, --tests, --benches).

Docker recipe pattern — the canonical use case the issue highlights:

FROM rust:1 AS chef
RUN cargo install soldr
WORKDIR /app

FROM chef AS planner
COPY . .
RUN soldr cook --release --prepare-only --recipe-path recipe.json

FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json
# Heavy step — cached as long as recipe.json (i.e. Cargo.lock) is stable.
RUN soldr cook --release --cook-only --recipe-path recipe.json
COPY . .
RUN soldr cargo build --release --bin myapp

Local-dev pattern — first-time setup on a fresh clone:

git clone <repo> && cd <repo>
soldr cook --release         # one-time dep prebuild; ~10 min cold, ~0s warm
soldr cargo build --release  # builds only the project on top

Cargo.lock missing. soldr cook continues with a warning if Cargo.lock is absent — cargo-chef will derive the recipe from Cargo.toml alone, which weakens content-addressability. For deterministic builds, commit Cargo.lock (libraries should still ship Cargo.lock for reproducibility under soldr cook, even though cargo normally .gitignores lockfiles in library crates).

Companion automation. zackees/setup-soldr#110 proposes a GitHub Action that key-tarballs the resulting target/ by Cargo.lock hash; with soldr cook available as a primitive that action's implementation reduces to: hash Cargo.lock → restore tarball → on miss, soldr cook + tar + save.

soldr save / soldr load

Bundle a build-cache directory plus a content-verified snapshot of source-file mtimes into a single .tar.zst archive (save), then restore it on a fresh checkout (load). Intended for CI cache layers that need stable Cargo fingerprints across actions/checkout runs without resorting to mtime-rewrite tricks.

soldr save --cache-dir <dir> --workspace <dir> --out cache.tar.zst
soldr load --archive cache.tar.zst --cache-dir <dir> --workspace <dir>

Recognised soldr save flags:

  • --cache-dir <DIR> — cache directory to archive.
  • --workspace <DIR> — workspace whose source-file mtimes get snapshotted.
  • --out <FILE> — destination archive.
  • --threads <N> — parallel hash / compression worker hint.
  • --mtimes-only — write only the source-file mtime manifest.
  • --delta-from-manifest <FILE> — save a delta against a previously restored base manifest.
  • --ci, --minimal — CI/minimal payload profile. Excludes logs, sockets, lock files, runtime scratch, zccache runtime binaries, and soldr-managed binary/toolchain trees from the cache payload while preserving cache artifacts and manifest state needed for warm rustc hits.
  • --json — emit a JSON summary. Save JSON includes profile, source_files, cache_files, excluded_files, excluded_bytes, archive_bytes, and elapsed_ms.

When --cache-dir contains the active embedded zccache root, soldr save first completes a cache checkpoint, gracefully shuts down the daemon, and waits for the exact daemon generation that acknowledged shutdown before the archive walk begins. A checkpoint alone is not a snapshot barrier because a new publication could otherwise land between the checkpoint and tar traversal. If quiescence cannot be proven, save fails instead of producing a racing snapshot. This intentionally stops the daemon; the next compile-like command starts a fresh generation. An unrelated --cache-dir does not flush, stop, or otherwise depend on the ambient Soldr daemon.

Recognised soldr load flags (issue #575):

  • --archive <FILE> — input archive produced by soldr save.
  • --cache-dir <DIR> — destination cache directory; created if absent.
  • --workspace <DIR> — workspace whose source-file mtimes get the content-verified replay. Optional when --mtimes-only is unset.
  • --threads <N> — parallel-extract worker count. Defaults to rayon's num_cpus; capped at the value passed here.
  • --mtimes-only — refuse cache entries; apply mtime snapshot only.
  • --manifest-out <FILE> — write the archive's protobuf manifest for a later soldr save --delta-from-manifest.
  • --profile-extract — emit a per-phase profile line to stderr after the load completes. Shape:
    soldr load: profile: zstd_decode=4120ms tar_parse=890ms extract_total=10510ms \
      workers={0:n=12058, 1:n=12090, 2:n=12053, 3:n=12030} \
      per_file_p50_us=180 p95_us=450 p99_us=1200 cache_files=48231
    
    Also enabled when SOLDR_PROFILE_EXTRACT=1 is set in the environment. Useful for tuning the parallel-extract worker count against real workloads (issue #575). The line lands on stderr; the existing soldr load: machine-readable status line on stdout is untouched.
  • --auto-defender-exclude — on Windows + admin, briefly add --cache-dir to Defender's exclusion list for the duration of the load (issue #596). Never UAC-prompts: no-op on non-Windows or when the current process isn't elevated.
  • --json — machine-readable status line on stdout instead of the human-readable summary.

SOLDR_LOAD_WORKERS=<N> overrides --threads and sizes the rayon pool that runs both the per-file extract workers and the mtime-replay walk.

soldr status

Show cache and target information. The stable JSON shape retains the historical compile-daemon fallbacks rollup for upgrade compatibility; the mandatory broker route no longer appends new fallback records.

Stable machine-facing mode:

soldr status --json

soldr clean

Clear the local embedded-zccache artifact cache and remove Soldr's zccache session state directory.

soldr config

Show or set configuration.

soldr cache

Inspect the embedded zccache service and its cache status.

Stable machine-facing mode:

soldr cache --json

soldr cache flush

Synchronously checkpoint pending embedded zccache publications and persistent index state without stopping soldr-daemon. The command succeeds only when all reported steps complete; --json exposes the detailed flush report.

soldr cache flush
soldr cache flush --json

soldr cache shutdown

Checkpoint the embedded cache, request graceful daemon shutdown, and by default wait for the exact PID and generation returned in the shutdown acknowledgement. The default wait is 300 seconds and can be changed with --shutdown-timeout-seconds <SECONDS>. Once a daemon acknowledges shutdown, Soldr never force-kills it: a timeout reports failure while the daemon continues its durability work.

--archive-logs <DIR> copies the finalized session log, journal, and stats only after the acknowledged generation is proven quiescent. It therefore cannot be combined with --no-wait. --no-depgraph-save skips the explicit pre-shutdown checkpoint for debugging, but graceful shutdown still completes its own cache flush. --json emits the stable machine-facing result.

soldr cache shutdown
soldr cache shutdown --shutdown-timeout-seconds 600 --archive-logs ./soldr-logs
soldr cache shutdown --no-wait

soldr cache prune-target <path>

Prune stale per-prefix cargo build artifacts inside a given target/ directory. Scanned subdirectories under each profile (debug/, release/, …) are deps/, .fingerprint/, incremental/, and build/.

Two strategies are available:

  • Default — orphan siblings (issue #336). Keep the newest entry per (parent_dir, prefix) bucket. Each subdirectory's set of hash-siblings is pruned independently. Conservative: cargo tolerates orphans across the four subdirs as long as the live entry inside each is current.
  • --keep-latest — aggressive (issue #316). Bucket by prefix alone; keep only the newest hash family per logical artifact name, deleting every other hash's files across the four subdirs. Shrinks a heavily-rebuilt target/ from 16+ GB to ~2 GB on real workloads. Use when you don't need the per-subdir orphan retention.

Recency ranking. Both strategies prefer cargo's authoritative target/<profile>/.fingerprint/<prefix>-<hash>/invoked.timestamp mtime — the same signal cargo's unstable -Zgc uses — and fall back to the entry's own filesystem mtime when the fingerprint file is missing or unreadable. The JSON output reports per-decision provenance via keep_decisions_from_fingerprint and keep_decisions_from_mtime.

soldr cache prune-target ./target                  # dry-run, orphan-siblings
soldr cache prune-target ./target --keep-latest    # dry-run, aggressive
soldr cache prune-target ./target --force          # actually delete (orphan-siblings)
soldr cache prune-target ./target --keep-latest --force  # actually delete (aggressive)
soldr cache prune-target ./target --dry-run --json # machine-readable plan

Defaults to a dry run for safety; pass --force (or --no-dry-run) to actually delete entries. If target/.cargo-lock or any target/<profile>/.cargo-lock is present the command refuses with a non-zero exit code (suggests a live build).

JSON schema (--json):

{
  "schema_version": 1,
  "command": "cache prune-target",
  "target_dir": "<absolute path>",
  "dry_run": true,
  "keep_latest": false,
  "scanned": 3,
  "kept": 1,
  "deleted": 2,
  "reclaimed_bytes": 0,
  "reclaimed_human": "0 B",
  "keep_decisions_from_fingerprint": 1,
  "keep_decisions_from_mtime": 0,
  "entries": [
    {
      "path": "<absolute path>",
      "prefix": "libfoo",
      "hash": "abcdef1234567",
      "size_bytes": 0,
      "size_human": "0 B",
      "mtime_unix": 1700000500,
      "action": "keep"
    }
  ]
}

Explicit target pruning (issues #485 and #1818). Soldr no longer prunes hash families automatically around build-like invocations. Multiple families for one crate can all be live at once, so selecting only the newest family can turn an unchanged warm build into a rebuild loop.

Use soldr cache prune-target <path> when explicit target maintenance is desired. The historical --no-gc-target, --no-gc-target-before, and --no-gc-target-after flags remain accepted as compatibility no-ops. SOLDR_NO_GC_TARGET is likewise accepted but unnecessary now that no automatic pass runs.

soldr version

Print soldr version.

Stable machine-facing mode:

soldr version --json

soldr toolchain

Project-aware orchestrators around rustup and cargo install that read rust-toolchain.toml so users (and CI) don't have to thread the pinned channel through every command.

soldr toolchain install   # rustup toolchain install <channel> --profile minimal --no-self-update
soldr toolchain prepare   # install + component add + target add + cargo install for [soldr.plugins]
soldr toolchain ensure    # bootstrap rustup if missing, run `prepare`, smoke-verify (cargo/rustc --version)
soldr toolchain ensure --json  # same, but emit a stable JSON payload (schema_version: 1)
soldr toolchain link --shim-dir <path> [--json] [--force]  # write PATH shim files (issue #407 Phase 3)
soldr toolchain doctor [--json]   # run env-detection probes (musl-cc, shared target/) (issue #407 Phase 4)

prepare runs, in order:

  1. rustup toolchain install <channel> --profile minimal --no-self-update
  2. rustup component add --toolchain <channel> <component> for every entry in [toolchain].components
  3. rustup target add --toolchain <channel> <target> for every entry in [toolchain].targets
  4. cargo install <name> [--version V] [--locked] [--features ...] [--no-default-features] for every entry in [soldr.plugins]

The Cargo front door performs the toolchain/component/target portion automatically before launching the child build. A successful preparation is memoized under the Soldr cache, so an unchanged warm invocation launches no Rustup subprocesses. The memo is invalidated when the channel, profile, component or target requirements, explicit +toolchain, effective Rustup home/binary, or installed toolchain identity changes. Failed preparation is never memoized. soldr toolchain prepare and ensure remain explicit, unconditional orchestrators and also handle [soldr.plugins].

Plugin installs are bootstrap/dev-tool acquisition, not project compilation. They invoke the directly resolved cargo binary and clear inherited RUSTC_WRAPPER / RUSTC_WORKSPACE_WRAPPER so a setup step cannot recursively re-enter Soldr's zccache wrapper while installing the tools that future builds may use.

The first non-zero exit short-circuits the chain.

soldr toolchain ensure

One-shot "make sure this host can build" verb (issue #407 Phase 2):

  1. Auto-bootstraps rustup into the soldr-managed bin dir if missing (reuses the same logic as soldr bootstrap). Respects SOLDR_NO_BOOTSTRAP=1.
  2. Runs the same install + component add + target add + plugin- install pipeline as prepare.
  3. Smoke-verifies the resolved toolchain by spawning cargo --version and rustc --version. Either failure marks the verify as failed and exits non-zero.

ensure is intended for one-stop bootstrap callers like setup-soldr#133 that want to delegate every TS toolchain step to the soldr binary. Existing install and prepare output formats are unchanged.

The --json payload (schema_version: 1) is the stable contract for those callers — fields may be added in future schema versions but existing field names and types will not change without a schema bump:

{
  "schema_version": 1,
  "channel": "1.95.0",
  "rustup_bootstrapped": false,
  "components_added": ["rustfmt", "clippy"],
  "targets_added": ["aarch64-apple-darwin"],
  "plugins_installed": ["cargo-zigbuild@0.18"],
  "smoke_verify": {
    "cargo_version": "cargo 1.95.0 (abc1234 2026-04-15)",
    "rustc_version": "rustc 1.95.0 (def5678 2026-04-15)",
    "ok": true
  },
  "elapsed_ms": 12345
}

Notes on the schema:

  • channel is null when no rust-toolchain.toml is present; the rest of the payload still serializes so consumers can parse unconditionally.
  • rustup_bootstrapped is true only when ensure actually fetched rustup-init for this invocation. A pre-existing rustup or SOLDR_NO_BOOTSTRAP=1 keeps it false.
  • components_added / targets_added / plugins_installed mirror the manifest entries that the prepare pipeline attempted. Plugin labels are name for bare or * versions, otherwise name@version.
  • smoke_verify.ok is false when either spawn fails or returns non-zero. The JSON payload is emitted in both success and failure cases; only the process exit code differs.

Write PATH shim files into --shim-dir so a child process that resolves cargo / rustfmt / clippy-driver / rustc / rustdoc through PATH gets routed back through soldr <tool> (issue #407 Phase 3, ports setup-soldr's ensure-shims.ts).

Each shim is platform-aware:

  • Unix: a #!/bin/sh script that execs <soldr-path> <tool> "$@". File mode 0o755.
  • Windows: a .cmd script with CRLF line endings that calls "<soldr-path>" <tool> %*.

The absolute path to the running soldr binary (resolved via std::env::current_exe()) is baked into each shim at write time, so the shim does not depend on PATH to find soldr itself.

Idempotency:

  • Existing shim file whose contents equal the expected body → left alone, reported as skip_reason: "existing-matches". Mtime is preserved.
  • Existing shim file whose contents differ AND --force not passed → left alone, reported as skip_reason: "existing-differs". The caller can detect this and decide whether to re-run with --force.
  • Existing shim file whose contents differ AND --force passed → overwritten, reported as created: true.

The --json payload (schema_version: 1):

{
  "schema_version": 1,
  "shim_dir": "/runner/.setup-soldr/shims",
  "tools": [
    {"name": "cargo", "shim_path": "/runner/.setup-soldr/shims/cargo", "created": true},
    {"name": "rustfmt", "shim_path": "/runner/.setup-soldr/shims/rustfmt", "created": false, "skip_reason": "existing-matches"},
    {"name": "clippy-driver", "shim_path": "/runner/.setup-soldr/shims/clippy-driver", "created": true},
    {"name": "rustc", "shim_path": "/runner/.setup-soldr/shims/rustc", "created": true},
    {"name": "rustdoc", "shim_path": "/runner/.setup-soldr/shims/rustdoc", "created": true}
  ],
  "elapsed_ms": 12
}

Notes on the schema:

  • tools[].skip_reason is omitted (not null) when created: true.
  • The tools array order is stable: cargo, rustfmt, clippy-driver, rustc, rustdoc.
  • link never adds the shim directory to the caller's PATH — that is the caller's responsibility (e.g. setup-soldr emits a GitHub Actions addPath call after consuming the JSON).

soldr toolchain doctor

Run env-detection probes that ship the diagnostic intel setup-soldr used to compute in TypeScript (issue #407 Phase 4, ports the env-detection halves of setup-soldr's detect-musl-cc.ts, detect-shared-target-warning.ts, and diagnostics.ts).

Namespaced under toolchain to avoid colliding with the top-level soldr doctor system check.

Probes (run in stable order):

  1. musl-cc — scans PATH for musl-gcc / musl-clang and, if found, captures the first line of --version output. Auto-skipped on non-Linux hosts (details.skipped = "not-linux"). When the host is Linux but no musl C compiler is on PATH, the probe still reports ok: true with details.found = false — not all Linux workflows need musl tooling, so missing it is informational rather than fatal.
  2. shared-target-warning — walks the current workspace's target/ (up to 3 levels deep) looking for a populated cargo .fingerprint/ directory. Mirrors the prepopulated-target detector landed in PR #508. Reports would_warn: true when at least one fingerprint dir is found, signalling that a subsequent soldr cargo build may collide with the rust-plan restore path.

Exit code: 0 when every probe reports ok: true, 1 otherwise.

The --json payload (schema_version: 1) is the stable contract for setup-soldr#133 and similar consumers:

{
  "schema_version": 1,
  "host": {"os": "linux", "arch": "x86_64", "libc": "gnu"},
  "probes": [
    {
      "name": "musl-cc",
      "ok": true,
      "details": {
        "musl_cc": "/usr/bin/musl-gcc",
        "tool": "musl-gcc",
        "version": "musl-tools 1.2.5-1"
      }
    },
    {
      "name": "shared-target-warning",
      "ok": true,
      "details": {
        "target_dir": "/workspace/target",
        "fingerprint_dirs_found": 0,
        "would_warn": false
      }
    }
  ],
  "elapsed_ms": 12
}

Notes on the schema:

  • host.os is std::env::consts::OS (linux / windows / macos).
  • host.arch is std::env::consts::ARCH (x86_64 / aarch64 / …).
  • host.libc is gnu on Linux, msvc on Windows, darwin on macOS (CLAUDE.md mandates MSVC-default on Windows).
  • The probes array preserves declaration order: musl-cc first, shared-target-warning second. Future probes will be appended.
  • Each probe's details object is probe-specific. Consumers must switch on probe.name before reading nested keys.
  • probe.ok = false is reserved for future probes; today both probes always set ok: true because a missing musl-cc or a clean target/ is informational rather than blocking.

[soldr.plugins]

Top-level [soldr] section of rust-toolchain.toml. Currently surfaces a plugins table keyed by cargo crate name. Each value is either a bare version requirement or a detailed table.

[toolchain]
channel = "1.95.0"

[soldr.plugins]
cargo-nextest = "0.9"
cargo-zigbuild = { version = "0.18", locked = true }
cargo-deny    = "*"          # any version — `--version` is omitted
cargo-llvm-cov = { version = "0.6", features = ["no_cfg_coverage"], no_default_features = true }

Field semantics for the detailed shape:

FieldTypeMaps to
versionstring--version <value>. "*" or unset omits the flag entirely.
lockedbool--locked when true.
featureslist of string--features <a,b,c> when non-empty.
no_default_featuresbool--no-default-features when true.

Installs are dispatched to the cargo binary resolved by soldr's toolchain probe (resolve_toolchain_binary("cargo")) and invoked directly — not through the rustc wrapper. This routes installs into soldr-managed $CARGO_HOME while letting the active cargo honor rust-toolchain.toml at exec time, so no explicit channel is threaded through. cargo install is idempotent by design, so a second prepare after a successful one is a no-op for plugins.

soldr doctor

Diagnose drift between rust-toolchain.toml and the rustup state currently installed for the declared channel. Read-only — doctor never invokes rustup toolchain install, rustup component add, or rustup target add. Exits 1 when drift is detected, 0 otherwise. When no rust-toolchain.toml exists in the current working directory the command exits 0 and reports that no manifest was found.

soldr doctor
soldr doctor --json
soldr doctor --refresh-defender-probe       # Windows: force fresh probe of the cache dir

Flags:

  • --json — emit the stable machine-facing JSON form.
  • --refresh-defender-probe — ignore the cached Defender real-time-scan probe result and run a fresh probe of the soldr cache directory. No-op outside Windows. Issue #357.

Example human output (drift detected):

manifest: /home/user/project/rust-toolchain.toml
toolchain: 1.95.0
  status: installed

components (declared 2):
  rustfmt   installed
  clippy    MISSING

targets (declared 1):
  x86_64-unknown-linux-musl   installed

result: drift detected (1 missing component)
hint: run `soldr toolchain prepare` to bring installed state in sync with manifest

Example JSON output (schema_version: 1):

{
  "schema_version": 1,
  "command": "doctor",
  "manifest_path": "/home/user/project/rust-toolchain.toml",
  "toolchain": {"channel": "1.95.0", "installed": true},
  "components": [
    {"name": "rustfmt", "installed": true},
    {"name": "clippy", "installed": false}
  ],
  "targets": [
    {"triple": "x86_64-unknown-linux-musl", "installed": true}
  ],
  "drift": true,
  "missing_components": ["clippy"],
  "missing_targets": [],
  "timeouts": [
    {"name": "compile reply", "env_var": "SOLDR_COMPILE_REPLY_TIMEOUT_SECS", "default_secs": 1800, "effective_secs": 1800, "source": "default", "override_ignored": false}
  ],
  "fallbacks": {"total": 0, "recent": []},
  "soldr_debug_info": {
    "binary_path": "C:\\Users\\user\\.soldr\\bin\\soldr.exe",
    "debug_info_found": 1,
    "debug_info_expected": 1,
    "symbol_path": "C:\\Users\\user\\.soldr\\bin"
  },
  "defender_probe": {
    "verdict": "scanned",
    "probed_path": "C:\\Users\\user\\.soldr\\cache",
    "median_write_ms": 412,
    "probed_at_unix": 1715000000,
    "refreshed_this_run": false
  }
}

timeouts (soldr#1838 Phase 3). One row per daemon/toolchain timeout that has an env override, so a consumer can see the effective value and where it came from without re-deriving it: name, env_var, default_secs, effective_secs, source ("default", "override", or "default (override ignored: unparseable)"), and override_ignoredtrue when a variable was set but did not take effect (the soldr#1837 malformed-value-falls-back-to-default rule), broken out so CI can assert on it without string-matching source.

fallbacks (soldr#1838 Phase 4). Historical compile-daemon fallback telemetry retained as { total, recent[] } for JSON compatibility. The mandatory broker route does not append new records.

Defender probe (issue #357, Windows-only). soldr doctor runs a throttled empirical scan probe to detect whether the soldr cache directory is being inspected by Windows Defender's real-time protection. The probe writes a 1 MiB .dll file into the cache directory, times the syscall, and classifies the median across 3 repeats: ≥80 ms means scanned, below means excluded (or running on a trusted Dev Drive). State persists at ~/.soldr/defender-probe.json and refreshes only when the cache path changes, the soldr version changes, the cached state is older than 7 days, or --refresh-defender-probe is passed. The field is omitted from output on non-Windows platforms.

Component installed-state matching is target-qualified: rustup's component list --installed returns names like clippy-x86_64-unknown-linux-gnu, so a declared clippy matches any installed entry that either equals clippy exactly or starts with clippy-.

soldr optimize

Apply platform-specific hot-cache optimizations to remove build-time penalties caused by antivirus / real-time scanners. On Windows this adds soldr-owned cache paths (and optionally the current project's target/) to Windows Defender's exclusion list. On macOS and Linux it is a no-op with a clear message — soldr's workloads do not require exclusions there.

soldr optimize                                # default scope: all
soldr optimize --scope global                 # only ~/.soldr/* paths
soldr optimize --scope project                # only <workspace>/target
soldr optimize --scope all                    # both
soldr optimize --dry-run --json               # plan-only output
soldr optimize --undo --scope global          # reverse soldr-added exclusions

Flags:

  • --scope {global|project|all} — what to optimize. Defaults to all.
  • --undo — reverse exclusions previously added by soldr. Only paths tracked in ~/.soldr/managed-defender-exclusions.json are removed; entries the user added by hand are never touched.
  • --dry-run — print the plan and exit without invoking PowerShell.
  • --json — emit the stable machine-facing JSON form.
  • --manifest-path <PATH> — explicit Cargo.toml for the project scope. When unset, soldr walks up from the current directory.

Platform behavior:

PlatformBehavior
Windows 10Add Defender exclusions. UAC self-relaunch when not admin.
Windows 11 pre-22H2Same as Windows 10, plus an info note about Dev Drive in 22H2.
Windows 11 22H2+Same as Windows 10, plus a Dev Drive suggestion when fsutil devdrv is supported.
Windows + Defender disabledPrints "Defender not active; no exclusions needed." Exits 0.
Windows + no PowerShell on PATHHard error; exits non-zero.
macOS / LinuxNo-op; exits 0 with a message.

The global scope covers:

  • ~/.soldr/cache
  • ~/.soldr/bench
  • ~/.soldr/runtime
  • ~/.soldr/state.redb
  • Soldr's embedded-zccache state and session/report owner directory (~/.soldr/cache/zccache). When ZCCACHE_CACHE_DIR is set, that caller-selected zccache cache root is excluded explicitly.

The project scope covers <workspace_root>/target/, where <workspace_root> is the nearest ancestor of the current directory containing a Cargo.toml. Without --manifest-path and without a matching ancestor, the command exits with no Rust project detected.

UAC self-relaunch:

When the current process is not elevated, soldr re-launches itself via Start-Process powershell -Verb RunAs --as-elevated-helper. The elevated child writes its JSON status to a temp file referenced by the SOLDR_OPTIMIZE_HELPER_OUTPUT env var; the parent reads, prints, and deletes it. The same exit code is propagated. If UAC is denied or unavailable, soldr prints instructions for running the helper from an elevated PowerShell and exits non-zero — soldr will never silently elevate via tokens or scheduled tasks.

CI auto-skip:

soldr optimize detects CI via GITHUB_ACTIONS, CI, BUILDKITE, CIRCLECI, TRAVIS (truthy values), or a non-empty JENKINS_URL, and exits 0 with a clear message. Ephemeral runners discard Defender state at job end, so adding exclusions is a no-op there.

Example JSON output (schema_version: 1):

{
  "schema_version": 1,
  "command": "optimize",
  "platform": "Windows10",
  "scope": "all",
  "undo": false,
  "dry_run": true,
  "defender_present": true,
  "defender_active": true,
  "actions": [
    {
      "path": "C:\\Users\\you\\.soldr\\cache",
      "action": "add",
      "scope": "global",
      "status": "planned"
    }
  ],
  "note": "dry-run: would add 6 paths to Defender exclusions"
}

Suppressing the pre-build warning:

When the cargo front door detects that the soldr cache directory is being scanned by Defender, it emits a one-line warning to stderr suggesting soldr optimize global. Set SOLDR_QUIET_DEFENDER=1 to silence it. The warning is also suppressed automatically in CI.

soldr gc

Review reclaimable Cargo target/ directories tracked in ~/.soldr/state.redb. Implemented by issue #234 and made safe-by-default by issue #289. The wrapper-mode hot path upserts each invocation's resolved workspace target/ path with the current timestamp; soldr gc walks the registry, drops missing rows, applies safety guards, and prints an info summary without prompting or deleting anything.

soldr gc                                      # info summary only
soldr gc --json                               # machine-readable summary
soldr gc --older-than 30d --larger-than 1GB   # summary with tunable filters
soldr gc purge                                # interactive deletion flow
soldr gc purge --all                          # delete every eligible candidate
soldr gc purge --older-than 30d --larger-than 1GB
soldr gc purge --json                         # machine-readable purge report
soldr gc list --kind cargo_target_incremental # list one taxonomy kind
soldr gc purge --target-incremental --all      # delete target/<profile>/incremental/
soldr gc purge --build-scripts --all           # delete build-script-build binaries
soldr gc purge --doc --all                     # delete target/doc/
soldr gc purge --subcommand-caches --all       # delete target/{criterion,nextest,...}/
soldr gc purge --registry-src --all            # delete extracted registry sources
soldr gc purge --git-checkouts --all           # delete git checkout worktrees

Defaults:

  • --older-than 10d
  • --larger-than 256M

Compatibility:

  • soldr gc --dry-run is accepted as a temporary alias for soldr gc
  • soldr gc --all is rejected with a pointer to soldr gc purge --all

Safety guards (from docs/TARGET_GC_PROPOSAL.md):

  • skip a candidate whose workspace Cargo.lock was modified within the staleness window
  • skip a candidate whose target/.cargo-lock exists (active build)
  • only consider paths under gc.allowlist_roots (default: ~/dev)

The summary includes the registry path, eligible candidate count, total reclaimable size, skipped/dropped counts, and the largest eligible target directories with size and last-used age.

gc list --json uses the issue #323 taxonomy. Derived kinds can be purged through explicit opt-in flags or --kind; primary kinds are report-only and gc purge --kind <primary> is rejected before deletion.

Derived purge kinds:

  • cargo_target (default gc purge behavior)
  • cargo_target_incremental
  • cargo_target_build_script_binaries
  • cargo_target_doc
  • cargo_target_subcommand_caches
  • cargo_registry_src
  • cargo_git_checkouts

Report-only primary kinds:

  • cargo_registry_cache
  • cargo_git_db
  • cargo_installed_binaries
  • rustup_toolchain

Eviction order and in_worktree (issue #2134). cargo_target entries carry in_worktree, and age_seconds is the same effective age eviction uses — the more recent of soldr's registry stamp and the directory's mtime, because the stamp goes stale while a directory stays hot (a repo built with bare cargo never updates it). Eviction orders worktree → coldest → size, so those two fields together explain why any given target was chosen. Size is the last key, not the first: it only breaks ties between equally cold targets.

in_worktree is omitted for kinds that have no owning workspace.

cargo_registry_src last-used provenance (issue #349). When soldr gc list --kind cargo_registry_src (or unfiltered gc list) walks $CARGO_HOME/registry/src/..., each entry's last_used_unix field is preferentially derived from cargo's own $CARGO_HOME/.global-cache SQLite tracker — the same data cargo's unstable -Zgc uses to evict least-recently-used crate sources. When the tracker is missing, locked, schema-drifted, or has no row for a particular crate, soldr falls back to the directory's filesystem mtime. The provenance is exposed as last_used_source on each entry: "global_cache" or "fs_mtime". The field is omitted from cargo_target entries (no comparable tracker exists).

Configure additional allowlist roots via ~/.soldr/config.toml:

[gc]
allowlist_roots = ["~/dev", "/work/repos"]

During build-like soldr cargo ... invocations, soldr checks free space on the relevant target/current filesystem before spawning Cargo. When less than 2 GB is available, it emits a yellow stderr warning that recommends soldr gc. Disk-space detection failures are ignored so they never fail the build.

soldr gc cargo (issue #323)

Shells out to nightly cargo's unstable -Zgc clean gc against $CARGO_HOME. The CLI prepends rustup run <toolchain> so the workspace rust-toolchain.toml is bypassed — cargo here always means "the cargo shipped with the requested toolchain".

soldr gc cargo                                  # nightly, conservative defaults
soldr gc cargo --dry-run --json                 # plan + machine-readable report
soldr gc cargo --max-src-age 7days --max-crate-age 14days
soldr gc cargo --toolchain nightly-2026-01-01   # pin the nightly snapshot

Toolchain resolution: --toolchain flag → $SOLDR_GC_CARGO_TOOLCHAINnightly default. Missing toolchain is a hard error from explicit gc cargo; the gc sweep orchestrator downgrades it to a skip so CI runners without nightly still get the soldr target purge stage.

JSON shape (schema_version: 1):

{
  "schema_version": 1,
  "command": "gc",
  "mode": "cargo",
  "toolchain": "nightly",
  "exit_code": 0,
  "dry_run": false,
  "args": ["-Zgc", "clean", "gc", "--max-src-age=7days"],
  "stdout_bytes": 612,
  "stderr_bytes": 0,
  "skipped": false,
  "skipped_reason": null
}

soldr gc locations (issue #323)

Read-only enumeration of every cache directory soldr cares about. No deletion, no last-used derivation. Walks $CARGO_HOME/{registry/{src, cache,index},git/{db,checkouts},.global-cache}, $RUSTUP_HOME/{toolchains,update-hashes}, ~/.soldr/cache/, and ~/.soldr/state.redb. Missing paths are reported with exists: false and zero size.

soldr gc locations
soldr gc locations --json

Per-entry JSON: {kind, path, exists, size_bytes, size_human, file_count, owner, purge_safety}. owner is cargo / rustup / soldr; purge_safety is regenerable (safe to delete; cargo will refetch) or user_action (the user installed it on purpose — never auto-purge).

soldr gc sweep (issue #323)

Orchestrator that combines gc locations, cargo's clean gc, and the soldr target purge in one shot. Designed to be the user-facing "free me some disk space" command.

soldr gc sweep                            # full pipeline, prompt for each target
soldr gc sweep --all --dry-run --json     # plan everything, delete nothing
soldr gc sweep --no-cargo-gc              # skip cargo (e.g. no nightly available)
soldr gc sweep --aggressive               # second cargo pass with tighter ages

Stages, in order:

  1. gc locations (always — read-only).
  2. cargo clean gc with conservative defaults (unless --no-cargo-gc or nightly is missing — auto-skipped).
  3. soldr's target purge over registered workspaces. Respects --all (no prompt) and the configured gc.allowlist_roots.
  4. --aggressive only: second cargo pass with --max-src-age=7days --max-crate-age=14days --max-git-co-age=7days, each clamped to auto_gc.min_age_secs.

soldr gc target (issue #574)

Cross-repo target/ reclamation: walks a configurable root, finds every workspace with a sibling target/ directory, and reports their sizes (sorted descending). With --purge it deletes them after a single confirmation prompt (or --yes for non-interactive automation).

soldr gc target                                    # report under ~/dev (default)
soldr gc target --root ~/code --max-depth 6        # custom root and walk depth
soldr gc target --dry-run --json                   # machine-readable report
soldr gc target --purge                            # interactive deletion
soldr gc target --purge --yes                      # non-interactive deletion
soldr gc target --purge --yes --json               # CI-friendly purge + JSON

Options:

  • --root <PATH> — walk root. Falls back to $SOLDR_GC_TARGET_ROOT then ~/dev.
  • --max-depth <N> — maximum directory depth for the workspace scan (default 4). Hidden directories (leading .) and any directory named target are skipped.
  • --dry-run — report-only (default).
  • --purge — delete every reported target/ directory.
  • --yes — skip the y/n confirmation prompt. Required when stdin is not a terminal (CI).
  • --json — emit the stable machine-facing payload (schema_version: 1).

JSON shape (stable):

{
  "schema_version": 1,
  "command": "gc target",
  "mode": "report" | "purge",
  "root": "/home/me/dev",
  "max_depth": 4,
  "entry_count": 0,
  "total_bytes": 0,
  "total_human": "0 B",
  "entries": [
    {
      "workspace_root": "/home/me/dev/foo",
      "target_dir": "/home/me/dev/foo/target",
      "size_bytes": 0,
      "size_human": "0 B",
      "file_count": 0,
      "last_modified_ms": 0
    }
  ],
  "purged_count": 0,
  "failed_count": 0,
  "purged_bytes": 0,
  "purged_human": "0 B",
  "failures": []
}

The walker is independent of the per-repo gc list taxonomy above — it scans real filesystem trees rather than the soldr registry, so it finds workspaces that have never gone through soldr cargo ....

Host-volume disk watchdog (issue #574)

Before every soldr cargo ... build, soldr probes free space on the build volume (the disk hosting the project's target/ dir, or CWD if target/ doesn't exist yet) and either warns or aborts:

  • Above SOLDR_TARGET_WARN_FREE_GB (default 10 GiB) — silent.
  • Between warn and SOLDR_TARGET_BLOCK_FREE_GB (default 5 GiB) — one-line stderr warning pointing at soldr gc target.
  • Below the block threshold — cargo is refused with a clear error directing the user at soldr gc target --purge.

Set SOLDR_TARGET_AUTO_PRUNE_ENABLED=0 to disable the watchdog entirely. The watchdog is layered on top of the legacy 2 GiB low-disk advisory (issue #289) and never replaces it.

Daemon-owned cache maintenance (issues #1762–#1764)

Each soldr-daemon owns exactly the one root selected by SoldrPaths: production .soldr, development .soldr-dev, or an explicit SOLDR_CACHE_DIR. Embedded zccache remains below that root. Standalone .zccache is owned only by a standalone zccache daemon; Soldr never discovers, migrates, links into, or deletes it.

The daemon stays resident by default, performs a lightweight pressure check every five minutes, and performs a full age pass every 24 hours. The last completed full pass and the latest structured report are stored under <root>/cache/soldr-daemon/, so an overdue restart catches up immediately. Nonzero soldr daemon start --idle-timeout <seconds> remains an explicit auto-exit option. Soldr does not install Task Scheduler jobs, systemd timers, launchd agents, or any other OS scheduler.

The embedded artifact budget defaults to 5% of filesystem capacity, clamped to 40–200 GiB and reduced when needed to preserve recovery space. At 85% fill it evicts LRU artifacts older than four days toward 70%; at 100% fill or critically low free space it evicts regardless of age toward 80% plus recovery headroom. A daily pass expires artifacts older than 30 days even below budget. Physical, hardlink-deduplicated allocated bytes are used. Set exactly one of ZCCACHE_CACHE_SIZE_BYTES or ZCCACHE_CACHE_SIZE_PERCENT to override the dynamic budget.

The same coordinated pass bounds cook artifacts, trash buckets, registered workspace targets, PEP517 targets and wheels, daemon events, stale embedded generations, and cache/zccache/history. Build history defaults to four days and 1 GiB per root; active publishers survive, while abandoned unfinished rows do not block retention forever. Expired database records retain their useful metrics with archived paths marked unavailable. The first pass after the zccache#1149 integration removes completed pre-redaction histories. The daily 30-day absolute bound overrides cook's keep_per_origin protection, so every abandoned cook origin eventually expires.

Builds hold a shared root-maintenance lease from before BuildSessionStart through sanitized archive publication. Maintenance holds the exclusive side for the complete pass, so a build cannot begin in the probe/delete gap. Daemon shutdown lets an already-started pass finish. Soldr embeds zccache with host-owned maintenance, so zccache does not also start its standalone periodic maintenance scheduler inside the same process.

soldr status, soldr cache, and soldr doctor expose the owning root, identity, embedded root, effective budget policy, measured usage/fill/free space, last attempt/success, pressure tier, reclaimed bytes/items, and component errors. For an orphaned root, the only manual surface is explicit:

soldr gc maintain --root /absolute/path/to/the/root [--json]

It refuses relative paths, symlinks/junctions/reparse points, any version-blind daemon endpoint occupant, and any root whose daemon-ownership lock is busy. It holds that ownership lock for the full pass and never searches for sibling roots. Deferred or partially failed manual passes print their structured status but exit nonzero, so emergency cleanup scripts cannot mistake a no-op for success.

Invocation fallback and Cargo-volume GC (issue #323)

The daemon is primary for Soldr-owned state. The cargo front door retains its post-build detached fallback for Cargo-owned global caches and registered workspace targets when free space on any soldr-relevant volume drops below the configured trigger. Per volume (Windows: drive letter; Unix: device id):

  1. tier 1 — cargo clean gc with conservative ages,
  2. tier 2 — soldr target purge with larger_than = 256M and older_than = max(1h, auto_gc.min_age_secs),
  3. tier 3 — cargo clean gc with --max-src-age=7d --max-crate-age=14d --max-git-co-age=7d,
  4. stop. Anything more aggressive requires explicit soldr gc sweep --aggressive.

Configure in ~/.soldr/config.toml:

[auto_gc]
enabled = true          # opt-out; on by default
trigger_free_gb = 20    # start GC when free space < this
target_free_gb = 30     # stop GC when free space >= this
min_age_secs = 3600     # never touch anything modified within this window

Fallback behavior:

  • Background-only. A detached soldr gc auto-sweep process survives the wrapper exit, so the build never blocks waiting for Cargo-volume GC.
  • Throttled to ~once per 5 minutes via ~/.soldr/.auto_gc_marker.
  • Set SOLDR_AUTO_GC_DISABLED=1 to disable for a single invocation without editing config.
  • Volumes that already have plenty of free space are skipped, even when another volume on the same machine is below the trigger.
  • Every check that crosses the trigger writes a structured log line to ~/.soldr/logs/auto-gc.log. The file rotates to auto-gc.log.old once it exceeds 10 MiB.
  • Cargo's .package-cache mutex serializes auto-GC against any in-flight cargo build, so concurrent builds and GC don't race.

Per-Build XML Log (issue #1790)

Every managed soldr cargo ... build — success or failure, cache enabled or disabled — writes one self-contained XML file:

~/.soldr/logs/builds/<timestamp>-<sanitized-cwd>.xml

The directory is flat and the filename's compact UTC timestamp prefix (YYYYMMDDTHHMMSSZ) sorts lexically = chronologically, so ls / Get-ChildItem in that directory is already newest-last. The cwd suffix is a lowercased, filename-safe slug (non [a-z0-9] bytes collapsed to -, capped at 80 chars) so logs from different checkouts of the same repo (or different repos entirely) don't collide.

The write is always-on and best-effort: a failure to write the log never fails the build — soldr prints soldr warning: failed to write build log: <err> to stderr and continues.

The format was originally JSON and was converted to XML by owner decision: the log is dominated by repeated attribute-blocks inside group nodes (one record per compiled crate, with the same handful of build-settings fields stamped on every group), which XML's attribute-on-element shape expresses more naturally than JSON's per-item object repetition. The emitter is hand-rolled (no new dependency).

Schema (schema_version: 1)

<?xml version="1.0" encoding="UTF-8"?>
<build schema_version="1" soldr_version="0.8.21" cwd="C:\Users\niteris\dev\soldr2" started_at_ms="0" ended_at_ms="0" duration_ms="0" exit_code="0">
  <args>
    <arg>cargo</arg>
    <arg>build</arg>
    <arg>--release</arg>
  </args>
  <steps>
    <download wall_ms="0" cpu_ms="0">
      <item name="cargo-nextest" source="github-release" started_at_ms="0" duration_ms="0"/>
    </download>
    <compile wall_ms="0" cpu_ms="0" target="x86_64-pc-windows-msvc" profile="release" debug="false" opt_level="3" lto="off">
      <item crate="foo" duration_ms="0" cache="hit"/>
    </compile>
    <link wall_ms="0" cpu_ms="0" derived="true" target="x86_64-pc-windows-msvc" profile="release" debug="false" opt_level="3" lto="off">
      <item crate="foo" duration_ms="0"/>
    </link>
  </steps>
  <totals wall_ms="0" cpu_ms="0" crate_count="0" cache_hits="0" cache_misses="0"/>
</build>
  • <build> header attributescwd, started_at_ms / ended_at_ms / duration_ms, exit_code. The full invoked argv is the child <args> element (one <arg> per token) rather than an attribute, since argv is variable-length.
  • build settings on <compile> and <link> — there is no separate <settings> element. Instead, the derived [profile.*] metadata (target, profile, debug, opt_level, lto) — read from the invoked flags plus the target Cargo.toml's [profile.<name>] table — is stamped as attributes on BOTH the <compile> and <link> group nodes, so each group is self-describing on its own. RUSTFLAGS / CARGO_PROFILE_* environment overrides are not accounted for in v1.
  • <steps><download> — any tool/artifact fetches (e.g. cargo-nextest, zccache) that happened during the build, one self-closing <item> per fetch with name, source ("github-release", "crates-io", "catalogue", etc.), started_at_ms, and duration_ms.
  • <steps><compile> — one <item> per crate compiled during the session (crate, duration_ms, cache: "hit" / "miss" / "unknown"), sourced from the daemon's build-history DB and cross-referenced against the zccache compile journal for cache outcome.
  • <steps><link> — v1 has no independently-measured link phase, so this section is derived: the compile event with the latest end timestamp is treated as the linking crate and echoed here with derived="true". That crate's entry therefore also appears in <compile> — this is intentional, not a double-count bug.
  • wall_ms vs cpu_mswall_ms is calendar time spanning the earliest start to the latest end within a group. cpu_ms is aggregate busy time summed across (possibly parallel) units — e.g. the sum of every compile's duration — not OS-reported CPU time. On a build with N-way parallelism, a group's cpu_ms can comfortably exceed its wall_ms. Note: <totals> cpu_ms excludes the derived <link> group's cpu_ms (it re-labels a slice already counted in <compile>, so adding it again would double-count).
  • <totals> — build-wide wall_ms / cpu_ms rollup, crate_count, and cache_hits / cache_misses (falls back to the zccache session-stats summary when no per-crate cache outcome could be resolved from the compile journal).
  • Empty groups (e.g. no fetches happened) render as a self-closing element with only the group's own attributes, e.g. <download wall_ms="0" cpu_ms="0"/>.
  • All attribute values and <arg> text content are XML-escaped (&, <, >, ", ', and stray control characters below 0x20).

Retention

The same detached soldr gc auto-sweep process that runs the Cargo-volume GC pass above (throttled to ~once per 5 minutes) also prunes ~/.soldr/logs/builds/ down to the newest 100 files, sorted by filename (so oldest-timestamp files are removed first). This matches both *.xml files and any legacy *.json files left over from interim builds before the JSON->XML conversion, so old logs in either format still get GC'd. No separate throttle or opt-out exists for this prune — it rides the existing auto-GC sweep unconditionally.


Structured JSON Output

The supported JSON protocol currently exists on:

  • soldr status --json
  • soldr cache --json
  • soldr cache prune-target <path> --json
  • soldr version --json

The JSON response always includes:

  • schema_version
  • command

Current schema version:

  • schema_version: 1

Compatibility rules for schema version 1:

  • existing fields keep their current meaning
  • fields may be added in later releases without changing schema_version
  • removing a field, renaming a field, or changing the meaning/type of an existing field requires a new schema version
  • human-readable stdout for commands without --json is not covered by this compatibility promise

Example:

{
  "schema_version": 1,
  "command": "version",
  "soldr_version": "0.7.4"
}

Help Surface

Usage:
  soldr <COMMAND>
  soldr <TOOL>[@version] [args...]

Commands:
  cargo    Run Cargo through soldr
  status   Show cache status and tool info
  clean    Clear caches
  config   Show or set configuration
  cache    Inspect the compilation cache
  version  Show version
  gc       Review reclaimable Cargo target/ directories; use gc purge to delete

Environment Variables

VariablePurposeDefault
RUSTC_WRAPPERInternal build hook used by soldr cargo ...unset
SOLDR_CACHE_ENABLEDInternal toggle propagated from soldr cargo ... into wrapper mode1
SOLDR_RUSTC_WRAPPERReplace Soldr's normal embedded-zccache route with another wrapper binary, or disable rustc wrapper injection with none / empty while leaving other Soldr front-door behavior intactunset
SOLDR_REAL_CARGO, SOLDR_REAL_RUSTC, ...Internal real-tool path overrides used by setup-soldr PATH shims to avoid recursive tool lookupunset
SOLDR_ZCCACHE_BINLegacy compatibility variable; it does not replace the embedded service on the normal soldr cargo ... path. Use SOLDR_RUSTC_WRAPPER=/path/to/zccache for an intentional external-wrapper experiment.unset
SOLDR_ZCCACHE_LOCAL_DIRLegacy compatibility variable from the removed downloaded-zccache flow; ignored by the normal embedded path. Develop against _vender/zccache and rebuild Soldr instead.unset
SOLDR_CACHE_DIROverride the exact product root owned by this soldr daemon. On Windows, keep this path short — see the note below.official builds: ~/.soldr; development builds: ~/.soldr-dev
ZCCACHE_CACHE_SIZE_BYTESExact embedded artifact budget in bytes. Mutually exclusive with ZCCACHE_CACHE_SIZE_PERCENT.dynamic 5%, clamped 40–200 GiB
ZCCACHE_CACHE_SIZE_PERCENTEmbedded artifact budget as an integer percentage from 1 through 100. Mutually exclusive with ZCCACHE_CACHE_SIZE_BYTES.dynamic 5%, clamped 40–200 GiB
SOLDR_CACHE_LIFECYCLEEmbedded-cache durability policy for soldr cargo .... job leaves normal background persistence to the long-lived Soldr daemon. command requests an embedded cache flush before the command exits; it does not stop the root-owning daemon.job
SOLDR_CACHE_SHUTDOWN_TIMEOUT_SECSPositive compatibility timeout accepted with command-lifetime mode. Embedded mode uses it to select the flush-on-command path; there is no separate zccache daemon to stop.30
SOLDR_TRUST_INHERITED_ENVAdvanced escape hatch for CI/action workflows that intentionally inject soldr/zccache workspace-pinned env into soldr cargo .... Truthy values are equivalent to --trust-inherited-soldr-env; unset/default means soldr cargo ... derives a fresh soldr workspace context from the current cwd/manifest while preserving normal OS, Cargo, Rust, proxy, cert, and CI env.unset
SOLDR_RELOCATED_EXEInternal recursion guard set after Windows self-relocationunset
SOLDR_ORIGINAL_EXEInternal path to the original executable when Windows self-relocation is activeunset
SOLDR_ZCCACHE_SESSION_DIRInternal session/report directory passed from soldr cargo ... into wrapper modeunset
SOLDR_ZCCACHE_PRIVATEOpt-in private auxiliary session/rust-plan root. When truthy (1/true/yes/on), soldr cargo ... routes that state to <cwd>/.zccache and soldr save/soldr load default --cache-dir to the same path when omitted. It does not relocate the compiler-artifact service embedded in soldr-daemon; use SOLDR_CACHE_DIR for a fully isolated embedded compiler store. Explicit ZCCACHE_CACHE_DIR (front door) or --cache-dir (save/load) always wins.unset
SOLDR_SAVE_PROFILEDefault payload profile for soldr save when --ci / --minimal is not passed. Values: full/default/complete for historical all-files archives, or ci/minimal for the CI/minimal profile that excludes runtime-only files, zccache runtime binaries, and reports excluded_files / excluded_bytes. CLI flags win over the env var.full
ZCCACHE_CACHE_DIRAuxiliary zccache front-door/session, rust-plan, and direct-rustfmt cache-root override. It does not relocate the compiler service embedded in soldr-daemon; use SOLDR_CACHE_DIR for that. soldr cargo ... ignores inherited values by default so stale workspace state from setup/action wrappers cannot bleed across projects; pass --trust-inherited-soldr-env or set SOLDR_TRUST_INHERITED_ENV=1 only when intentionally injecting this state.unset
ZCCACHE_SESSION_IDPer-build zccache session identifier set by soldrunset
SOLDR_NATIVE_CACHENative C/C++ compiler cache toggle. Falsy values (0/false/no/off) disable only cc-rs CC/CXX wrapper injection, leaving rustc-side zccache enabled. Useful when a target cross compiler, such as the managed MinGW gcc.exe / g++.exe path, must run directly while Rust compilation still uses the cache.unset (on)
SOLDR_CARGO_WAIT_TIMEOUT_SECSOpt-in wall-clock watchdog for the Cargo child. Normal soldr Cargo ... invocations have no Soldr-imposed wall-clock deadline and may run for hours. A timeout terminates the process tree, records abort diagnostics, and returns failure without changing compile topology.unset (no deadline)
SOLDR_COMPILE_REPLY_TIMEOUT_SECSOverrides the compile-dispatch reply timeout. Default is 30 min so a legitimate slow release compile is never cut off; set a small value (e.g. 30) to fail fast instead of waiting out the backstop if the daemon stops responding. 0/empty/unparseable falls back to the default.1800
ZCCACHE_PATH_REMAPzccache path-remap mode. soldr seeds auto on the child cargo for managed-zccache builds so multiple git worktrees of the same repo share cache hits (issue #352, Tier L1.x). Caller-supplied values are preserved. Works for non-git checkouts too: since zccache#353, ZCCACHE_PATH_REMAP=auto with no .git/ ancestor falls back to the cwd as the remap root and still injects --remap-path-prefix=<cwd>=., so tarball/zip/git-archive checkouts produce path-independent artifacts and share hits (the .git/ walk is only how the preferred worktree root is discovered).unset (soldr injects auto)
SOLDR_PATH_REMAPEscape hatch for the default ZCCACHE_PATH_REMAP=auto injection. off (case-insensitive) suppresses the injection; any other value, or unset, keeps the default behavior.unset (auto)
SOLDR_TIMESTAMP_LINESPrefix each relayed output line with elapsed seconds since soldr start ( 12.34), color-preserving, so per-line cost is visible in a log. A single # t0=<epoch-seconds> header anchors absolute time. Applies to the cargo front door and the PEP 517 build backend; both stamp piped/non-TTY output by default and leave an interactive terminal unstamped. 1/true/on forces stamping on (e.g. on a TTY); 0/false/off forces it off. Diagnostic capture, cargo-JSON parsing, and the archived build log always see the unstamped bytes.unset (on for non-TTY, off for TTY)
SCCACHE_DIRsccache cache-root override soldr injects when SOLDR_RUSTC_WRAPPER=sccache and the caller has not set it themselves~/.soldr/cache/sccache
SOLDR_PEP517_STABLE_TARGET_DIRPEP 517 backend only: set to 0 / false / no / off to skip pinning CARGO_TARGET_DIR to the content-identified <effective-soldr-root>/cargo-target/pep517/<project-id> namespace for isolated builds (see PEP 517 Build Backend). A caller-provided CARGO_TARGET_DIR always wins regardless.unset (pin enabled)
SOLDR_PEP517_PROJECT_IDRead-only diagnostic/cache identity exported by the PEP 517 backend. It identifies manifests, lockfile, toolchain/configuration, maturin settings, and build-policy environment; source freshness remains Cargo's responsibility.content-derived
SOLDR_PEP517_STATSPEP 517 wheel/editable build diagnostics: off (also 0 / false) suppresses stderr statistics; full emits the cache-session JSON after the default one-line summary; any other nonempty value selects the one-line summary. When unset, verbose frontends detected through PIP_VERBOSE or UV_VERBOSE select full.concise summary
SOLDR_PEP517_WHEEL_CACHEPEP 517 wheel/editable hooks: reuse the last successful wheel when the metadata fingerprint of sources, staged artifacts, prepared metadata, and build settings matches. off (also 0 / false / none) disables reuse.unset (on)
SOLDR_PEP517_LINKERPEP 517 backend only: auto (default) tries the fastest supported linker and caches a verified platform-linker fallback after a linker-availability failure; none / default / off disables the automatic attempt. An explicit SOLDR_LINKER=fast remains non-fallbacking.auto
SOLDR_LOGLog levelwarn
SOLDR_OFFLINEDisable network access for tool fetchesfalse
SOLDR_RUST_PLAN_MEMODefault-on: memoize the target-cache preparation subprocess outputs (cargo metadata --format-version 1, rustc -Vv, cargo --version) in a versioned protobuf memo under <zccache cache dir>/plans/, keyed by a content-identity hash over the workspace manifests, Cargo.lock, hierarchical .cargo/config*, metadata passthrough args, toolchain binary identity (path + size + mtime), rust-toolchain pins, rustup settings.toml, and the steering env vars (issue #1540). Any key mismatch, decode error, or discovery error falls back to the authoritative subprocesses. Set to a falsy value (0 / false / no / off) to disable.unset (on)
SOLDR_FETCH_OVERLAPKill switch for the soldr build --target <T> dependency-prefetch overlap (issue #1543). By default the blessed build spawns a best-effort cargo fetch --target <T> concurrently with catalogue/SDK preparation so fresh-build time approaches max(fetch, prepare) instead of their sum; the prefetch is joined before the main cargo build spawns and any prefetch failure is logged and ignored. The overlap is automatically skipped for --offline / --frozen builds, truthy CARGO_NET_OFFLINE, or when no Cargo.lock exists. Set to 0 / false / no / off (case-insensitive) to disable.unset (on)
SOLDR_RUST_PLAN_SKIP_WARM_RESTOREDefault-on: skip rust-plan restore when target/ is already warm from a prior step in the same GitHub Actions job + attempt (issue #229). Set to a falsy value (0 / false / no / off) to opt out.unset (on)
SOLDR_DYLINT_PREPARE_TTL_SECSFreshness window (seconds) for the dylint prepared-toolchain marker under <soldr root>/dylint/prepared/v1/. A fresh, valid marker lets a warm top-level soldr cargo dylint skip the nightly-map HTTP fetch and the rustup component list / rustc -vV probes entirely. 0 means never trust the marker (every run pays the full cold path).86400 (24 h)
SOLDR_DYLINT_REVERIFYTruthy (1/true) bypasses the dylint prepared-toolchain marker and always re-runs the full catalogue-fetch + rustup verification path. Use after manually mutating the nightly toolchain or when diagnosing identity mismatches.unset
SOLDR_SOURCE_BUILD_CACHEFalsy (0/false/no/off) restores the historical fully-uncached source-build spawn. By default soldr build-from-source and dylint source preparation route compiler work through Soldr so fresh machines can reuse cached objects.unset (cached)
SOLDR_TOOLCHAIN_BIN_CACHEoff (case-insensitive) disables the in-process memo and on-disk cache (<soldr root>/cache/toolchain-bins/v2/<rustup-home+host-scope>/<channel>/<tool>.path) for channel-scoped rustup which binary resolution. The cache saves one rustup which subprocess spawn per tool per nested cargo-dylint re-entry; entries self-invalidate when the cached path no longer exists, and the v2 scope prevents one toolchain home or host architecture from reusing another's path.unset (on)
DYLINT_DRIVER_PATHSoldr sets this on the dylint child process tree to <soldr root>/dylint/drivers (a stable soldr-owned home for cargo-dylint's per-toolchain driver builds) only when the caller has not already set it — an explicit caller value always wins. A fixed path means warm runs reuse the already-built driver and CI caches have a deterministic path to restore.soldr-injected
SOLDR_TARGET_CACHE_MODEMaster toggle for target-cache. thin enables thin-slice mode (zccache saves the rmeta/dep-info skeleton, restores it before cargo build). full enables full-target mode (zccache saves+restores the entire target/ tree). off / false / 0 / no / empty / unset disables the feature entirely — maybe_prepare_rust_artifact_plan short-circuits to Ok(None) and no surrounding code (front-door, GC, registry) does any "free" work on this path. Designed for CI runners that can persist ~/.cache/zccache/ across runs; off by default because a local dev machine has nothing to save it to. CI workflows that want it set this explicitly (typically via setup-soldr). See Target cache for the contract.unset (off)
SOLDR_TARGET_CACHE_TAR_THREADSReader-thread count for the target-cache tar walk in zccache, AND for soldr's own thin-slice manifest walk (issue #272). auto lets each side pick a vCPU-bounded count (capped at 8). 1 disables parallelism (sequential walk). Any positive integer sets an explicit count, clamped to [1, 8] on the soldr side. soldr validates the value at the cargo front door and uses it when statting bundle files for the manifest.v2.json thin-slice manifest; the bulk multi-GB target/ tar walk lives in zccache.unset (auto)
SOLDR_LINKERPick the linker injected for soldr cargo ... builds (issue #285). Accepted values: default (no injection — keep the rust-toolchain default), ld (system linker — also no injection on every supported platform), mold (Linux only; hard error elsewhere), rust-lld (Windows MSVC and Linux/MinGW via clang -fuse-ld=lld; no-op on macOS — see below), fast (mold on Linux when present on PATH, otherwise rust-lld; rust-lld on Windows MSVC; no-op on macOS — see below). The choice resolves to CARGO_TARGET_<TRIPLE>_LINKER and CARGO_TARGET_<TRIPLE>_RUSTFLAGS injected into the spawned cargo process; the active target is the same one Cargo would pick (--target flag, CARGO_BUILD_TARGET, or the host triple). A linker = "..." field in ~/.soldr/config.toml is honored when the env var is unset. On macOS targets rust-lld and fast fall back silently to the platform default linker (issue #509): Apple clang only accepts -fuse-ld=lld when the toolchain wires up an ld64.lld shim, and stock macOS toolchains do not — injecting it would break even cc-rs build-script compilations.unset
SOLDR_QUIET_DEFENDERSuppress the once-per-day pre-build warning emitted by the cargo front door when Defender is actively scanning the soldr cache directory (issue #358). Truthy values silence the warning; the warning is also automatically suppressed in CI environments.unset
SOLDR_OPTIMIZE_HELPER_OUTPUTInternal: set by the parent soldr process when it re-launches itself elevated via --as-elevated-helper. The elevated child writes its JSON status to this path so the parent can read and propagate it.unset
SOLDR_TARGET_WARN_FREE_GBFree-space threshold (in GiB) below which the host-volume disk watchdog (issue #574) emits a one-line stderr warning before soldr cargo ... dispatches the build. The watchdog probes the disk hosting the project's target/ dir, falling back to CWD when target/ doesn't exist yet.10
SOLDR_TARGET_BLOCK_FREE_GBFree-space threshold (in GiB) below which the host-volume disk watchdog (issue #574) refuses to start the build, pointing the user at soldr gc target --purge. Always strictly less than SOLDR_TARGET_WARN_FREE_GB; inverted thresholds collapse to "block wins".5
SOLDR_TARGET_AUTO_PRUNE_ENABLEDMaster toggle for the host-volume disk watchdog (issue #574). Falsy values (0, false, no, off, empty — case-insensitive) disable the watchdog entirely.1
SOLDR_GC_TARGET_ROOTDefault walk root for soldr gc target (issue #574). The --root <PATH> flag always takes precedence.~/dev
SOLDR_TEST_DISK_FREE_BYTESTest seam for the watchdog: when set to a u64 byte count (or error), overrides the real fs2::available_space probe so tests can drive every threshold edge. Internal — never set this in production.unset
SOLDR_PROFILE_EXTRACTEnv-var equivalent of soldr load --profile-extract (issue #575). Any non-empty value other than 0 enables the per-phase profile line on stderr after a load (zstd_decode, tar_parse, extract_total, per-worker job counts, per-file p50/p95/p99). Useful for tuning the parallel-extract worker count against real workloads.unset
SOLDR_LOAD_WORKERSCap on the parallel-extract worker pool used by soldr load (issue #575). Positive integer; wins over the explicit --threads flag. When unset, --threads (or rayon's num_cpus default) is used.unset

Windows: keep SOLDR_CACHE_DIR short. Under the cache root soldr appends a fixed staging suffix of roughly 143 characters (cache/zccache/daemon-state/embedded-v1/v<VERSION>/staging/<session>/<compile>/) before the artifact's own filename. Windows' classic MAX_PATH is 260, so a deep cache root leaves too little budget and the linker fails part-way through a build with, for example:

LINK : fatal error LNK1104: cannot open file '...\staging\...\<crate>-<hash>.dll.lib'

That error names a file inside soldr's own cache rather than anything in your project, which is why soldr calls that out explicitly when it detects it (soldr#1969). The fix is a shorter root — the default ~/.soldr is well within budget; a root nested inside a temp/scratch tree may not be.

RUSTC_WRAPPER=soldr cargo build remains a low-level passthrough path, but it does not create infrastructure. A top-level Soldr front door must first register the exact daemon image/root route and ensure the singleton broker is available. Every cacheable wrapper invocation then uses that broker SESSION route. Broker, daemon, transport, version-skew, retirement, initialization, and protocol failures are hard failures; there is no direct-daemon or direct-compiler fallback.

When SOLDR_RUSTC_WRAPPER is set to a non-empty value such as sccache, soldr puts that binary in the wrapper slot instead of itself, bypassing the embedded path entirely. If it is set to none or an empty string, soldr leaves RUSTC_WRAPPER unset for that build.

Release/LTO musl validation and daemon-failure diagnostics are recorded in DATALAKE_RELEASE_MUSL.md.

On the normal embedded path, soldr cargo ... resolves a fresh Soldr workspace context by default. It preserves normal process environment used by Cargo, Rust, proxies, certificates, CI, and platform SDKs, but ignores inherited Soldr/zccache workspace-pinned state such as ZCCACHE_CACHE_DIR, SOLDR_TARGET_CACHE_*, SOLDR_TARGET_REGISTRY_RECORDED, and SETUP_SOLDR_*. Pass --trust-inherited-soldr-env or set SOLDR_TRUST_INHERITED_ENV=1 only for advanced workflows that intentionally inject those values. Custom wrapper modes leave caller-provided wrapper environment alone; when SOLDR_RUSTC_WRAPPER=sccache and the caller has set SCCACHE_DIR themselves, Soldr forwards their value rather than overriding it.

Compile-capable Soldr front doors register the exact daemon image, root, and route service before starting the build tool. Non-compiling commands do not request a daemon route.

Daemon recovery uses that registered service definition. Wrappers carry only the route service name and never locate, place, or start a daemon image.

The singleton broker serializes creation per root/version/image route. It places the image under its stable user-owned root, launches one child, watches early exit, and returns only after the route-local endpoint answers an active BackendHandle probe.

Bootstrap cargo-install paths are intentionally uncached. soldr build-from-source ... and [soldr.plugins] installs from soldr toolchain prepare / ensure invoke the directly resolved cargo binary and scrub inherited RUSTC_WRAPPER / RUSTC_WORKSPACE_WRAPPER. Those commands install dev tools and cross-target helper binaries; routing them through Soldr's wrapper slot would make setup recursively depend on the cache layer it is preparing.

rustdoc is intentionally not a zccache driver route today. Direct soldr rustdoc ... invocations and rustdoc PATH shims resolve the toolchain rustdoc binary and run it directly. soldr cargo doc, soldr doc, and doc tests still run with RUSTC_WRAPPER=soldr, so rustc dependency compile units remain cached; only the rustdoc driver phase itself is uncached because the embedded zccache runtime has no rustdoc parser/route.

rust-analyzer is launched as the real toolchain language server, not as a cache driver. When caching is enabled, soldr rust-analyzer ... gives the server process Soldr's cache policy plus a scoped child PATH shim so language-server child builds can re-enter the broker-owned route. SOLDR_DISABLE_CHILD_SHIMS=1 keeps the language server as a direct passthrough when an editor owns its build environment.

Set SOLDR_CACHE_LIFECYCLE=command for self-build jobs that need embedded cache state flushed before a following archive or test step. Command mode requests a structured checkpoint of the embedded zccache artifact index, depgraph, metadata cache, compiler hash cache, system-includes cache, and artifact store, then finalizes session statistics. An incomplete or timed-out checkpoint is reported as a failure. Command mode does not run zccache stop, does not terminate the root-owning Soldr daemon, and never changes ownership based on ZCCACHE_CACHE_DIR. Use soldr cache shutdown when the whole Soldr daemon must exit.

On Windows, soldr may copy the running soldr.exe into SOLDR_CACHE_DIR/runtime/soldr-self/<version-and-hash>/soldr.exe and re-run the command from that relocated copy before build orchestration starts. This keeps disposable worktree builds from repeatedly using the worktree-local soldr.exe as RUSTC_WRAPPER. The trampoline sets SOLDR_RELOCATED_EXE=1 and SOLDR_ORIGINAL_EXE=<original path> as a recursion guard and preserves argv, inherited environment, stdio, and exit status. Stale relocated copies are purged by a best-effort runtime GC step that runs periodically and skips copies that cannot be removed because they are still locked.

SOLDR_RUST_PLAN_SKIP_WARM_RESTORE is a default-on short-circuit for the rust-plan restore step. After a successful rust-plan save, soldr writes a sentinel next to the thin-slice bundle recording the plan inputs hash, target dir, GITHUB_RUN_ID, GITHUB_JOB, GITHUB_RUN_ATTEMPT, zccache session id, and a unix timestamp. On the next invocation, if the sentinel exists and every match field equals the current value — and the sentinel is no older than 5 minutes — soldr skips rust-plan restore and leaves the already-warm target/ tree untouched. This avoids invalidating Cargo's mtime-based fingerprints when split CI steps share a checkout but spawn fresh shells per step (issue #229). The flag is enabled when unset; set it to a falsy value (0, false, no, off, or empty, case-insensitive) to opt out, and any other value (including the historical truthy spellings 1, true, yes, on) keeps the short-circuit enabled. The gate is conservative: a missing, stale, or partially-mismatched sentinel falls through to the normal restore, so the short-circuit can never make a build less correct than the default path. Promoted to default-on after the #229 CI validation runs (PRs #247, #257, #260, #261, #262) landed cleanly on main.

As of issue #1529, the cache-resident sentinel is only half of the proof. A successful save also writes a paired .soldr-warm-restore.json generation marker inside the live target directory. The next invocation skips restore only when both records parse and their unique generation id and plan hash match. cargo clean or whole-target deletion removes the marker naturally; missing, partial, corrupt, stale, and mismatched markers force the normal restore without a target-tree walk.

Target cache (default off)

soldr's target-cache (also called the "rust-plan" path) save/restores target/ artifacts via zccache so a fresh CI runner with a populated cache root can skip recompilation. It is off by default — the planner short-circuits the moment SOLDR_TARGET_CACHE_MODE is empty/unset and never touches cargo metadata, the tar walker, or the per-build registry. CI workflows that want it (typically through setup-soldr) set the env var explicitly to thin or full.

Activation contract (verified against rust_plan.rs + cargo_front_door/cache_plan.rs in soldr#784):

  • rust_artifact_cache_mode_from_env() returns Ok(None) on the unset/off branch. maybe_prepare_rust_artifact_plan short-circuits to Ok(None) immediately — no cargo metadata, no RustArtifactPlanContext allocation, no env-var fan-out.
  • restore_rust_artifacts is let Some(plan) = … else { return Ok(()) } — a pure stat-free no-op when the plan is absent.
  • save_rust_artifacts is if let Some(plan) = … — same no-op shape.
  • target_dir_for_hooks falls through to super::resolve_target_dir_for_hooks, which only walks --target-dir / CARGO_TARGET_DIR / workspace lookup. The watchdog hook at cargo_front_door/mod.rs therefore sees the same target dir resolution regardless of target-cache state.

The corollary: a local dev machine running soldr cargo build with no setup-soldr involvement pays zero overhead for target-cache. The feature is opt-in by design.

If a contributor runs setup-soldr locally (e.g. via act or a docker reproducer), the env vars get exported and the target-cache code DOES fire — that's intentional ("reproduce CI exactly"). To suppress it manually, unset SOLDR_TARGET_CACHE_MODE or set it to off before invoking soldr cargo ….


Cache Layout

~/.soldr/
|-- bin/
|   `-- <tool>-<version>/
|-- cache/
|   |-- zccache/
|   |   |-- daemon-state/
|   |   |   `-- embedded-v1/
|   |   |       `-- v<VERSION>/ # embedded artifacts, indexes, logs, journal
|   |   `-- history/<build-id>/ # per-build session reports and journal tails
|   `-- sccache/   # injected when SOLDR_RUSTC_WRAPPER=sccache and SCCACHE_DIR is unset
|-- cargo-target/pep517/ # stable per-project PEP 517 Cargo targets
|-- pep517/wheels/ # last successful wheels, bounded by daemon maintenance
|-- runtime/
|   `-- soldr-self/ # Windows self-relocated soldr.exe copies plus periodic GC marker
|-- config.toml
|-- state.redb             # redb state store, including tracked target/ dirs
|-- .gc_warning_marker     # last-emitted timestamp for the stale-target startup warning
`-- daemon.*

Both wrapper-cache subdirectories live entirely under the soldr-owned cache root so they never collide with a user-managed ~/.zccache or the system-default sccache location on the same machine.


Linux artifact glibc floors

Which Linux artifact runs on an old distro is not one number. The two download routes are built by different toolchains and land on different floors, and the release archive's floor is set by binaries soldr does not compile at all.

Measured with readelf -V on the published v0.8.30 assets:

artifactbuilt byfloor
soldr-…-x86_64-unknown-linux-gnu.tar.zstsoldrsoldr buildGLIBC 2.39
…the same archive's crgxfetched prebuiltGLIBC 2.39
…the same archive's cargo-cheffetched prebuiltGLIBC 2.39
soldr-…-manylinux_2_17_x86_64.whlsoldrmaturin build --zigGLIBC 2.17
any *-unknown-linux-musl artifactstaticn/a

Three things follow, and the second is the one that surprises people.

The wheel really is manylinux_2_17. release-auto.yml passes --zig --compatibility manylinux_2_17 on the wheel lanes, so cargo-zigbuild links the embedded binary against a 2.17 baseline. The tag is enforced by verify_wheel_glibc.py, not merely asserted. On a distro too old for the archive, pip install soldr is the route that works.

The archive's floor is capped by crgx and cargo-chef. Those are fetched prebuilt from the soldr-toolchain catalogue, so no change to how soldr builds itself can lower them — it needs an upstream republish. Even once soldr and soldr-daemon improve, an archive bundling 2.39 binaries does not fully run on RHEL 8 (2.28) or Debian 10 (2.28). Measure the archive, not just soldr, when answering "does the release run here".

soldr's own binaries improve after v0.8.30. That release was tagged before soldr#2157, so its 2.39 reflects the old host-glibc link rather than current behaviour. GNU Linux targets now select the catalogue-backed glibc 2.17 sysroot. The release ratchet remains until a real release run confirms the measured floor.

The directive: glibc 2.17, everywhere

The target floor for every Linux -gnu artifact is glibc 2.17. The 2.28 and 2.39 numbers above are the current measured state, not the goal, and the ratchets that encode them are waypoints to be lowered — not a settled policy.

This is one requirement spanning three repos, because the archive's floor is the highest floor of any binary inside it:

repoobligation
zackees/forgerecipes build against glibc 2.17 — this is where the tools are compiled
zackees/soldr-toolchaincatalogued -gnu assets measure 2.17 or lower; deps above it get recompiled
zackees/soldr (here)own binaries reach 2.17; ratchets lowered once measured

Fixing only soldr's own binaries does not deliver a 2.17 archive: crgx and cargo-chef are fetched prebuilt at 2.39 and cap the result on their own. Those need an upstream rebuild.

Do not reach for zig or cargo-zigbuild to get there. Both are being purged in favour of the blessed toolchain, so any 2.17 route built on them is a dead end. Reaching 2.17 is a matter of building against an old sysroot, which is toolchain-agnostic.

One open question worth settling before designing around either answer: whether Rust's precompiled std blocks 2.17 for a stock build. The evidence cuts both ways — the wheel lane reaches 2.17 with the same std, while __cxa_thread_atexit_impl@2.18, getrandom@2.25 and statx@2.28 have been observed as already-versioned references. Resolve it by building in a glibc-2.17 container with the pinned rustc and reading readelf -V: a 2.17 result means the blessed toolchain needs only an old sysroot, while a 2.28 result means 2.17 requires -Z build-std and is a materially larger decision. Background in soldr#2145 and soldr#1060.


GitHub Actions

- name: Build through soldr
  run: soldr cargo build --release

For bootstrap verification of another Rust project:

- name: Build third-party project through soldr
  run: soldr cargo build --target ${{ matrix.target }}

Broker/daemon failure output

A cacheable compile that cannot use its registered broker route fails with an infrastructure-attributed diagnostic. soldr doctor, soldr status, and soldr logs paths provide the route, process, timeout, and log evidence. See docs/DAEMON_TIMEOUTS.md for bounded recovery steps.

Summary

The key design rule is simple:

  • users build through soldr cargo ...
  • soldr owns the wrapper slot on the common path
  • soldr delegates cache-enabled wrapper invocations over Soldr IPC into the zccache service embedded in soldr-daemon
  • users do not need to manually wire RUSTC_WRAPPER for the common path