Compilação cruzada
Compilar de forma cruzada um complemento (addon) NAPI-RS significa produzir um binário .node para uma plataforma-alvo (digamos aarch64-unknown-linux-gnu) em um host diferente (digamos um runner de CI Linux x64). O napi build suporta isso com dois mecanismos recomendados:
--use-napi-crosspara targets Linux glibc em um host Linux x64/arm64 — uma toolchain cruzada gcc baixada do npm, fixada em um piso de glibc 2.17.--cross-compile(-x) para targets Windows MSVC a partir de um host não-Windows (viacargo-xwin) e para targets musl (viacargo-zigbuild). Ele também cobre targets glibc, macOS e FreeBSD através docargo-zigbuildquando--use-napi-crossou um runner nativo não está disponível no seu host.
Targets Android, WASI e OpenHarmony não precisam de nenhuma flag de cross: a CLI configura as toolchains deles a partir de variáveis de ambiente da plataforma (NDK / WASI SDK / OHOS SDK), independentemente de qual flag de cross for passada (se houver). A matriz de decisão abaixo tem o detalhe por target. O NAPI-RS padronizou nas toolchains zig/xwin porque elas são muito mais leves do que a compilação cruzada baseada em contêineres (napi-rs#491).
Esta página diz qual mecanismo usar para o seu par host/target e como lidar com as duas coisas que mais dão errado: versões de glibc e dependências C/C++. Para o que cada flag faz exatamente — comandos executados, variáveis de ambiente, regras de combinação — veja a referência de flags do napi build. O projeto de demonstração cross-build mostra esses mecanismos compilando addons para muitas plataformas a partir de um único host de CI Linux.
Matriz de decisão
A coluna CI gerada mostra o que o workflow de CI gerado pelo napi new faz para aquele target. É a configuração de referência comprovadamente funcional — na dúvida, copie-a.
| Target | CI gerada (configuração de referência) | A partir de Linux x64/arm64 | A partir de macOS | A partir de Windows |
|---|---|---|---|---|
x86_64-apple-darwin |
macos-latest, sem flag |
-x[1] |
sem flag | não suportado |
aarch64-apple-darwin |
macos-latest, sem flag (nativo) |
-x[1:1] |
sem flag | não suportado |
x86_64-pc-windows-msvc |
windows-latest, sem flag |
-x[2] |
-x[2:1] |
sem flag |
i686-pc-windows-msvc |
windows-latest, sem flag |
-x[2:2] |
-x[2:3] |
sem flag |
aarch64-pc-windows-msvc |
windows-latest (x64), sem flag |
-x[2:4] |
-x[2:5] |
sem flag |
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 + etapa de setup do zig |
-x + zig |
-x + zig |
-x + zig |
aarch64-unknown-linux-musl |
ubuntu-latest, -x + etapa de setup do zig |
-x + zig |
-x + zig |
-x + zig |
aarch64-linux-android / armv7-linux-androideabi |
ubuntu-latest, sem flag (NDK pré-instalado) |
sem flag + env do NDK | sem flag + env do NDK | sem flag + env do NDK |
wasm32-wasip1-threads |
ubuntu-latest, sem flag |
sem flag | sem flag | sem flag |
x86_64-unknown-freebsd |
job em VM FreeBSD 15, sem flag (nativo) | -x + zig[4] |
-x + zig[4:1] |
-x + zig[4:2] |
powerpc64le / s390x -unknown-linux-gnu |
nenhum job gerado | --use-napi-cross |
— | — |
loongarch64 / riscv64gc -unknown-linux-gnu |
nenhum job gerado | sem flag + um gcc cruzado que você instala | — | — |
Árvore de decisão
flowchart TD
A[Quero o target T a partir do host H] --> B{T é o triple do host?}
B -- sim --> N0[sem flag]
B -- não --> C{T é Windows MSVC?}
C -- "H é Windows" --> N1["sem flag - o MSVC faz link cruzado de todas as arquiteturas Windows"]
C -- "H é macOS ou Linux" --> X1["-x (o cargo-xwin baixa o SDK da MS)"]
C -- não --> WG{T é Windows GNU ou gnullvm?}
WG -- sim --> WGN["sem flag + mingw/llvm-mingw + LIBNODE_PATH"]
WG -- não --> D{T é macOS?}
D -- "H é macOS" --> N2[sem flag + rustup target]
D -- "H é Linux" --> X2["-x (zig, apenas Rust puro - prefira um runner macOS)"]
D -- não --> E{T é Linux glibc?}
E -- "H é Linux x64/arm64" --> NC["--use-napi-cross (piso de glibc 2.17)"]
E -- "H é macOS ou Windows" --> X3["-x (glibc padrão do zig, não 2.17)"]
E -- não --> F{T é Linux musl?}
F -- sim --> X4["-x + zig no PATH"]
F -- não --> G{T é Android, WASI ou OpenHarmony?}
G -- sim --> N3["sem flag - defina o env do NDK / WASI_SDK / OHOS"]
G -- não --> H2{T é FreeBSD?}
H2 -- sim --> VM["VM FreeBSD (referência) ou -x + zig"]
O caminho do Windows com -x é exclusivo para MSVC. Em um host que não seja Windows, a CLI rejeita um target Windows GNU ou gnullvm explícito antes de ler metadados do Cargo, baixar toolchains ou instalar subcomandos do Cargo. Compile esses targets sem flag de cross e forneça mingw-w64 ou llvm-mingw, além de LIBNODE_PATH; veja a nota sobre Windows em Receitas por target.
As três flags em resumo
--use-napi-cross |
--cross-compile / -x |
--use-cross (legada) |
|
|---|---|---|---|
| Status | Recomendada para targets Linux glibc | Recomendada para targets Windows MSVC a partir de um host não-Windows e para musl; o fallback via zig para glibc/macOS/FreeBSD quando o caminho preferido não está disponível | Legada, não recomendada |
| Mecanismo | Apenas variáveis de ambiente: baixa uma toolchain cruzada gcc do npm (@napi-rs/cross-toolchain) e aponta as env de linker/CC/sysroot para ela; o comando continua sendo cargo build |
Troca o subcomando do cargo: cargo zigbuild para targets não-Windows, ou cargo xwin build para targets Windows MSVC a partir de um host não-Windows; targets Windows GNU/gnullvm explícitos são rejeitados antes da execução de qualquer comando |
Troca o binário: cross build executa a compilação dentro de um contêiner Docker/Podman |
| Targets | Cinco triples Linux glibc: x64, arm64, armv7, ppc64le, s390x | Targets Linux (gnu e musl) e macOS via zig; Windows MSVC via xwin | O que o cross-rs tiver imagens para — apenas Linux, sem imagens para macOS ou Windows MSVC |
| Piso de glibc | 2.17 | O padrão do zig (2.28 para zig 0.12–0.14) | A glibc da imagem (majoritariamente 2.31; variantes :centos 2.17) |
| Pré-requisitos | Host Linux x64/arm64, npm no PATH; a toolchain é baixada e cacheada automaticamente |
zig no PATH para o caminho do zigbuild, clang para o caminho do xwin (a CLI nunca instala nem verifica nenhum dos dois); o subcomando do cargo selecionado (cargo-zigbuild ou cargo-xwin) é instalado automaticamente no primeiro uso |
cross instalado manualmente, mais um Docker >= 20.10 ou Podman >= 3.4 em execução |
| Dependências C/C++ | Compiladas com o gcc embutido; o gcc de aarch64 é antigo — veja a limitação conhecida | Compiladas com zig cc; dependências de frameworks da Apple precisam de um SDK macOS |
Toolchain completa do contêiner — último recurso para build scripts com autotools/CMake |
Escolha exatamente uma flag por build. Qualquer par é rejeitado antes da leitura dos metadados do Cargo, do download de toolchains ou da instalação de subcomandos do Cargo. Veja as regras de combinação.
Receitas por target
Seja qual for o mecanismo escolhido, a biblioteca padrão do Rust para o target precisa estar instalada primeiro: rustup target add <triple>. Cada receita termina com um comando de copiar e colar e uma nota sobre como a CI gerada compila o mesmo target.
Linux glibc (x64, arm64, armv7)
A partir de um host Linux x64/arm64, use --use-napi-cross: ele compila contra a glibc 2.17, então o binário carrega em praticamente qualquer distro glibc. A partir de macOS ou Windows, use -x (o zig roda em ambos) — ao custo do piso de glibc padrão do zig, que é mais alto.
napi build --release --target aarch64-unknown-linux-gnu --use-napi-cross
A CI gerada compila x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu e armv7-unknown-linux-gnueabihf no ubuntu-latest com exatamente essa flag.
Linux musl (x64, arm64)
Use -x a partir de qualquer host, com o zig instalado e no PATH. A CLI adiciona automaticamente -C target-feature=-crt-static ao RUSTFLAGS para targets musl. Não recorra ao musl para consertar um erro GLIBC_x.yy not found — isso é um problema de piso de glibc, veja Versões de glibc.
napi build --release --target aarch64-unknown-linux-musl --cross-compile
A CI gerada compila os dois targets musl no ubuntu-latest com -x, depois de uma etapa setup-zig.
Windows (MSVC) a partir de macOS ou Linux
Use -x: a compilação passa pelo cargo-xwin, que baixa por conta própria a CRT da Microsoft e o SDK do Windows (a licença da Microsoft se aplica). Você precisa do clang instalado (apt install clang / brew install llvm). Para i686, a CLI define XWIN_ARCH=x86 automaticamente. Em um host Windows nenhuma flag é necessária — o MSVC faz link cruzado de x64, x86 e arm64 nativamente.
napi build --release --target x86_64-pc-windows-msvc --cross-compile
A CI gerada compila os três targets MSVC no windows-latest sem flag; use -x quando você não tiver um runner Windows.
E quanto a *-pc-windows-gnu? x86_64-pc-windows-gnu é um target aceito pela CLI desde napi-rs#2935 (o loader JS gerado escolhe o binário win32-x64-gnu quando o próprio Node é um build MINGW); as outras arquiteturas windows-gnu não são aceitas. Não use -x para ele: em um host que não seja Windows, a CLI rejeita essa combinação antes de ler metadados do Cargo ou instalar o cargo-xwin, pois o cargo-xwin suporta somente triples MSVC. Em vez disso, compile-o sem flag de cross: rustup target add x86_64-pc-windows-gnu, instale uma toolchain mingw-w64 (apt install mingw-w64 / brew install mingw-w64) e defina LIBNODE_PATH para um diretório contendo o libnode.dll do Node do MSYS2 — o napi-build linka addons windows-gnu diretamente contra ele. Esse target normalmente é compilado dentro do MSYS2/MINGW, onde os dois pré-requisitos já estão disponíveis. Ainda não existem builds oficiais do Node.js para windows-gnu, então, a menos que você mire especificamente o Node do MSYS2/MINGW, compile para o triple *-pc-windows-msvc — contexto histórico em napi-rs#2001.
macOS
Em um host macOS, nenhuma flag de cross é necessária — adicione a outra arquitetura com rustup target add e compile. A CI gerada também define MACOSX_DEPLOYMENT_TARGET: '10.13' para fixar a versão mínima do macOS. A partir do Linux, -x funciona apenas para crates Rust puros: dependências que linkam frameworks da Apple precisam de um SDK macOS real (SDKROOT), então prefira um runner macOS. Compilar targets macOS a partir do Windows não é suportado.
napi build --release --target aarch64-apple-darwin
A CI gerada compila os dois targets darwin nativamente no macos-latest sem flag.
Android
Sem flag de cross. Em um host que não seja Android, a CLI configura a toolchain a partir de ANDROID_NDK_LATEST_HOME (pré-instalada nos runners ubuntu-latest do GitHub) e para antes do Cargo se a variável estiver ausente. Em um host Android, ela mantém o ambiente da toolchain nativa sem alterações.
napi build --release --target aarch64-linux-android
A CI gerada compila aarch64-linux-android e armv7-linux-androideabi no ubuntu-latest sem flag.
WASI
Sem flag de cross. O link é feito pelo rust-lld que acompanha o rustup. WASI_SDK_PATH é opcional — mas, se definida, precisa apontar para um diretório existente — e a CLI a lê seja uma flag de cross passada ou não.
napi build --release --target wasm32-wasip1-threads
A CI gerada já compila wasm32-wasip1-threads no ubuntu-latest — nenhuma flag necessária.
FreeBSD
Há duas configurações que funcionam. A configuração de referência é a da CI gerada: compilar nativamente dentro de uma VM FreeBSD 15 (via cross-platform-actions/action) em um runner ubuntu-latest — sem flag de cross. O job gerado apenas compila e faz upload do artefato; se você quiser que seus testes também rodem no FreeBSD, adicione essa etapa ao script da VM você mesmo. O FreeBSD também pode ser compilado de forma cruzada a partir do Linux: sob -x ele passa pelo cargo-zigbuild como qualquer outro target não-Windows — execute em um host Linux com o zig instalado. As ressalvas usuais do zig se aplicam: dependências C/C++ são compiladas pelo zig cc (veja Dependências nativas).
napi build --release --target x86_64-unknown-freebsd --cross-compile
A CI gerada compila nativamente na VM FreeBSD 15; o comando -x acima é a alternativa de compilação cruzada a partir de um host Linux.
Versões de glibc
Um binário *-linux-gnu linka a glibc dinamicamente e, na hora de carregar, exige pelo menos a versão de glibc contra a qual foi compilado. Seu binário herda a glibc do host de build como piso: compile em uma distro recente sem flag de cross, e os usuários em distros mais antigas recebem:
Error: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found
Esse erro significa: compile contra uma glibc mais antiga. Ele não significa: mude para um target musl.
--use-napi-crossfixa o piso na glibc 2.17 (linhagem manylinux2014), independentemente da distro do host.-xcompila contra a glibc padrão do zig — 2.28 para zig 0.12–0.14 — e não 2.17.- Fixar uma versão explícita com um sufixo no triple (
--target aarch64-unknown-linux-gnu.2.17) ainda não é suportado: o sufixo quebra a busca de artefatos da CLI. Acompanhe napi-rs#3176.
Verifique o artefato
Antes de publicar, confira se o binário é da arquitetura pretendida e não exige mais glibc do que você mirou:
# 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
Espere no máximo GLIBC_2.17 quando compilado com --use-napi-cross, e o padrão do zig quando compilado com -x.
Dependências nativas
Dependências C/C++ são o obstáculo mais comum na compilação cruzada: crates como ring, openssl-sys ou zstd-sys compilam código C via build script, que precisa de um compilador C que mire o seu target — configurar apenas o rustc não é suficiente.
-
Crates baseados em cc (
ring, etc.): definaTARGET_CC=clang— o clang é inerentemente um compilador cruzado.TARGET_CCtem precedência sobreCC(desde@napi-rs/cli3.0.0-alpha.92).shTARGET_CC=clang napi build --release --target aarch64-unknown-linux-gnu --use-napi-cross -
Limitação conhecida —
aws-lc-sys: o backend padrão do rustls (trazido transitivamente porreqwest,hyper-rustls, etc.) falha ao compilar com--use-napi-crosspara aarch64, porque o gcc embutido é antigo demais (cross-toolchain#4). Contorne comTARGET_CC=clangou use-xno lugar. -
TLS / OpenSSL: prefira rustls com o backend
ring, ou habilite a featurevendoreddoopenssl-syspara que o OpenSSL seja compilado a partir do código-fonte com a toolchain cruzada em vez de linkar bibliotecas do host. -
Último recurso: dependências cujos build scripts rodam autotools ou CMake e capturam binutils do host podem só compilar no caminho legado com contêiner (
--use-cross), onde a toolchain inteira corresponde ao target.
As imagens Docker estão descontinuadas
WARNING
As imagens Docker pré-construídas (ghcr.io/napi-rs/napi-rs/nodejs-rust:*) e
as builds baseadas em *.Dockerfile estão descontinuadas. Migre para
--use-napi-cross (targets Linux glibc) ou -x (targets musl)
em um runner ubuntu-latest comum.
Imagem antiga (ghcr.io/napi-rs/napi-rs/...) |
Nova configuração em um ubuntu-latest comum |
|---|---|
nodejs-rust:lts-debian |
napi build --release --target x86_64-unknown-linux-gnu --use-napi-cross — o mesmo piso de glibc 2.17 que a imagem Debian fornecia |
nodejs-rust:lts-debian-aarch64 |
napi build --release --target aarch64-unknown-linux-gnu --use-napi-cross |
nodejs-rust:lts-alpine |
instale o zig e então napi build --release --target x86_64-unknown-linux-musl -x |
nodejs-rust:lts-debian-zig / lts-alpine-zig |
instale o zig e então napi build --release --target <triple> -x |
Se você ainda usa as imagens, siga duas regras. Primeiro, rode um napi build --target <triple> puro dentro delas, sem flags de cross — a imagem já fixa a toolchain e a glibc, e adicionar flags de cross por cima disso é o que quebra as builds. Segundo, fixe a imagem por digest (nodejs-rust@sha256:...), porque as tags lts-* mudam com o tempo.
Adicionar um target a um projeto existente
- Adicione o triple a
targetsna sua configuraçãonapi(veja napi config). - Execute
napi create-npm-dirspara gerar a estrutura dos pacotes npm por plataforma. - Adicione uma entrada na matriz de CI para o target — copie o job mais próximo da CI gerada (a matriz de decisão diz o runner e a flag).
- Depois de atualizar o
@napi-rs/cli— especialmente entre versões major — gere novamente o seu workflow de CI a partir de um scaffold novo donapi newem vez de remendá-lo, para que ele não se desvie do que a CLI espera.
Veja também
- Referência de flags de compilação cruzada do
napi build— comandos exatos, contrato de variáveis de ambiente, regras de combinação - FAQ: Compilar para Linux alpine — especificidades do musl
Patrocine nossa equipe
https://github.com/sponsors/napi-rs/
Integrar e configurar corretamente toolchains de compilação multiplataforma na comunidade open source pode ser muito tedioso e trabalhoso. Entender esses parâmetros de compilação e resolver bugs potenciais pode consumir muito tempo e ser difícil de testar.
Agradecimentos especiais ao membro da nossa equipe @messense, que vem trabalhando no cargo-xwin e no cargo-zigbuild, o que nos permitiu compilar addons nativos do Windows em sistemas não-Windows.
Se você usa o NAPI-RS na sua empresa, considere patrocinar nossa equipe para apoiar o desenvolvimento do NAPI-RS. Ficaremos muito gratos pelo seu apoio.
o zig consegue linkar binários macOS apenas para crates Rust puros — dependências que linkam frameworks da Apple precisam de um SDK macOS real (
SDKROOT). Prefira um runner macOS. ↩︎ ↩︎o cargo-xwin baixa por conta própria a CRT da Microsoft e o SDK do Windows; a licença da Microsoft se aplica. Ele precisa do
clanginstalado (por exemplo,brew install llvmno macOS). ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎--use-napi-crosssó funciona em hosts Linux x64/arm64 (a toolchain baixada é um binário Linux); portanto, a partir de macOS ou Windows use-x— mas o piso de glibc passa a ser o padrão do zig, não 2.17. Veja Versões de glibc. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎Sob
-x, o FreeBSD passa pelo cargo-zigbuild como qualquer outro target não-Windows — tenha ozignoPATH; hosts Linux são a rota mais comprovada na prática. Se você quiser que seus testes também rodem no FreeBSD, execute-os em uma VM FreeBSD. Veja a receita do FreeBSD. ↩︎ ↩︎ ↩︎