Cross-compilation para ARM, WebAssembly e outros targets

1. Introdução à Cross-compilation em Rust

Cross-compilation é o processo de gerar código binário para uma plataforma diferente daquela em que o compilador está sendo executado. Em Rust, isso é essencial por diversas razões: dispositivos embarcados com recursos limitados, servidores ARM em datacenters, módulos WebAssembly para navegadores, e aplicativos móveis para Android e iOS.

A diferença fundamental entre compilação nativa e cruzada está no conceito de target triple. Um target triple segue o formato <arch>-<vendor>-<os>-<abi> (ex: arm-unknown-linux-gnueabihf). Rust suporta oficialmente dezenas de targets, incluindo ARM (várias variantes), WebAssembly, x86_64, MIPS, RISC-V, entre outros.

O compilador rustc já inclui suporte nativo para cross-compilation, mas requer toolchains específicas e bibliotecas C para cada target.

2. Configuração do Ambiente de Cross-compilation

O primeiro passo é adicionar o target desejado ao seu ambiente Rust:

// Adicionando targets
$ rustup target add arm-unknown-linux-gnueabihf
$ rustup target add wasm32-unknown-unknown
$ rustup target add aarch64-linux-android

Para gerenciar múltiplos targets, você pode usar toolchains específicas:

$ rustup toolchain install nightly --target arm-unknown-linux-gnueabihf
$ rustup toolchain list

Dependendo do target, você precisará instalar linkers e bibliotecas C. Para ARM Linux:

# Ubuntu/Debian
$ sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf

# macOS (usando homebrew)
$ brew install arm-linux-gnueabihf-binutils

3. Compilando para ARM (Embedded e Linux)

Targets ARM comuns incluem arm-unknown-linux-gnueabihf (ARM Linux com hardware float) e thumbv7em-none-eabihf (ARM Cortex-M4 sem sistema operacional).

Configure o arquivo .cargo/config.toml:

[target.arm-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
rustflags = ["-C", "target-feature=+crt-static"]

[target.thumbv7em-none-eabihf]
linker = "arm-none-eabi-gcc"
rustflags = ["-C", "link-arg=-nostartfiles"]

Exemplo prático: compilando para Raspberry Pi:

// src/main.rs
use std::process::Command;

fn main() {
    let output = Command::new("uname")
        .arg("-m")
        .output()
        .expect("Falha ao executar comando");

    println!("Arquitetura: {}", 
        String::from_utf8_lossy(&output.stdout).trim());

    println!("Olá do Rust rodando em ARM!");
}

Compile com:

$ cargo build --target arm-unknown-linux-gnueabihf --release
$ scp target/arm-unknown-linux-gnueabihf/release/meu_app pi@192.168.1.100:~/

4. Compilando para WebAssembly (WASM)

Rust oferece dois targets WASM principais: wasm32-unknown-unknown (para navegadores) e wasm32-wasi (para ambientes server-side).

Para compilar para o navegador:

// src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Olá, {}! Bem-vindo ao WebAssembly com Rust!", name)
}

Crie o projeto e compile:

$ cargo new --lib meu_wasm
$ cd meu_wasm
$ cargo add wasm-bindgen
$ cargo build --target wasm32-unknown-unknown --release
$ wasm-pack build --target web

O wasm-pack gera automaticamente os arquivos JavaScript de glue e o pacote pronto para uso no navegador:

<script type="module">
    import init, { fibonacci, greet } from './pkg/meu_wasm.js';

    async function main() {
        await init();
        console.log(greet("Mundo"));
        console.log(`Fibonacci(10) = ${fibonacci(10)}`);
    }
    main();
</script>

5. Compilando para Outros Targets (Android, iOS, Windows)

Android

Para compilar para Android, use os targets aarch64-linux-android (64 bits) ou armv7-linux-androideabi (32 bits). Você precisará do Android NDK:

