| name | rust-impl-cross-compile |
| description | Use when the user cross-compiles to another target triple, uses `rustup target add`, uses the `cross` crate, configures per-target linker via `.cargo/config.toml`, or guards code with `cfg(target_os = "...")`. Prevents missing target triple installation, picking wrong target for the use case (musl vs gnu, msvc vs gnu on Windows), and using `cfg` without `cfg_attr` for selective attributes. Covers: target triple format (arch-vendor-os-env), common triples (x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl, aarch64-apple-darwin, x86_64-pc-windows-msvc, x86_64-pc-windows-gnu, wasm32-unknown-unknown, wasm32-wasi), `rustup target add`, `cargo build --target <triple>`, `cross` crate (container-based), conditional compilation `cfg(target_os = "...", target_arch = "...", target_pointer_width = "...", target_feature = "...")`, `cfg_attr`, `.cargo/config.toml` for per-target linker / runner. Keywords: "cross-compile", "cross compile", "target triple", "rustup target add", "cargo build --target", "cross crate", "musl static", "musl gnu", "msvc gnu", wasm32, "wasm32-unknown-unknown", "wasm32-wasi", "cfg target_os", "cfg target_arch", "target_pointer_width", "target_feature", cfg_attr, ".cargo/config.toml", linker, runner, "static linking".
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires Rust 1.85+, edition 2024. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
rust-impl-cross-compile
The cross-compilation skill: how to build a Rust crate for a target other than the host. Covers target triple format and common triples, rustup target add to install standard library artifacts, cargo build --target <triple>, the cross crate for container-based toolchains, conditional compilation via cfg(target_os = ...) and related predicates, the cfg_attr attribute for selective attribute emission, and per-target linker / runner configuration via .cargo/config.toml.
Cross-references: [[rust-impl-cargo-project]] (single-crate Cargo.toml + profiles), [[rust-impl-build-scripts]] (build.rs that detects target via env vars), [[rust-impl-ffi-bindgen]] (cross-compile C deps and adjust sysroot), [[rust-core-toolchain]] (rustup channels, components, target lists).
When to use this skill
- User runs
cargo build and gets linker 'cc' not found or error: target may not be installed after switching --target.
- User wants a portable static Linux binary and asks
musl vs gnu.
- User builds for Windows from Linux and asks
pc-windows-msvc vs pc-windows-gnu.
- User builds for macOS ARM (Apple Silicon) from x86_64 host.
- User asks how to build a
.wasm module and asks wasm32-unknown-unknown vs wasm32-wasip1.
- User writes platform-conditional code with
#[cfg(target_os = "...")] or cfg!(...).
- User wants a single attribute (e.g.
#[repr(C)], #[link(...)]) to appear only on one target.
- User configures a custom linker for a target via
[target.<triple>] linker = "..." in .cargo/config.toml.
- User asks about the
cross tool because installing native toolchains is tedious.
For host-only profile / lints / dependency tuning see [[rust-impl-cargo-project]]. For build-time target detection in build.rs see [[rust-impl-build-scripts]].
Target triple format
Rust target triples follow the shape <arch>-<vendor>-<os>-<env> where each field is a fixed identifier (see platform-support):
| Field | Examples | Meaning |
|---|
arch | x86_64, aarch64, i686, armv7, thumbv7em, riscv64gc, wasm32, wasm64 | CPU instruction set. |
vendor | unknown, apple, pc, none, nvidia | Convention only; unknown is the placeholder for non-Apple / non-PC. |
os | linux, darwin, windows, none, wasi, freebsd, android | Operating system. none for bare metal / freestanding. |
env | gnu, musl, msvc, eabihf, androideabi, wasip1 (1.81+), absent | ABI / libc / runtime flavour. May be absent (aarch64-apple-darwin). |
ALWAYS write the full triple. NEVER abbreviate to linux or windows. NEVER assume a host target; query with rustc -vV | grep host.
x86_64-unknown-linux-gnu arch=x86_64 vendor=unknown os=linux env=gnu
x86_64-unknown-linux-musl arch=x86_64 vendor=unknown os=linux env=musl
aarch64-apple-darwin arch=aarch64 vendor=apple os=darwin env=(none)
x86_64-pc-windows-msvc arch=x86_64 vendor=pc os=windows env=msvc
x86_64-pc-windows-gnu arch=x86_64 vendor=pc os=windows env=gnu
wasm32-unknown-unknown arch=wasm32 vendor=unknown os=unknown env=(none)
wasm32-wasip1 arch=wasm32 vendor=unknown os=wasi env=p1
thumbv7em-none-eabihf arch=thumbv7em vendor=none os=none env=eabihf
NOTE: wasm32-wasi was renamed to wasm32-wasip1 in Rust 1.81 (rust181). wasm32-wasi still works as a legacy alias but new code should use wasm32-wasip1 (or wasm32-wasip2 for WASIp2).
Common target triples
Decision rules per use case. Tier classification is from platform-support: Tier 1 has CI guarantees + host tools, Tier 2 with-host-tools has guaranteed std + tools, Tier 2 without-host-tools has guaranteed std only, Tier 3 is best-effort.
| Target Triple | Tier | When to use it |
|---|
x86_64-unknown-linux-gnu | 1 | Default 64-bit Linux. Dynamic linking against glibc 2.17+. |
aarch64-unknown-linux-gnu | 1 | ARM64 Linux servers (AWS Graviton, RPi 64-bit, etc.). |
x86_64-unknown-linux-musl | 2-host | Fully static Linux binary, no glibc dependency. |
aarch64-unknown-linux-musl | 2-host | Static ARM64 Linux binary. |
aarch64-apple-darwin | 1 | macOS on Apple Silicon (M1+). |
x86_64-apple-darwin | 1 | macOS on Intel. |
x86_64-pc-windows-msvc | 1 | Windows native, MSVC toolchain (needs Visual Studio Build Tools). |
x86_64-pc-windows-gnu | 1 | Windows via MinGW-w64 (cross-compile-friendly from Linux). |
aarch64-pc-windows-msvc | 1 | Windows on ARM64. |
wasm32-unknown-unknown | 2 | Browser WebAssembly. No OS. Requires wasm-bindgen for browser glue. |
wasm32-wasip1 | 2 | WebAssembly with WASI Preview 1 (filesystem, env, clock). |
wasm32-wasip2 | 2 | WebAssembly with WASIp2 (component model). |
thumbv7em-none-eabihf | 2 | Cortex-M4F / M7F embedded (no_std only, hardfloat). |
Linux: gnu vs musl
Need a portable static binary that runs on any Linux distro (no glibc dep)?
YES -> *-unknown-linux-musl
NO -> *-unknown-linux-gnu (default, dynamic glibc, smaller binary, glibc 2.17+)
Will the binary load shared libraries with dlopen at runtime?
YES -> *-gnu (musl has known dlopen quirks)
NO -> either is fine, choose by static vs dynamic preference
Is the binary going into a `FROM scratch` Docker image?
YES -> *-musl is the only sane choice
Windows: msvc vs gnu
Compiling on Windows with Visual Studio Build Tools installed?
YES -> *-pc-windows-msvc (recommended, matches Microsoft ecosystem)
Cross-compiling from Linux / macOS to Windows?
*-pc-windows-gnu is easier (just install mingw-w64 cross gcc).
*-pc-windows-msvc also works via `cargo-xwin` or LLVM tooling but heavier setup.
Linking against a precompiled `.lib` from a vendor?
Match the vendor's ABI. MSVC libs require msvc; MinGW libs require gnu.
WebAssembly: unknown vs wasi
Browser target with JS interop? -> wasm32-unknown-unknown + wasm-bindgen.
CLI / serverless WASI runtime (wasmtime, wasmer)? -> wasm32-wasip1 (or wasip2 for component model).
Pure compute, no I/O, no DOM? -> wasm32-unknown-unknown (smaller, no OS shims).
Cross-compile workflow
Step 1 : install the target standard library
rustup target add x86_64-unknown-linux-musl
rustup target add installs ONLY the precompiled standard library for the target (rustup-cross). It does NOT install a linker, libc, or any platform SDK. The host toolchain (compiler) is reused; only libstd / libcore / liballoc are added.
List available targets : rustup target list. List installed : rustup target list --installed. Remove : rustup target remove <triple>.
Step 2 : ensure a target-compatible linker exists
| Target | Linker needed on Linux host |
|---|
x86_64-unknown-linux-musl | musl-gcc (Alpine native) or via cross |
aarch64-unknown-linux-gnu | gcc-aarch64-linux-gnu (Debian/Ubuntu) |
aarch64-unknown-linux-musl | musl-cross toolchain or cross |
x86_64-pc-windows-gnu | gcc-mingw-w64-x86-64 |
x86_64-pc-windows-msvc | MSVC SDK + cargo-xwin, or cross |
aarch64-apple-darwin | macOS SDK (must build on macOS host or use osxcross) |
wasm32-unknown-unknown | none required, LLVM ships its own. |
wasm32-wasip1 | none required. |
NEVER expect rustup target add to install gcc / clang / lld. ALWAYS install the host-side cross toolchain separately, or delegate to cross.
Step 3 : point Cargo at the linker via .cargo/config.toml
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
[target.x86_64-unknown-linux-musl]
linker = "musl-gcc"
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
[target.<triple>] keys (cargo-config) :
| Key | Purpose | Env var override |
|---|
linker | Linker binary path. | CARGO_TARGET_<TRIPLE>_LINKER |
runner | Wrapper to run the produced executable (e.g. emulator for cargo run --target). | CARGO_TARGET_<TRIPLE>_RUNNER |
rustflags | Extra rustc flags for this target only. | CARGO_TARGET_<TRIPLE>_RUSTFLAGS |
rustdocflags | Extra rustdoc flags. | CARGO_TARGET_<TRIPLE>_RUSTDOCFLAGS |
Triple names in env vars uppercase and replace dashes with underscores : CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER.
Step 4 : build
cargo build --target x86_64-unknown-linux-musl --release
ALWAYS include --release for distribution builds. The target dir is nested under target/<triple>/, so cross builds never overwrite host builds.
Pin a default target for a project
[build]
target = "x86_64-unknown-linux-musl"
Now bare cargo build cross-builds without --target. NEVER do this in a library crate (forces all downstream users to install that target).
The cross tool (container-based)
cross (cross-rs) is a community wrapper that runs cargo inside a per-target Docker / Podman image. Each image bundles the cross linker + libc + sysroot, so the host needs only Docker.
cargo install cross --git https://github.com/cross-rs/cross
cross build --target aarch64-unknown-linux-gnu --release
Use cross when :
- Host toolchain setup is awkward (musl-cross on Debian, mingw-w64 on macOS, etc.).
- You build many targets in CI and want hermetic, reproducible toolchains.
- You want Windows / Android / BSD targets without installing each SDK.
NEVER use cross for aarch64-apple-darwin from a non-macOS host : Apple's SDK license forbids redistribution, so no Docker image exists. ALWAYS build Darwin targets on macOS hardware or via macOS CI runners.
cross reads Cross.toml for per-target image overrides, mounted volumes, and pre-build hooks. See [[references/methods.md]] for syntax.
Conditional compilation with cfg
cfg predicates select code that is compiled only when the predicate holds (reference-cfg). All cfg keys are evaluated at compile time against the current target.
Common cfg keys
| Key | Values | Notes |
|---|
target_os | linux, macos, windows, android, ios, freebsd, netbsd, openbsd, wasi, none, ... | OS identifier. |
target_arch | x86_64, aarch64, arm, x86, wasm32, riscv64, ... | Instruction-set arch. |
target_family | unix, windows, wasm | Coarse-grained OS family. |
target_env | gnu, musl, msvc, sgx, empty | Environment / ABI. |
target_endian | little, big | Byte order. |
target_pointer_width | 16, 32, 64 | Pointer size in bits. |
target_feature | "sse2", "avx2", "neon", "crt-static", ... | CPU / runtime features (must be quoted strings). |
unix / windows | (boolean keys) | Shortcut for target_family. |
#[cfg(target_os = "linux")]
fn platform() -> &'static str { "linux" }
#[cfg(target_os = "macos")]
fn platform() -> &'static str { "macos" }
#[cfg(target_os = "windows")]
fn platform() -> &'static str { "windows" }
Combining predicates
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn fast_linux_x64() {}
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn unix_like() {}
#[cfg(not(target_family = "windows"))]
fn not_windows() {}
Use all(...), any(...), not(...) for boolean composition. NEVER nest cfg attributes (#[cfg(a)] #[cfg(b)]) when you mean all ; while it works, cfg(all(a, b)) makes intent explicit.
Runtime cfg!(...) macro
if cfg!(target_os = "linux") {
}
cfg!(...) returns a bool constant. Both arms of the if are compiled on every target, so they must type-check everywhere. For mutually exclusive code, prefer #[cfg(...)] on items.
cfg_attr for conditional attributes
#[cfg_attr(predicate, attr1, attr2, ...)] emits the listed attributes ONLY when the predicate is true ([reference-cfg]).
#[cfg_attr(target_os = "linux", repr(C))]
struct Header {
magic: u32,
flags: u32,
}
#[cfg_attr(not(test), inline)]
fn hot_path() { }
ALWAYS use cfg_attr when an attribute itself should be conditional. NEVER copy-paste the whole item under #[cfg(...)] just to vary one attribute.
Quick-start workflow recipes
Static Linux binary from Linux host
sudo apt install musl-tools
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl
file target/x86_64-unknown-linux-musl/release/<name>
Windows .exe from Linux host (MinGW)
sudo apt install gcc-mingw-w64-x86-64
rustup target add x86_64-pc-windows-gnu
cargo build --release --target x86_64-pc-windows-gnu
WebAssembly (WASI) from any host
rustup target add wasm32-wasip1
cargo build --release --target wasm32-wasip1
wasmtime target/wasm32-wasip1/release/<name>.wasm
Many targets with cross
cargo install cross --git https://github.com/cross-rs/cross
cross build --release --target aarch64-unknown-linux-gnu
cross build --release --target armv7-unknown-linux-gnueabihf
cross build --release --target x86_64-pc-windows-gnu
Validation checklist
Before claiming a cross-build is done :
rustup target list --installed | grep <triple> shows the target.
- Linker is on
PATH or pinned in .cargo/config.toml.
cargo build --target <triple> exits 0.
file target/<triple>/release/<bin> reports the expected ELF / PE / Wasm format.
- For musl :
ldd target/<triple>/release/<bin> reports not a dynamic executable.
- For Wasm :
wasm-objdump -h <bin>.wasm lists sections without errors.
- Platform-conditional code paths were exercised on the actual target (or under an emulator) ;
cfg! only verifies the predicate, not the runtime correctness.
Reference links
- [[references/methods.md]] : full rustup / cargo / cross command catalogue, all
[target.<triple>] keys, full cfg-key list with valid values, env-var equivalents.
- [[references/examples.md]] : end-to-end recipes (musl static, Windows MinGW from Linux, macOS ARM, WASI, Cortex-M no_std,
Cross.toml, GitHub Actions matrix).
- [[references/anti-patterns.md]] : missing
rustup target add, choosing musl for the wrong reason, wasm32-unknown-unknown for WASI workloads, hard-coding linker path, missing cfg_attr, untested conditional code paths.