rgoussu@goussu: ~/library/rust/build-ecosystem
~/library/rust/build-ecosystem cat building-and-releasing.md

Building & releasing Rust

# Cross-compilation and the linker problem, cross and cargo-zigbuild, static musl builds, cargo-dist for release automation, and the supply chain: cargo-audit, cargo-deny, cargo-vet.

Conceptsaved 2026-08-09 #rust#build#release#cross-compilation#supply-chain

Overview

Getting from cargo build to shipped software is a story in three parts: cross-compiling for targets you don't develop on (where Rust is good but not Go-effortless), packaging releases for humans and CI to consume (where cargo-dist has become the standard answer), and defending the dependency graph (where Rust's everything-from-source, thousands-of-small-crates culture makes supply-chain tooling non-optional). The comparison point is Go's building & releasing: same static-binary endgame, more moving parts on the way.

Key points

  • Cross-compilation = target + linker: rustup target add installs the stdlib for a triple, but unlike Go you also need a cross-linker for the target — the reason plain cargo build --target fails where GOOS=linux go build just works.
  • The workarounds are mature: cross (runs the build in Docker images with the right toolchains, QEMU for foreign-arch tests) and cargo-zigbuild (uses Zig's bundled clang as a universal cross-linker — the lightest path, glibc version targeting included).
  • Fully-static Linux binaries: --target x86_64-unknown-linux-musl — the FROM scratch container play; watch for C dependencies (openssl → use rustls; or openssl/vendored).
  • cargo-dist automates the release: from a git tag it builds a target matrix in CI, produces archives, shell/PowerShell installers, Homebrew formulae and npm shims, and publishes a GitHub release with checksums — the community-standard answer to "how do users install my Rust tool", GoReleaser's counterpart.
  • cargo-release handles the other half — version bumps, changelog, tagging, cargo publish ordering across a workspace.
  • cargo-audit checks Cargo.lock against the RustSec advisory database — the minimum bar, in every CI.
  • cargo-deny is the policy engine: advisories + license allow-lists + duplicate version bans + registry/source restrictions, one deny.toml, fail-the-build semantics.
  • cargo-vet (Mozilla) tackles what scanning can't: has a human reviewed this code? — records audits per crate version, lets organizations share and import each other's audit sets (Mozilla's, Google's), and fails on unvetted additions. cargo-crev is the older web-of-trust cousin.
  • Why the vigilance: build.rs and proc macros execute arbitrary code at build time on developer and CI machines — a typosquatted crate owns your laptop before the binary even runs. Lockfiles, --locked in CI, minimal-dependency culture and vet/deny are the layered answer.

Details

Release pipeline, the house shape

rustup target add x86_64-unknown-linux-musl aarch64-apple-darwin
cargo zigbuild --release --target x86_64-unknown-linux-musl   # or: cross build
cargo dist init                     # generates the CI release workflow
git tag v1.2.0 && git push --tags   # cargo-dist takes it from here

CI gates alongside: cargo audit, cargo deny check, cargo vet (where adopted), plus cargo build --locked so the lockfile is the build.

SBOM & provenance

cargo auditable embeds the dependency list into the binary itself (readable by cargo audit bin); cargo-sbom/cyclonedx-rust produce standard SBOM documents; crates.io has shipped provenance attestations for publishes from CI. Between the immutable registry, lockfiles and these attestations, Rust's story now roughly matches Go's sumdb-and-govulncheck posture — with cargo-vet as the piece Go lacks.

Related