Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Cross-Compilation — One Source, Many Targets 🟡
交叉编译:一套源码,多种目标 🟡

What you’ll learn:
本章将学到什么:

  • How Rust target triples work and how to add them with rustup
    Rust target triple 是怎么工作的,以及如何用 rustup 安装目标
  • Building static musl binaries for container/cloud deployment
    如何为容器和云部署构建静态 musl 二进制
  • Cross-compiling to ARM (aarch64) with native toolchains, cross, and cargo-zigbuild
    如何用原生工具链、crosscargo-zigbuild 交叉编译到 ARM(aarch64)
  • Setting up GitHub Actions matrix builds for multi-architecture CI
    如何给 GitHub Actions 配置多架构矩阵构建

Cross-references: Build Scripts — build.rs runs on HOST during cross-compilation · Release Profiles — LTO and strip settings for cross-compiled release binaries · Windows — Windows cross-compilation and no_std targets
交叉阅读: 构建脚本 说明了 build.rs 在交叉编译时运行在 HOST 上;发布配置 继续讲 LTO 和 strip 等发布参数;Windows 负责 Windows 交叉编译与 no_std 目标的另一半话题。

Cross-compilation means building an executable on one machine (the host) that runs on a different machine (the target). The host might be your x86_64 laptop; the target might be an ARM server, a musl-based container, or even a Windows machine. Rust makes this remarkably feasible because rustc is already a cross-compiler — it just needs the right target libraries and a compatible linker.
交叉编译的意思很简单:在一台机器上构建,在另一台机器上运行。前者叫 host,后者叫 target。host 可能是 x86_64 笔记本,target 可能是 ARM 服务器、基于 musl 的容器,甚至是 Windows 主机。Rust 在这件事上天生就占便宜,因为 rustc 本身就是交叉编译器,只是还需要正确的目标库和匹配的链接器。

The Target Triple Anatomy
Target Triple 的结构

Every Rust compilation target is identified by a target triple which often has four parts despite the name:
每一个 Rust 编译目标都由一个 target triple 标识。名字虽然叫 triple,实际上经常有四段。

<arch>-<vendor>-<os>-<env>

Examples:
  x86_64  - unknown - linux  - gnu      ← standard Linux (glibc)
  x86_64  - unknown - linux  - musl     ← static Linux (musl libc)
  aarch64 - unknown - linux  - gnu      ← ARM 64-bit Linux
  x86_64  - pc      - windows- msvc     ← Windows with MSVC
  aarch64 - apple   - darwin             ← macOS on Apple Silicon
  x86_64  - unknown - none              ← bare metal (no OS)
<arch>-<vendor>-<os>-<env>

示例:
  x86_64  - unknown - linux  - gnu      ← 标准 Linux(glibc)
  x86_64  - unknown - linux  - musl     ← 静态 Linux(musl libc)
  aarch64 - unknown - linux  - gnu      ← ARM 64 位 Linux
  x86_64  - pc      - windows- msvc     ← 使用 MSVC 的 Windows
  aarch64 - apple   - darwin             ← Apple Silicon 上的 macOS
  x86_64  - unknown - none              ← 裸机,无操作系统

List all available targets:
查看可用目标:

# Show all targets rustc can compile to (~250 targets)
rustc --print target-list | wc -l

# Show installed targets on your system
rustup target list --installed

# Show current default target
rustc -vV | grep host

Installing Toolchains with rustup
rustup 安装目标工具链

# Add target libraries (Rust std for that target)
rustup target add x86_64-unknown-linux-musl
rustup target add aarch64-unknown-linux-gnu

# Now you can cross-compile:
cargo build --target x86_64-unknown-linux-musl
cargo build --target aarch64-unknown-linux-gnu  # needs a linker — see below

What rustup target add gives you: the pre-compiled std, core, and alloc libraries for that target. It does not give you a C linker or C library. For targets that need a C toolchain, especially most gnu targets, you still need to install that part yourself.
rustup target add 到底装了什么:它只会给出目标平台预编译好的 stdcorealloc。它不会顺手给出 C 链接器,也不会给出目标平台的 C 库。所以只要目标依赖 C 工具链,尤其是大部分 gnu 目标,就还得额外安装对应的系统工具。

