Skip to content

交叉编译

交叉编译一个 NAPI-RS addon,指的是在另一种宿主机(比如 Linux x64 CI runner)上为目标平台(比如 aarch64-unknown-linux-gnu)构建 .node 二进制文件。napi build 通过两种推荐机制支持交叉编译:

  • --use-napi-cross:用于在 Linux x64/arm64 宿主机上构建 Linux glibc 目标 —— 从 npm 下载 gcc 交叉工具链,把 glibc 下限固定在 2.17。
  • --cross-compile-x):用于从非 Windows 宿主机构建 Windows MSVC 目标(通过 cargo-xwin),以及构建 musl 目标(通过 cargo-zigbuild)。当你的宿主机上无法使用 --use-napi-cross 或原生 runner 时,它还能通过 cargo-zigbuild 覆盖 glibc、macOS 和 FreeBSD 目标。

Android、WASI 和 OpenHarmony 目标完全不需要交叉编译标志:无论是否传入(以及传入哪个)交叉编译标志,CLI 都会根据平台环境变量(NDK / WASI SDK / OHOS SDK)配置它们的工具链。下方的决策矩阵给出了每个目标的具体做法。NAPI-RS 之所以标准化到 zig/xwin 工具链,是因为它们远比基于容器的交叉编译轻量(napi-rs#491)。

本页告诉你针对你的宿主机/目标组合应该使用哪种机制,以及如何应对最常出问题的两件事:glibc 版本和 C/C++ 依赖。关于每个标志的确切行为 —— 派生的命令、环境变量、组合规则 —— 参见 napi build 标志参考cross-build 演示项目展示了如何用这些机制在单个 Linux CI 宿主机上为众多平台构建 addon。

决策矩阵

生成的 CI 一列展示了 napi new 生成的 CI workflow 对该目标采用的做法。它是已知可用的参考配置 —— 拿不准时,照抄它即可。

目标 生成的 CI(参考配置) 从 Linux x64/arm64 从 macOS 从 Windows
x86_64-apple-darwin macos-latest,不加标志 -x[1] 不加标志 不支持
aarch64-apple-darwin macos-latest,不加标志(原生) -x[1:1] 不加标志 不支持
x86_64-pc-windows-msvc windows-latest,不加标志 -x[2] -x[2:1] 不加标志
i686-pc-windows-msvc windows-latest,不加标志 -x[2:2] -x[2:3] 不加标志
aarch64-pc-windows-msvc windows-latest(x64),不加标志 -x[2:4] -x[2:5] 不加标志
x86_64-unknown-linux-gnu ubuntu-latest--use-napi-cross --use-napi-cross -x[3] -x[3:1]
aarch64-unknown-linux-gnu ubuntu-latest--use-napi-cross --use-napi-cross -x[3:2] -x[3:3]
armv7-unknown-linux-gnueabihf ubuntu-latest--use-napi-cross --use-napi-cross -x[3:4] -x[3:5]
x86_64-unknown-linux-musl ubuntu-latest-x + zig 安装步骤 -x + zig -x + zig -x + zig
aarch64-unknown-linux-musl ubuntu-latest-x + zig 安装步骤 -x + zig -x + zig -x + zig
aarch64-linux-android / armv7-linux-androideabi ubuntu-latest,不加标志(预装 NDK) 不加标志 + NDK 环境变量 不加标志 + NDK 环境变量 不加标志 + NDK 环境变量
wasm32-wasip1-threads ubuntu-latest,不加标志 不加标志 不加标志 不加标志
x86_64-unknown-freebsd FreeBSD 15 虚拟机任务,不加标志(原生) -x + zig[4] -x + zig[4:1] -x + zig[4:2]
powerpc64le / s390x -unknown-linux-gnu 无生成的任务 --use-napi-cross
loongarch64 / riscv64gc -unknown-linux-gnu 无生成的任务 不加标志 + 需自行安装的交叉 gcc

决策树

mermaid
flowchart TD
    A["我想在宿主机 H 上构建目标 T"] --> B{"T 就是宿主机的 triple 吗?"}
    B -- "是" --> N0["不加标志"]
    B -- "否" --> C{"T 是 Windows MSVC 吗?"}
    C -- "H 是 Windows" --> N1["不加标志 - MSVC 原生交叉链接所有 Windows 架构"]
    C -- "H 是 macOS 或 Linux" --> X1["-x (cargo-xwin 下载 MS SDK)"]
    C -- "否" --> WG{"T 是 Windows GNU 或 gnullvm 吗?"}
    WG -- "是" --> WGN["不加标志 + mingw/llvm-mingw + LIBNODE_PATH"]
    WG -- "否" --> D{"T 是 macOS 吗?"}
    D -- "H 是 macOS" --> N2["不加标志 + rustup target"]
    D -- "H 是 Linux" --> X2["-x (zig, 仅限纯 Rust - 优先用 macOS runner)"]
    D -- "否" --> E{"T 是 Linux glibc 吗?"}
    E -- "H 是 Linux x64/arm64" --> NC["--use-napi-cross (glibc 2.17 下限)"]
    E -- "H 是 macOS 或 Windows" --> X3["-x (zig 默认 glibc, 而非 2.17)"]
    E -- "否" --> F{"T 是 Linux musl 吗?"}
    F -- "是" --> X4["-x + PATH 上的 zig"]
    F -- "否" --> G{"T 是 Android、WASI 或 OpenHarmony 吗?"}
    G -- "是" --> N3["不加标志 - 设置 NDK / WASI_SDK / OHOS 环境变量"]
    G -- "否" --> H2{"T 是 FreeBSD 吗?"}
    H2 -- "是" --> VM["FreeBSD 虚拟机 (参考配置) 或 -x + zig"]

-x 的 Windows 路径只支持 MSVC。在非 Windows 宿主机上,CLI 会在读取 Cargo 元数据、下载工具链或安装 Cargo 子命令之前,拒绝显式指定的 Windows GNU 或 gnullvm 目标。这些目标应不加交叉编译标志,并自行提供 mingw-w64 或 llvm-mingw 以及 LIBNODE_PATH;参见各目标的构建方法中的 Windows 说明。

三个标志一览

--use-napi-cross --cross-compile / -x --use-cross(遗留)
状态 Linux glibc 目标的推荐方案 从非 Windows 宿主机构建 Windows MSVC 目标以及构建 musl 目标的推荐方案;当首选路径不可用时,是 glibc/macOS/FreeBSD 的 zig 兜底方案 遗留方案,不推荐
机制 只设置环境变量:从 npm 下载 gcc 交叉工具链(@napi-rs/cross-toolchain),并把 linker/CC/sysroot 环境变量指向它;命令仍然是 cargo build 替换 cargo 子命令:非 Windows 目标用 cargo zigbuild,从非 Windows 宿主机构建 Windows MSVC 目标用 cargo xwin build;显式指定的 Windows GNU/gnullvm 目标会在命令运行前被拒绝 替换二进制:cross build 在 Docker/Podman 容器内运行构建
目标 五个 Linux glibc triple:x64、arm64、armv7、ppc64le、s390x 通过 zig 支持 Linux(gnu 和 musl)与 macOS 目标;通过 xwin 支持 Windows MSVC 取决于 cross-rs 提供了哪些镜像 —— 仅限 Linux,没有 macOS 或 Windows MSVC 镜像
glibc 下限 2.17 zig 的默认值(zig 0.12–0.14 为 2.28) 镜像自带的 glibc(大多为 2.31;:centos 变体为 2.17)
前置条件 Linux x64/arm64 宿主机,PATH 上有 npm;工具链会自动下载并缓存 zigbuild 路径需要 PATH 上有 zig,xwin 路径需要 clang(CLI 从不安装或检查这两者);所选的 cargo 子命令(cargo-zigbuild 或 cargo-xwin)会在首次使用时自动安装 手动安装 cross,并有运行中的 Docker >= 20.10 或 Podman >= 3.4
C/C++ 依赖 用自带的 gcc 编译;aarch64 的 gcc 较老 —— 参见已知限制 zig cc 编译;依赖 Apple framework 的依赖项需要 macOS SDK 完整的容器工具链 —— autotools/CMake 构建脚本的最后手段

每次构建只能选用其中一个标志。任意两个标志的组合都会在读取 Cargo 元数据、下载工具链或安装 Cargo 子命令之前被拒绝。参见组合规则

各目标的构建方法

无论选择哪种机制,都必须先安装目标的 Rust 标准库:rustup target add <triple>。每个构建方法都以一条可直接复制粘贴的命令结尾,并附带说明生成的 CI 是如何构建同一目标的。

Linux glibc (x64, arm64, armv7)

在 Linux x64/arm64 宿主机上使用 --use-napi-cross:它针对 glibc 2.17 构建,因此二进制几乎可以在所有 glibc 发行版上加载。在 macOS 或 Windows 上改用 -x(zig 在两者上都能运行)—— 代价是 glibc 下限变成 zig 更高的默认值。

sh
napi build --release --target aarch64-unknown-linux-gnu --use-napi-cross

生成的 CI 正是用这个标志在 ubuntu-latest 上构建 x86_64-unknown-linux-gnuaarch64-unknown-linux-gnuarmv7-unknown-linux-gnueabihf

Linux musl (x64, arm64)

在任意宿主机上使用 -x,并确保安装了 zig 且在 PATH 上。对于 musl 目标,CLI 会自动向 RUSTFLAGS 追加 -C target-feature=-crt-static。不要为了解决 GLIBC_x.yy not found 错误而改用 musl —— 那是 glibc 下限的问题,参见 Glibc 版本

sh
napi build --release --target aarch64-unknown-linux-musl --cross-compile

生成的 CI 在 setup-zig 步骤之后,用 -xubuntu-latest 上构建两个 musl 目标。

从 macOS 或 Linux 构建 Windows (MSVC)

使用 -x:构建会走 cargo-xwin,它会自行下载 Microsoft CRT 和 Windows SDK(受 Microsoft 许可条款约束)。你需要安装 clangapt install clang / brew install llvm)。对于 i686,CLI 会自动设置 XWIN_ARCH=x86。在 Windows 宿主机上则完全不需要标志 —— MSVC 原生就能交叉链接 x64、x86 和 arm64。

