Rust Build Setup

July 30, 2026 · View on GitHub

This is the canonical build-setup reference for the rmcp server family: soma, labby, axon, cortex, unifi-rmcp, gotify-rmcp, apprise-rmcp, tailscale-rmcp, and unraid-rmcp.

All family repos share a common Cargo configuration model: heavy lifting lives in ~/.cargo/config.toml on the developer's machine; per-repo .cargo/config.toml files are kept minimal and contain only what the global config cannot express (xtask alias, repo-specific linker overrides).


System prerequisites

ToolPurposeInstall
Rust stable ≥ 1.97.1Compilerrustup update stable
clangLinker driver for the mold integrationapt install clang
moldHigh-speed linker; 5-10× faster than GNU ld on Linuxapt install mold
mingw-w64Cross-compiler for x86_64-pc-windows-gnu targetsapt install mingw-w64
justCommand runner (optional, but used by all Justfile recipes)cargo install just

clang and mold are required for fast Linux incremental builds. Without them the global config falls back to the system linker; builds still work but link times are significantly slower on large dependency graphs.

mingw-w64 is only needed for local Windows cross-compilation. CI installs it automatically.


Global Cargo config (~/.cargo/config.toml)

All family repos assume the following global configuration on the developer's machine. This file is not committed to any repo — it lives only in ~/.cargo/config.toml.

# kache is the compiler cache: a plain RUSTC_WRAPPER, content-addressed
# (blake3). There is NO PATH front door — the ~/.local/bin/cargo shim was
# deleted with soldr on 2026-07-28. Keep dev incremental disabled: incremental
# compilation does not compose with any compile wrapper.

[build]
# Stable across builds so cache keys do not churn.
jobs = 12
rustc-wrapper = "kache"