# Ubuntu/Debian — install the cross-linker for aarch64
sudo apt install gcc-aarch64-linux-gnu

# Ubuntu/Debian — install musl toolchain for static builds
sudo apt install musl-tools

# Fedora
sudo dnf install gcc-aarch64-linux-gnu

.cargo/config.toml — Per-Target Configuration
.cargo/config.toml:按目标配置

Instead of passing --target on every command, configure defaults in .cargo/config.toml at your project root or home directory:
如果不想每次命令都手敲 --target,可以把目标配置放进项目根目录或者用户目录下的 .cargo/config.toml

# .cargo/config.toml

# Default target for this project (optional — omit to keep native default)
# [build]
# target = "x86_64-unknown-linux-musl"

# Linker for aarch64 cross-compilation
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
rustflags = ["-C", "target-feature=+crc"]

# Linker for musl static builds (usually just the system gcc works)
[target.x86_64-unknown-linux-musl]
linker = "musl-gcc"
rustflags = ["-C", "target-feature=+crc,+aes"]

# ARM 32-bit (Raspberry Pi, embedded)
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"

# Environment variables for all targets
[env]
# Example: set a custom sysroot
# SYSROOT = "/opt/cross/sysroot"

Config file search order (first match wins):
配置文件查找顺序,先找到谁就用谁:

  1. <project>/.cargo/config.toml
    1. 当前项目下的 .cargo/config.toml
  2. <project>/../.cargo/config.toml (parent directories, walking up)
    2. 沿父目录逐级向上查找的 .cargo/config.toml
  3. $CARGO_HOME/config.toml (usually ~/.cargo/config.toml)
    3. $CARGO_HOME/config.toml,通常就是 ~/.cargo/config.toml

Static Binaries with musl
用 musl 构建静态二进制

For deploying to minimal containers such as Alpine or scratch, or to systems where you can’t control the glibc version, musl is often the cleanest answer:
如果目标环境是 Alpine、scratch 这类极简容器,或者压根控制不了线上 glibc 版本,那 musl 静态构建通常是最省心的方案。

# Install musl target
rustup target add x86_64-unknown-linux-musl
sudo apt install musl-tools  # provides musl-gcc

# Build a fully static binary
cargo build --release --target x86_64-unknown-linux-musl

# Verify it's static
file target/x86_64-unknown-linux-musl/release/diag_tool
# → ELF 64-bit LSB executable, x86-64, statically linked

ldd target/x86_64-unknown-linux-musl/release/diag_tool
# → not a dynamic executable

Static vs dynamic trade-offs:
静态链接和动态链接的取舍:

Aspect
方面
glibc (dynamic)
glibc 动态链接
musl (static)
musl 静态链接
Binary size
体积
Smaller (shared libs)
更小,依赖共享库
Larger (~5-15 MB increase)
更大,通常多 5 到 15 MB
Portability
可移植性
Needs matching glibc version
依赖目标机 glibc 版本匹配
Runs anywhere on Linux
基本能在 Linux 上通跑
DNS resolution
DNS 解析
Full nsswitch support
支持更完整
Basic resolver (no mDNS)
解析器较基础
Deployment
部署
Needs sysroot or container
通常要容器或系统依赖配合
Single binary, no deps
单文件部署,几乎没额外依赖
Performance
性能
Slightly faster malloc
内存分配通常略快
Slightly slower malloc
分配器通常略慢
dlopen() support
dlopen()
YesNo

For the project: A static musl build is ideal for deployment to diverse server hardware where you can’t guarantee the host OS version. The single-binary deployment model eliminates “works on my machine” issues.
对这个工程来说,如果二进制要部署到版本混杂的服务器环境,musl 静态构建会非常合适。单文件交付的方式,也能少掉一堆“本机能跑,线上炸了”的破事。