sh
napi build --release --target x86_64-pc-windows-msvc --cross-compile

生成的 CI 在 windows-latest 上不加标志构建全部三个 MSVC 目标;没有 Windows runner 时再使用 -x

*-pc-windows-gnu 呢?自 napi-rs#2935 起,x86_64-pc-windows-gnu 是 CLI 接受的目标(当 Node 本身是 MINGW 构建时,生成的 JS loader 会选择 win32-x64-gnu 二进制);其他 windows-gnu 架构不被接受。不要对它使用 -x:在非 Windows 宿主机上,CLI 会在读取 Cargo 元数据或安装 cargo-xwin 之前拒绝这个组合,因为 cargo-xwin 只支持 MSVC triple。请改为不加交叉编译标志构建:rustup target add x86_64-pc-windows-gnu,安装 mingw-w64 工具链(apt install mingw-w64 / brew install mingw-w64),并把 LIBNODE_PATH 设置为一个包含 MSYS2 版 Node 提供的 libnode.dll 的目录 —— napi-build 会让 windows-gnu addon 直接链接它。这个目标通常在 MSYS2/MINGW 环境内构建,那里两个前置条件都已具备。目前仍然没有官方的 Node.js windows-gnu 构建,所以除非你专门面向 MSYS2/MINGW 的 Node,否则请改为构建 *-pc-windows-msvc triple —— 历史背景见 napi-rs#2001