$ rustup target add aarch64-linux-android
$ export ANDROID_NDK_HOME=/path/to/android-ndk
$ export CC_aarch64_linux_android=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang
$ cargo build --target aarch64-linux-android --release

iOS

Para iOS, use os targets aarch64-apple-ios (dispositivos físicos) e x86_64-apple-ios (simulador):

$ rustup target add aarch64-apple-ios
$ cargo build --target aarch64-apple-ios --release

No macOS, o Xcode fornece as toolchains necessárias automaticamente.

Windows a partir de Linux/macOS

Para compilar binários Windows:

# Para usar a ABI GNU (Mingw)
$ sudo apt-get install mingw-w64
$ rustup target add x86_64-pc-windows-gnu
$ cargo build --target x86_64-pc-windows-gnu --release

# Para usar a ABI MSVC (requer Wine + MSVC toolchain)
$ rustup target add x86_64-pc-windows-msvc

6. Gerenciamento de Dependências e C FFI

Ao fazer cross-compilation com dependências C, configure variáveis de ambiente e o arquivo build.rs:

// build.rs
use std::env;

fn main() {
    let target = env::var("TARGET").unwrap();

    if target.contains("arm") {
        println!("cargo:rustc-link-search=/usr/arm-linux-gnueabihf/lib");
        println!("cargo:rustc-link-lib=static=my_c_lib");
    } else if target.contains("wasm32") {
        println!("cargo:rustc-cfg=wasm");
    }

    // Configurar pkg-config para cross-compilation
    if target.contains("linux") && !target.contains("x86_64") {
        env::set_var("PKG_CONFIG_ALLOW_CROSS", "1");
        env::set_var("PKG_CONFIG_LIBDIR", 
            format!("/usr/{}/lib/pkgconfig", target));
    }
}

Dicas importantes:

  • Use cc crate para compilar código C condicionalmente
  • Configure CC e AR para o linker correto
  • Evite paths absolutos em bibliotecas C

7. Testando e Depurando Binários Cross-compilados

Para testar binários ARM sem hardware físico, use QEMU:

$ sudo apt-get install qemu-user qemu-system-arm

# Executar binário ARM64
$ qemu-aarch64 -L /usr/aarch64-linux-gnu target/aarch64-unknown-linux-gnu/release/meu_app

# Executar testes
$ cargo test --target arm-unknown-linux-gnueabihf -- --test-threads=1

Para depuração remota com GDB:

$ qemu-arm -g 1234 target/arm-unknown-linux-gnueabihf/debug/meu_app
$ gdb-multiarch target/arm-unknown-linux-gnueabihf/debug/meu_app
(gdb) target remote localhost:1234
(gdb) continue

8. Boas Práticas e Automação

Crie scripts de build com just ou Makefile:

# Makefile
TARGETS := arm-unknown-linux-gnueabihf wasm32-unknown-unknown x86_64-pc-windows-gnu

.PHONY: all $(TARGETS)

all: $(TARGETS)

$(TARGETS):
    cargo build --target $@ --release

test-arm:
    qemu-arm -L /usr/arm-linux-gnueabihf target/arm-unknown-linux-gnueabihf/debug/meu_app

deploy-arm:
    scp target/arm-unknown-linux-gnueabihf/release/meu_app pi@192.168.1.100:~/

Para CI/CD com GitHub Actions:

# .github/workflows/cross-compile.yml
name: Cross-compilation
on: [push]

jobs:
  build:
    strategy:
      matrix:
        target: [arm-unknown-linux-gnueabihf, wasm32-unknown-unknown]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          target: ${{ matrix.target }}
      - run: sudo apt-get install gcc-arm-linux-gnueabihf
      - run: cargo build --target ${{ matrix.target }} --release

Checklist final:

  • Use cfg!(target_arch = "arm") para código condicional
  • Verifique features específicas do target com #[cfg(target_arch = "wasm32")]
  • Teste em ambientes reais sempre que possível
  • Documente as dependências de toolchain no README do projeto

Referências