Cross-Compiling to ARM (aarch64)
交叉编译到 ARM(aarch64)

ARM servers such as AWS Graviton、Ampere Altra、Grace are becoming more common. Cross-compiling for aarch64 from an x86_64 host is a very normal requirement now:
AWS Graviton、Ampere Altra、Grace 这类 ARM 服务器越来越常见了。所以从 x86_64 主机构建 aarch64 二进制,现在已经是很正常的需求。

# Step 1: Install target + cross-linker
rustup target add aarch64-unknown-linux-gnu
sudo apt install gcc-aarch64-linux-gnu

# Step 2: Configure linker in .cargo/config.toml (see above)

# Step 3: Build
cargo build --release --target aarch64-unknown-linux-gnu

# Step 4: Verify the binary
file target/aarch64-unknown-linux-gnu/release/diag_tool
# → ELF 64-bit LSB executable, ARM aarch64

Running tests for the target architecture requires either an actual ARM machine or QEMU user-mode emulation:
如果还想跑目标架构测试,那就得有真实 ARM 机器,或者上 QEMU 用户态模拟。

# Install QEMU user-mode (runs ARM binaries on x86_64)
sudo apt install qemu-user qemu-user-static binfmt-support

# Now cargo test can run cross-compiled tests through QEMU
cargo test --target aarch64-unknown-linux-gnu
# (Slow — each test binary is emulated. Use for CI validation, not daily dev.)

Configure QEMU as the test runner in .cargo/config.toml:
可以把 QEMU 直接配成目标测试运行器:

[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
runner = "qemu-aarch64-static -L /usr/aarch64-linux-gnu"

The cross Tool — Docker-Based Cross-Compilation
cross:基于 Docker 的交叉编译

The cross tool provides a nearly zero-setup cross-compilation experience by using pre-configured Docker images:
cross 通过预配置好的 Docker 镜像,把交叉编译这件事做成了接近零准备的体验。

# Install cross (from crates.io — stable releases)
cargo install cross
# Or from git for latest features (less stable):
# cargo install cross --git https://github.com/cross-rs/cross

# Cross-compile — no toolchain setup needed!
cross build --release --target aarch64-unknown-linux-gnu
cross build --release --target x86_64-unknown-linux-musl
cross build --release --target armv7-unknown-linux-gnueabihf

# Cross-test — QEMU included in the Docker image
cross test --target aarch64-unknown-linux-gnu

How it works: cross replaces cargo and runs the build inside a Docker container that already contains the right sysroot, linker, and toolchain. Your source is mounted into the container, and the output still goes into the usual target/ directory.
它的工作方式 其实很朴素:用 cross 代替 cargo,把构建过程扔进一个已经准备好 sysroot、链接器和工具链的容器里。源码还是挂载进容器,输出也还是回到熟悉的 target/ 目录。

Customizing the Docker image with Cross.toml:
如果默认镜像不够用,可以通过 Cross.toml 自定义。

# Cross.toml
[target.aarch64-unknown-linux-gnu]
# Use a custom Docker image with extra system libraries
image = "my-registry/cross-aarch64:latest"

# Pre-install system packages
pre-build = [
    "dpkg --add-architecture arm64",
    "apt-get update && apt-get install -y libpci-dev:arm64"
]

[target.aarch64-unknown-linux-gnu.env]
# Pass environment variables into the container
passthrough = ["CI", "GITHUB_TOKEN"]

cross requires Docker or Podman, but it saves you from manually dealing with cross-compilers, sysroots, and QEMU. For CI, it’s usually the most straightforward choice.
cross 的代价就是要有 Docker 或 Podman,但好处也很明显:不用手工折腾交叉编译器、sysroot 和 QEMU。对 CI 来说,它通常是最省脑子的方案。

Using Zig as a Cross-Compilation Linker
把 Zig 当成交叉编译链接器

Zig bundles a C compiler and cross-compilation sysroot for dozens of targets in a single small download. That makes it a very convenient cross-linker for Rust:
Zig 把 C 编译器和多目标 sysroot 都打包进一个很小的下载里,所以拿它做 Rust 的交叉链接器会非常顺手。

# Install Zig (single binary, no package manager needed)
# Download from https://ziglang.org/download/
# Or via package manager:
sudo snap install zig --classic --beta  # Ubuntu
brew install zig                          # macOS

# Install cargo-zigbuild
cargo install cargo-zigbuild

Why Zig? The biggest advantage is glibc version targeting. Zig lets you specify the exact glibc version to link against, which is gold when your binaries must run on older enterprise distributions:
为什么要用 Zig:最大的亮点就是它能精确指定 glibc 版本。只要目标环境里存在老旧企业发行版,这一点就非常值钱。

# Build for glibc 2.17 (CentOS 7 / RHEL 7 compatibility)
cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.17

# Build for aarch64 with glibc 2.28 (Ubuntu 18.04+)
cargo zigbuild --release --target aarch64-unknown-linux-gnu.2.28

# Build for musl (fully static)
cargo zigbuild --release --target x86_64-unknown-linux-musl

The .2.17 suffix is Zig-specific. It tells Zig to link against glibc 2.17 symbol versions so the result still runs on CentOS 7 and later, without needing Docker or hand-managed sysroots.
这里的 .2.17 后缀是 Zig 扩展语法,意思是按 glibc 2.17 的符号版本去链接。这样产物就能在 CentOS 7 及之后的系统上运行,而且不用靠 Docker,也不用自己维护 sysroot。

Comparison: cross vs cargo-zigbuild vs manual:
crosscargo-zigbuild 和手工配置的对比:

Feature
维度
Manual
手工配置
crosscargo-zigbuild
Setup effort
准备成本
High
Low (needs Docker)
低,但需要 Docker
Low (single binary)
低,只要一个 Zig
Docker required
需要 Docker
NoYesNo
glibc version targeting
glibc 版本可控
NoNoYes
Test execution
测试执行
Needs QEMU
自己配 QEMU
Included
镜像里通常带好
Needs QEMU
自己配 QEMU
macOS → Linux
macOS 到 Linux
Difficult
较麻烦
Easy
简单
Easy
简单
Linux → macOS
Linux 到 macOS
Very difficult
很难
Not supported
不支持
Limited
支持有限
Binary size overhead
额外体积
NoneNoneNone

CI Pipeline: GitHub Actions Matrix
CI 流水线:GitHub Actions 矩阵构建

A production-grade CI workflow that builds for multiple targets often looks like this:
面向生产环境的多目标 CI,通常长得就是下面这样。

# .github/workflows/cross-build.yml
name: Cross-Platform Build

on: [push, pull_request]

env:
  CARGO_TERM_COLOR: always

jobs:
  build:
    strategy:
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
            name: linux-x86_64
          - target: x86_64-unknown-linux-musl
            os: ubuntu-latest
            name: linux-x86_64-static
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-latest
            name: linux-aarch64
            use_cross: true
          - target: x86_64-pc-windows-msvc
            os: windows-latest
            name: windows-x86_64

    runs-on: ${{ matrix.os }}
    name: Build (${{ matrix.name }})

    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - name: Install musl tools
        if: matrix.target == 'x86_64-unknown-linux-musl'
        run: sudo apt-get install -y musl-tools

      - name: Install cross
        if: matrix.use_cross
        run: cargo install cross

      - name: Build (native)
        if: "!matrix.use_cross"
        run: cargo build --release --target ${{ matrix.target }}

      - name: Build (cross)
        if: matrix.use_cross
        run: cross build --release --target ${{ matrix.target }}

      - name: Run tests
        if: "!matrix.use_cross"
        run: cargo test --target ${{ matrix.target }}

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: diag_tool-${{ matrix.name }}
          path: target/${{ matrix.target }}/release/diag_tool*

Application: Multi-Architecture Server Builds
应用场景:多架构服务器构建

The binary currently has no cross-compilation setup. For a diagnostics tool meant to cover diverse server fleets, the following structure is a sensible addition:
当前二进制还没有正式的交叉编译配置。如果它的部署目标是一堆架构和系统都不统一的服务器,那下面这套结构就很值得补上。

my_workspace/
├── .cargo/
│   └── config.toml          ← linker configs per target
├── Cross.toml                ← cross tool configuration
└── .github/workflows/
    └── cross-build.yml       ← CI matrix for 3 targets

Recommended .cargo/config.toml:
建议的 .cargo/config.toml

# .cargo/config.toml for the project

# Release profile optimizations (already in Cargo.toml, shown for reference)
# [profile.release]
# lto = true
# codegen-units = 1
# panic = "abort"
# strip = true

# aarch64 for ARM servers (Graviton, Ampere, Grace)
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

# musl for portable static binaries
[target.x86_64-unknown-linux-musl]
linker = "musl-gcc"

Recommended build targets:
建议重点支持的目标:

TargetUse Case
用途
Deploy To
部署位置
x86_64-unknown-linux-gnuDefault native build
默认原生构建
Standard x86 servers
普通 x86 服务器
x86_64-unknown-linux-muslStatic binary, any distro
静态单文件
Containers, minimal hosts
容器、极简主机
aarch64-unknown-linux-gnuARM servers
ARM 服务器构建
Graviton, Ampere, Grace
Graviton、Ampere、Grace

Key insight: The [profile.release] in the workspace root already has lto = true, codegen-units = 1, panic = "abort", and strip = true. That combination is already extremely suitable for cross-compiled deployment binaries. Add musl on top, and you get a compact single binary with almost no runtime dependency burden.
关键点:workspace 根下的 [profile.release] 已经配好了 lto = truecodegen-units = 1panic = "abort"strip = true。这套配置本来就很适合交叉编译后的部署二进制。再叠一层 musl,基本就能得到一个紧凑、依赖极少的单文件产物。

Troubleshooting Cross-Compilation
交叉编译排障

Symptom
现象
Cause
原因
Fix
处理方式
linker 'aarch64-linux-gnu-gcc' not found
找不到 aarch64-linux-gnu-gcc
Missing cross-linker toolchain
没装交叉链接器
sudo apt install gcc-aarch64-linux-gnu
cannot find -lssl (musl target)
musl 目标找不到 -lssl
System OpenSSL is glibc-linked
系统 OpenSSL 绑定的是 glibc
Use vendored feature: openssl = { version = "0.10", features = ["vendored"] }
改用 vendored OpenSSL。
build.rs runs wrong binary
build.rs 跑错平台逻辑
build.rs runs on HOST, not target
build.rs 运行在 HOST 上
Check CARGO_CFG_TARGET_OS in build.rs, not cfg!(target_os)
build.rs 里读 CARGO_CFG_TARGET_OS
Tests pass locally, fail in cross
本地测试过了,cross 里挂了
Docker image missing test fixtures
容器里缺测试资源
Mount test data via Cross.toml
Cross.toml 把测试数据挂进去。
undefined reference to __cxa_thread_atexit_impl
出现 __cxa_thread_atexit_impl 未定义
Old glibc on target
目标机 glibc 太旧
Use cargo-zigbuild with explicit glibc version
cargo-zigbuild 锁定 glibc 版本。
Binary segfaults on ARM
ARM 上运行直接崩
Compiled for wrong ARM variant
ARM 目标选错了
Verify target triple matches hardware
确认 target triple 和硬件一致。
GLIBC_2.XX not found at runtime
运行时报 GLIBC_2.XX not found
Build machine has newer glibc
构建机 glibc 太新
Use musl or cargo-zigbuild for glibc pinning
用 musl,或者用 cargo-zigbuild 锁版本。

Cross-Compilation Decision Tree
交叉编译决策树

flowchart TD
    START["Need to cross-compile?<br/>需要交叉编译吗?"] --> STATIC{"Static binary?<br/>要静态二进制吗?"}
    
    STATIC -->|Yes<br/>要| MUSL["musl target<br/>--target x86_64-unknown-linux-musl"]
    STATIC -->|No<br/>不要| GLIBC{"Need old glibc?<br/>需要兼容老 glibc 吗?"}
    
    GLIBC -->|Yes<br/>需要| ZIG["cargo-zigbuild<br/>--target x86_64-unknown-linux-gnu.2.17"]
    GLIBC -->|No<br/>不需要| ARCH{"Target arch?<br/>目标架构是什么?"}
    
    ARCH -->|"Same arch<br/>同架构"| NATIVE["Native toolchain<br/>rustup target add + linker"]
    ARCH -->|"ARM/other<br/>ARM 或其他"| DOCKER{"Docker available?<br/>有 Docker 吗?"}
    
    DOCKER -->|Yes<br/>有| CROSS["cross build<br/>Docker-based, zero setup"]
    DOCKER -->|No<br/>没有| MANUAL["Manual sysroot<br/>apt install gcc-aarch64-linux-gnu"]
    
    style MUSL fill:#91e5a3,color:#000
    style ZIG fill:#91e5a3,color:#000
    style CROSS fill:#91e5a3,color:#000
    style NATIVE fill:#e3f2fd,color:#000
    style MANUAL fill:#ffd43b,color:#000

🏋️ Exercises
🏋️ 练习

🟢 Exercise 1: Static musl Binary
🟢 练习 1:构建静态 musl 二进制

Build any Rust binary for x86_64-unknown-linux-musl. Verify it’s statically linked using file and ldd.
为任意 Rust 二进制构建 x86_64-unknown-linux-musl 版本,并用 fileldd 验证它真的是静态链接。

Solution 参考答案
rustup target add x86_64-unknown-linux-musl
cargo new hello-static && cd hello-static
cargo build --release --target x86_64-unknown-linux-musl

# Verify
file target/x86_64-unknown-linux-musl/release/hello-static
# Output: ... statically linked ...

ldd target/x86_64-unknown-linux-musl/release/hello-static
# Output: not a dynamic executable

🟡 Exercise 2: GitHub Actions Cross-Build Matrix
🟡 练习 2:GitHub Actions 交叉构建矩阵

Write a GitHub Actions workflow that builds a Rust project for three targets: x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl, and aarch64-unknown-linux-gnu. Use a matrix strategy.
写一个 GitHub Actions 工作流,用矩阵方式为 x86_64-unknown-linux-gnux86_64-unknown-linux-muslaarch64-unknown-linux-gnu 三个目标构建 Rust 项目。

Solution 参考答案
name: Cross-build
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        target:
          - x86_64-unknown-linux-gnu
          - x86_64-unknown-linux-musl
          - aarch64-unknown-linux-gnu
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}
      - name: Install cross
        run: cargo install cross --locked
      - name: Build
        run: cross build --release --target ${{ matrix.target }}
      - uses: actions/upload-artifact@v4
        with:
          name: binary-${{ matrix.target }}
          path: target/${{ matrix.target }}/release/my-binary

Key Takeaways
本章要点

  • Rust’s rustc is already a cross-compiler — you just need the right target and linker
    rustc 天生就是交叉编译器,关键只是目标库和链接器配对要对。
  • musl produces fully static binaries with zero runtime dependencies — ideal for containers
    musl 能产出几乎零运行时依赖的静态二进制,非常适合容器和复杂部署环境。
  • cargo-zigbuild solves the “which glibc version” problem for enterprise Linux targets
    cargo-zigbuild 专门解决企业 Linux 里最讨厌的 glibc 版本兼容问题。
  • cross is the easiest path for ARM and other exotic targets — Docker handles the sysroot
    cross 是 ARM 和其他异构目标最省事的路线,sysroot 这些脏活都让 Docker 干了。
  • Always test with file and ldd to verify the binary matches your deployment target
    最后一定要用 fileldd 验证产物,别光看它编过了就以为万事大吉。