macOS

在 macOS 宿主机上不需要交叉编译标志 —— 用 rustup target add 添加另一个架构然后构建即可。生成的 CI 还会设置 MACOSX_DEPLOYMENT_TARGET: '10.13' 来固定最低 macOS 版本。从 Linux 构建时,-x 只对纯 Rust crate 有效:链接 Apple framework 的依赖需要真正的 macOS SDK(SDKROOT),因此优先使用 macOS runner。不支持从 Windows 构建 macOS 目标。

sh
napi build --release --target aarch64-apple-darwin

生成的 CI 在 macos-latest 上不加标志原生构建两个 darwin 目标。

Android

不需要交叉编译标志。在非 Android 宿主机上,CLI 根据 ANDROID_NDK_LATEST_HOME 环境变量(GitHub ubuntu-latest runner 上预装)配置工具链;如果变量缺失,CLI 会在启动 Cargo 前停止。在 Android 宿主机上,CLI 保留原生工具链环境,不做修改。

sh
napi build --release --target aarch64-linux-android

生成的 CI 在 ubuntu-latest 上不加标志构建 aarch64-linux-androidarmv7-linux-androideabi

WASI

不需要交叉编译标志。链接由 rustup 自带的 rust-lld 处理。WASI_SDK_PATH 是可选的 —— 但一旦设置,就必须指向一个存在的目录 —— 而且无论是否传入交叉编译标志,CLI 都会读取它。

