在 Windows 上写 Rust,然后编译到 Linux:手动、cross、cargo-zigbuild 到底怎么选
在 Windows 上写 Rust,然后编译到 Linux:手动、cross、cargo-zigbuild 到底怎么选
很多人第一次做 Rust 交叉编译时,都会从同一个命令开始:
rustup target add x86_64-unknown-linux-musl
然后信心满满地敲下:
cargo build --release --target x86_64-unknown-linux-musl
有时候它真的能过。
然后你换了一个真实项目,里面有 openssl-sys、ring、sqlite3-sys、zstd-sys,编译器突然开始报一堆 linker、header、library 找不到的错误。你会怀疑是不是 Rust 的交叉编译不成熟。
其实不是。
问题在于,rustup target add 只安装目标平台的 Rust 标准库。Rust 官方文档也说得很清楚:交叉编译通常还需要额外工具,尤其是 linker。比如编译 Android target,还需要 Android NDK。(Rust语言)
所以 Rust 交叉编译真正要回答的不是一个问题,而是两个问题:
Rust 能不能为目标平台生成代码?
最终能不能把这些代码链接成一个能运行的二进制?
第一个问题通常比较简单。第二个问题才是大坑。
先把选择说清楚
如果你只是想在 Windows 上开发,然后把程序部署到 Linux 服务器,大概有四条路线。
| 路线 | 适合场景 | 优点 | 麻烦点 |
|---|---|---|---|
手动 cargo build --target | 纯 Rust 项目、musl target、你愿意自己配 linker | 最透明,没有额外封装 | linker、sysroot、native deps 都要自己处理 |
cross | 需要稳定复现构建环境,尤其是 Linux GNU/ARM target | Docker/Podman 封装工具链,团队和 CI 里好用 | 依赖容器环境,遇到特殊 C 库时要定制镜像 |
cargo-zigbuild | 想少配 linker,又不想上 Docker | 用 Zig 做 linker,Linux GNU/musl 很方便,还能指定 glibc 版本 | 不是万能,native headers/libs 仍可能要配置 |
| WSL/Linux CI | 生产构建、依赖复杂、你不想赌 | 最接近目标环境 | 不算“纯 Windows 本地构建” |
我个人的排序很简单:
纯 Rust,先试 musl。
依赖复杂,用 WSL 或 Linux CI。
想在本机少折腾 linker,试 cargo-zigbuild。
要团队可复现构建和跨架构测试,用 cross。
手动路线:最干净,也最容易暴露问题
手动交叉编译的好处是你知道每一步发生了什么。
比如目标是 Linux musl:
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl
如果项目足够干净,这就结束了。
但“足够干净”这个条件很重要。没有 native 依赖的 CLI、小服务、agent 工具,通常比较顺。可一旦项目里有 C/C++ 依赖,Rust 之外的世界就进来了。
这时候你需要的可能是:
# .cargo/config.toml
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
Cargo 本身支持按 target 配置 linker、runner、rustflags 等项,这也是手动交叉编译的标准做法。(Rust语言)
手动路线适合你想把事情弄明白的时候。它也适合比较简单的项目。
但如果你在 Windows 上手搓 x86_64-unknown-linux-gnu,通常会开始痛苦。
因为 GNU Linux target 不是只有 Rust target。它还牵涉 glibc、Linux sysroot、动态链接器路径、目标平台库版本。你可以配出来,但除非你有明确理由,不然我不建议把这件事变成日常工作流。
cross:把目标平台环境放进容器里
cross 的思路和手动路线不一样。
它不要求你在宿主机上装一堆目标平台工具链,而是用 Docker 或 Podman 提供构建环境。它的 README 里直接把自己描述为“zero setup cross compilation and cross testing of Rust crates”,并说明它会提供交叉编译所需的环境、工具链和交叉编译库。(GitHub)
安装方式:
cargo install cross --git https://github.com/cross-rs/cross
使用方式几乎就是把 cargo 换成 cross:
cross build --release --target aarch64-unknown-linux-gnu
测试也可以:
cross test --target aarch64-unknown-linux-gnu
cross 的 CLI 和 Cargo 很像,但它依赖 Docker 或 Podman。官方 README 也写明了 Docker/Podman 是依赖项,Docker 需要 20.10 或更高版本,Podman 需要 3.4.0 或更高版本。(GitHub)
这就是它的核心价值:不要让每个开发者都在自己机器上配一套神秘 toolchain。把构建环境放进容器,团队里每个人和 CI 尽量用同一套东西。
它尤其适合这些场景:
你要编 aarch64-unknown-linux-gnu。
你要在 CI 里产出多架构 Linux binary。
你想跑 cross test,而不只是 build。
项目有 native 依赖,但能通过 apt 包或自定义镜像解决。
例如项目依赖 OpenSSL,cross 可以在 Cross.toml 里给目标平台装依赖:
[target.aarch64-unknown-linux-gnu]
pre-build = [
"dpkg --add-architecture $CROSS_DEB_ARCH",
"apt-get update && apt-get --assume-yes install libssl-dev:$CROSS_DEB_ARCH"
]
cross 的文档说明可以在项目根目录放 Cross.toml,也可以通过 CROSS_CONFIG 指定配置文件;如果默认镜像不够,还可以用 pre-build、自定义 Dockerfile 或指定 image。(GitHub)
不过 cross 也不是银弹。
它依赖容器。Windows 上通常意味着 Docker Desktop 或 WSL2。
它的默认镜像覆盖常见情况,但不可能覆盖所有 C/C++ 库。
如果你的依赖很偏,最后还是要写 Cross.toml 或维护自己的 Dockerfile。
但这不算缺点,更像是代价。你用容器换来了可复现构建。
cargo-zigbuild:用 Zig 把 linker 这件事变轻
cargo-zigbuild 是另一条很有意思的路。
它不是把整个构建环境放进 Docker,而是让 Cargo 用 Zig 作为 linker。项目 README 的描述很直接:用 Zig 作为 linker 来编译 Cargo 项目,让交叉编译更容易。(GitHub)
安装:
cargo install --locked cargo-zigbuild
或者:
pip install cargo-zigbuild
后者会顺带安装 ziglang。官方 README 也列出了这两种安装方式。(GitHub)
典型使用流程是:
rustup target add aarch64-unknown-linux-gnu
cargo zigbuild --release --target aarch64-unknown-linux-gnu
对很多 Linux target 来说,它比你自己找 cross GCC、sysroot、linker 要省心不少。
它最吸引人的地方之一,是可以指定最低 glibc 版本。比如你想编一个 ARM64 Linux GNU binary,并希望兼容 glibc 2.17:
cargo zigbuild --release --target aarch64-unknown-linux-gnu.2.17
cargo-zigbuild 文档里说明,GNU target 默认 glibc 版本会随 Zig 版本变化,也支持在 target 后面加 glibc 版本后缀来指定最低版本。文档同时提醒,这个行为不一定完全等同于在真实构建宿主机上动态链接某个 glibc 版本。(GitHub)
这句话很重要。
cargo-zigbuild 能减轻 linker 和 libc 的配置压力,但它没有消灭“目标环境测试”这件事。你编出来之后,仍然应该在目标 Linux 发行版或兼容容器里跑一下。
还有几个坑要提前知道。
如果不传 --target,cargo zigbuild 实际上不会用 Zig。
某些 RUSTFLAGS,比如 -C linker,会让它绕开 Zig。
它使用 zig cc -nostdinc,所以有时找不到系统 headers 或 libraries,需要通过 CFLAGS、RUSTFLAGS 等方式显式告诉它路径。
项目 README 也写明了 caveats:目前主要支持 Linux 和 macOS targets,其他目标平台需要自己确认能否工作。(GitHub)
所以我会把 cargo-zigbuild 放在一个很现实的位置:
它不是比 cross 更“高级”的方案。
它只是换了一种 tradeoff。
不用 Docker,轻。
链接 Linux GNU/musl target,舒服。
但遇到复杂 native 依赖,它还是会把问题还给你。
cross 和 cargo-zigbuild 怎么选
如果只看使用体验,两个工具都像是在帮你逃离 linker 地狱。但它们解决问题的方式完全不同。
cross 的答案是:我给你一个目标平台构建环境。
cargo-zigbuild 的答案是:我给你一个更会交叉链接的 linker。
所以选择也很清楚。
| 情况 | 更推荐 |
|---|---|
| 团队开发,CI 要稳定复现 | cross |
| 要跑跨架构测试 | cross |
| 目标是 Linux GNU,且你不想手配 glibc toolchain | cargo-zigbuild |
| 不想装 Docker | cargo-zigbuild |
| native 依赖可以通过 apt 安装 | cross |
| 想控制最低 glibc 版本 | cargo-zigbuild |
| 依赖非常复杂,最终部署环境固定 | WSL/Linux CI |
| 纯 Rust 小项目 | 手动 cargo build --target |
举个例子。
如果你在 Windows 上写一个纯 Rust CLI,要发给 Linux x86_64 服务器:
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl
够了。别上复杂工具。
如果你要发 aarch64-unknown-linux-gnu,并且项目里没有太多奇怪的 C 依赖:
rustup target add aarch64-unknown-linux-gnu
cargo zigbuild --release --target aarch64-unknown-linux-gnu
可以先试 cargo-zigbuild。
如果项目依赖 OpenSSL、libpq、libsqlite3、libclang 这类东西,而且你希望 CI 和本地尽量一致:
cross build --release --target aarch64-unknown-linux-gnu
然后把依赖写进 Cross.toml。
如果这是生产环境核心服务:
用 Linux CI 或 WSL 构建。
不要让发布流程建立在“我本机刚好能编过”上。
Windows 到 Linux:我的推荐路径
回到最开始的问题:Windows 上开发 Rust,能不能编译 Linux target?
能。
但我会按这个顺序做判断:
第一步,看是不是纯 Rust 项目。
如果是,先试 musl:
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl
第二步,看是不是 GNU Linux target。
如果你明确要 glibc,比如要兼容某些 Linux 动态库,可以试:
rustup target add x86_64-unknown-linux-gnu
cargo zigbuild --release --target x86_64-unknown-linux-gnu
如果你还要控制 glibc 版本:
cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.17
第三步,看 native 依赖复杂不复杂。
复杂的话,别继续在 Windows 宿主机上堆环境变量。用 cross,或者直接用 Linux CI。
cross build --release --target x86_64-unknown-linux-gnu
第四步,在目标环境测试。
这一步跳不过去。交叉编译成功只代表“产物被链接出来了”,不代表它一定能在你的生产机器上跑。
OpenSSL 是分水岭
Rust 交叉编译里,OpenSSL 经常是第一个让人破防的依赖。
如果你只是用 HTTP 客户端,优先考虑 Rustls:
reqwest = { version = "...", default-features = false, features = ["rustls-tls"] }
这样可以少掉很多目标平台 OpenSSL headers/libs 的麻烦。
如果你必须用 OpenSSL,那就把它当成“需要目标平台系统依赖”的项目来处理。
这时候 cross 的 pre-build 或自定义镜像会比手动在 Windows 上拼路径靠谱。cargo-zigbuild 也可能能过,但你仍然要处理 headers 和 libraries 的查找问题。
重新整理一下结论
Rust 的交叉编译不是一句“支持”或“不支持”能说完的。
你真正要选的是构建策略。
纯 Rust 项目
优先 cargo build --target
想少配 linker,不想用 Docker
试 cargo-zigbuild
团队构建、多架构 CI、需要 cross test
用 cross
依赖复杂、生产发布
用 WSL 或 Linux CI
我现在会这样记:
rustup target add 解决 Rust 标准库。
手动 linker 解决链接。
cargo-zigbuild 用 Zig 帮你减轻链接负担。
cross 用容器帮你固定目标平台构建环境。
WSL 和 CI 让你尽量接近真实生产环境。
所以,不要只问“Windows 能不能编 Linux”。
更好的问题是:
我要的是 musl 还是 gnu?
我的项目是不是纯 Rust?
native 依赖有多复杂?
我想要本机方便,还是团队可复现?
这个 binary 最终要跑在哪个 Linux 版本上?
这些问题答清楚,工具自然就选出来了。