[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"

[profile.dev]
debug = 1
codegen-units = 8
split-debuginfo = "unpacked"
incremental = false
opt-level = 0

[profile.test]
debug = 1
codegen-units = 8

[profile.dev.package."*"]
opt-level = 1

Why mold?

mold replaces GNU ld as the linker for Linux builds. On large Rust workspaces with many crates and dependencies, the link step dominates incremental rebuild times. mold$ \text{is} \text{typically} 5–10 \times \text{faster} \text{than} $ld$ \text{and} 2–3 \times \text{faster} \text{than} $lld.

The global [target.x86_64-unknown-linux-gnu] block activates it via -fuse-ld=mold. All family repos inherit this automatically — no per-repo config is needed. Do not add rustflags to [target.x86_64-unknown-linux-gnu] in a per-repo config — that would replace, not extend, the global rustflags and silently drop the mold flag.

Why kache globally?

The host-level Cargo config sets rustc-wrapper = "kache" once, so dependency compilation is cacheable across every worktree without any repo carrying its own wrapper hook. kache normalizes paths to sentinels internally, so unlike sccache there is no basedir list to maintain and no daemon socket to configure — the daemon is optional for local-only use.

kache never fails a build on a cache problem. A dead daemon or a broken remote produces a green, slow build. Read kache stats; never infer cache health from a passing build. Full reference: ~/docs/dev/toolchain/kache.md.

Profile settings rationale

SettingValueRationale
profile.dev.debug`1$\text{Line} \text{tables} \text{only} — \text{enough} \text{for} \text{backtraces}, \text{without} \text{the} 3 \times \text{binary}-\text{size} \text{penalty} \text{of} \text{full} \text{DWARF}
$profile.dev.codegen-units`8Parallelises compilation within a crate; 8 balances parallelism and optimisation quality
profile.dev.split-debuginfo"unpacked"Keeps debug info in separate .dwo files, reducing link-step memory pressure
profile.dev.incrementalfalseRequired: incremental compilation does not compose with a compile wrapper
profile.dev.opt-level0No optimisation for the crate under active development
profile.dev.package."*".opt-level1Light optimisation for dependencies — prevents debug-only slowness in heavy crates like serde and tokio
profile.test.debug1Same as dev — enough for test failure backtraces
profile.test.codegen-units8Same rationale as dev

Per-repo .cargo/config.toml

Each family repo has a minimal .cargo/config.toml. The rule is: only put settings here that the global config cannot provide.

What belongs here

[alias]
# Required if the repo has an xtask/ crate.
xtask = "run --package xtask --"

[target.x86_64-pc-windows-gnu]
# Only if the repo cross-compiles for Windows and the global config may not
# be present (e.g. in CI without the standard global config).
linker = "x86_64-w64-mingw32-gcc"

What does NOT belong here

SettingReason to keep it in the global config
Profile settings (debug, codegen-units, etc.)Already set globally; duplicating causes confusion when the global changes
build.jobsMachine-specific; the global config tunes it per host
[target.x86_64-unknown-linux-gnu].rustflagsOverriding this drops the mold flag from the global config
build.rustc-wrapper for generic artifact syncGeneric repos must use explicit sync commands; hidden post-compile copies do not belong in Cargo config

Repos without an xtask crate

Repos without an xtask/ crate either omit .cargo/config.toml entirely or keep only documented repo-specific overrides.


Repo-specific overrides

Some repos intentionally diverge from the global config for documented reasons:

RepoOverrideReason
axonbuild.rustc-wrapper = "scripts/cargo-rustc-wrapper"Automatically refreshes the actively used local axon binary and named repo artifacts after successful bin builds
cortexbuild.rustc-wrapper = "scripts/cargo-rustc-wrapper" and build.target-dir = ".cache/cargo"Release-only local ~/.local/bin/cortex refresh plus a non-root target directory for Docker bind mounts
labbuild.rustc-wrapper = "scripts/cargo-rustc-wrapper"Keeps the active labby binary fresh for the local gateway/operator workflow

If you add a new legitimate per-repo override, document it in the repo's docs/RUST.md and add a row to this table in soma's docs/RUST.md.


Explicit artifact sync

Generic rmcp repos do not use repo-local rustc-wrapper hooks for artifact sync. Build normally with Cargo, then run an explicit recipe when you want to refresh checked-in or plugin-local binaries:

just sync-bin

For repos with bundled plugin binaries, sync-bin delegates to just build-plugin. In Soma itself, plugins launch the installed PATH binary, so sync-bin delegates to just install-local.

The global mise config also exposes a cross-repo dispatcher:

mise run cargo:sync-bin

That task calls just sync-bin when present, falls back to just build-plugin, and fails loudly in repos with no explicit artifact-sync recipe.


Windows cross-compilation

Repos that publish Windows binaries configure the mingw linker. The global ~/.cargo/config.toml already sets this; per-repo configs set it as a fallback for CI environments that may not have the standard global config.

[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"

Add the Windows target if it is not already installed:

rustup target add x86_64-pc-windows-gnu

Install the cross-compiler on Debian/Ubuntu build hosts:

apt install mingw-w64

Native Windows CI builds

PR CI also builds on native Windows through .github/workflows/ci.yml. This is separate from Linux-to-Windows cross-compilation:

  • cross-compilation is useful for tag-time packaging when dependencies support it
  • native Windows CI catches Windows runtime, path, shell, and MSVC issues earlier
  • GitHub-hosted windows-latest avoids dependencies on machine-local runner state

The Windows CI job sets explicit portable CPU flags:

$env:RUSTFLAGS = "-C target-cpu=x86-64 -C target-feature=-avx512f,-avx512vl,-avx512bw,-avx512dq,-avx512cd,-avx512ifma,-avx512vbmi,-avx512vbmi2,-avx512vnni,-avx512bitalg,-avx512vpopcntdq"

Keep machine-specific optimization out of committed config. In particular, do not add this to repo or runner-wide config for artifacts that will be shared:

[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-cpu=native"]

For the GitHub-hosted runner flow and artifact smoke testing, see docs/WINDOWS-RUNNER.md.


Quick verification

Run these after cloning to confirm the build environment is correctly wired:

# Verify mold is in use (should show "mold" in the link invocation)
cargo build -v 2>&1 | grep "link-arg"

# Verify the xtask alias works (if the repo has xtask/)
cargo xtask --help

# Verify Windows cross-compile target is installed
rustup target list --installed | grep windows-gnu