sh
napi build --release --target wasm32-wasip1-threads

生成的 CI 已经在 ubuntu-latest 上构建 wasm32-wasip1-threads —— 无需标志。

FreeBSD

有两种可用的配置。参考配置是生成的 CI 的做法:在 ubuntu-latest runner 上借助 cross-platform-actions/action 在 FreeBSD 15 虚拟机内原生构建 —— 不加交叉编译标志。生成的任务只负责构建并上传产物;如果你还想让测试也在 FreeBSD 上运行,需要自己把那个步骤加进虚拟机脚本。FreeBSD 也可以从 Linux 交叉编译:在 -x 下它和其他所有非 Windows 目标一样通过 cargo-zigbuild 构建 —— 在装有 zig 的 Linux 宿主机上运行。zig 的常见注意事项同样适用:C/C++ 依赖由 zig cc 编译(参见原生依赖)。

sh
napi build --release --target x86_64-unknown-freebsd --cross-compile

生成的 CI 在 FreeBSD 15 虚拟机中原生构建;上面的 -x 命令是从 Linux 宿主机交叉编译的替代方案。

Glibc 版本

*-linux-gnu 二进制动态链接 glibc,加载时要求系统 glibc 至少达到构建时所用的版本。你的二进制会继承构建宿主机的 glibc 作为下限:在最新的发行版上不加交叉编译标志构建,旧发行版上的用户就会看到:

Error: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found

这个错误的含义是:请针对更老的 glibc 构建。它意味着:换成 musl 目标。

  • --use-napi-cross 把下限固定在 glibc 2.17(与 manylinux2014 一致),与宿主机发行版无关。
  • -x 针对 zig 的默认 glibc 构建 —— zig 0.12–0.14 为 2.28 —— 而不是 2.17。
  • 通过给 triple 加后缀来固定明确版本(--target aarch64-unknown-linux-gnu.2.17目前尚不支持:该后缀会破坏 CLI 的产物查找。请关注 napi-rs#3176

校验构建产物

发布之前,确认二进制属于预期的架构、所需的 glibc 版本没有超出你的目标范围:

sh
# CPU architecture and file format
file my-package.linux-arm64-gnu.node

# Highest glibc symbol version the binary requires
objdump -T my-package.linux-arm64-gnu.node | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -1

--use-napi-cross 构建时最高应为 GLIBC_2.17,用 -x 构建时应为 zig 的默认值。

原生依赖

C/C++ 依赖是交叉编译中最常见的障碍:ringopenssl-syszstd-sys 之类的 crate 会通过构建脚本编译 C 源码,这需要一个面向你的目标的 C 编译器 —— 只配置 rustc 是不够的。

  • 基于 cc 的 crate(ring 等):设置 TARGET_CC=clang —— clang 天生就是交叉编译器。TARGET_CC 的优先级高于 CC(自 @napi-rs/cli 3.0.0-alpha.92 起)。

    sh
    TARGET_CC=clang napi build --release --target aarch64-unknown-linux-gnu --use-napi-cross
    
  • 已知限制 —— aws-lc-sys:默认的 rustls 后端(由 reqwesthyper-rustls 等间接引入)在用 --use-napi-cross 为 aarch64 构建时会失败,因为自带的 gcc 太老(cross-toolchain#4)。可以用 TARGET_CC=clang 绕过,或改用 -x

  • TLS / OpenSSL:优先使用带 ring 后端的 rustls,或启用 openssl-sysvendored feature,让 OpenSSL 用交叉工具链从源码编译,而不是链接宿主机的库。

  • 最后手段:构建脚本运行 autotools 或 CMake 并连带使用宿主机 binutils 的依赖,可能只有在遗留的容器路径(--use-cross)中才能构建成功 —— 那里的整套工具链都与目标一致。

Docker 镜像已弃用

WARNING

预构建的 Docker 镜像(ghcr.io/napi-rs/napi-rs/nodejs-rust:*)以及基于 *.Dockerfile 的构建已弃用。请迁移到普通 ubuntu-latest runner 上的 --use-napi-cross(Linux glibc 目标)或 -x(musl 目标)。

旧镜像(ghcr.io/napi-rs/napi-rs/... 普通 ubuntu-latest 上的新配置
nodejs-rust:lts-debian napi build --release --target x86_64-unknown-linux-gnu --use-napi-cross —— 与 Debian 镜像提供的相同的 glibc 2.17 下限
nodejs-rust:lts-debian-aarch64 napi build --release --target aarch64-unknown-linux-gnu --use-napi-cross
nodejs-rust:lts-alpine 安装 zig,然后 napi build --release --target x86_64-unknown-linux-musl -x
nodejs-rust:lts-debian-zig / lts-alpine-zig 安装 zig,然后 napi build --release --target <triple> -x

如果你仍在使用这些镜像,请遵守两条规则。第一,在镜像内只运行普通的 napi build --target <triple>不要加任何交叉编译标志 —— 镜像已经固定了工具链和 glibc,在此之上再叠加交叉编译标志正是构建失败的原因。第二,按摘要固定镜像版本(nodejs-rust@sha256:...),因为 lts-* 标签会随时间变化。

向现有项目添加新目标

  1. 把 triple 加入 napi 配置的 targets(参见 napi 配置)。
  2. 运行 napi create-npm-dirs 生成各平台的 npm 包。
  3. 为该目标添加一条 CI matrix 记录 —— 从生成的 CI 里复制最接近的任务(决策矩阵告诉你该用哪个 runner 和标志)。
  4. 升级 @napi-rs/cli 之后 —— 尤其是跨大版本升级时 —— 请用全新的 napi new 脚手架重新生成 CI workflow,而不是在旧文件上打补丁,以免它偏离 CLI 的预期。

另请参阅

赞助我们的团队

https://github.com/sponsors/napi-rs/

在开源社区中集成并正确配置跨平台编译工具链是一件非常繁琐、非常耗费人力的事情。理解这些编译参数并解决其中潜在的 bug 非常耗时,而且很难测试。 特别感谢我们的团队成员 @messense,他一直在开发 cargo-xwincargo-zigbuild,正是它们让我们能够在非 Windows 系统上构建 Windows 原生 addon。

如果你的公司正在使用 NAPI-RS,请考虑赞助我们的团队,支持 NAPI-RS 的开发。我们将非常感谢你的支持。


  1. zig 只能为纯 Rust crate 链接 macOS 二进制 —— 链接 Apple framework 的依赖需要真正的 macOS SDK(SDKROOT)。优先使用 macOS runner。 ↩︎ ↩︎

  2. cargo-xwin 会自行下载 Microsoft CRT 和 Windows SDK;受 Microsoft 许可条款约束。它需要安装 clang(例如在 macOS 上 brew install llvm)。 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  3. --use-napi-cross 只能在 Linux x64/arm64 宿主机上工作(下载的工具链是 Linux 二进制),因此在 macOS 或 Windows 上请改用 -x —— 但 glibc 下限会变成 zig 的默认值,而不是 2.17。参见 Glibc 版本↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  4. -x 下,FreeBSD 和其他所有非 Windows 目标一样通过 cargo-zigbuild 构建 —— 确保 PATH 上有 zig;Linux 宿主机是经过最多实战检验的路径。如果你还想让测试也在 FreeBSD 上运行,请在 FreeBSD 虚拟机中运行它们。参见 FreeBSD 构建方法↩︎ ↩︎ ↩︎