Kereszt-fordítás (cross-compilation) alatt azt értjük, amikor a fordítást futtató gép architektúrája és operációs rendszere eltér a végeredmény célplatformjától. Rust-ban ez szerencsére nem valami egzotikus hack, hanem a toolchain natív funkciója — de van néhány buktató, amit érdemes ismerni, mielőtt élesben nekiugrasz.

Mikor van szükség kereszt-fordításra

A leggyakoribb esetek:

  • Embedded és IoT projektek: Raspberry Pi-re (aarch64 vagy armv7), mikrokontrollerekre (thumbv7em-none-eabihf) fordítasz, de fejlesztés közben x86_64-es laptopot használsz.
  • CI/CD pipeline-ok: egy build szerveren szeretnél Linux, macOS és Windows binárisokat is generálni anélkül, hogy három különböző futtatót tartanál fenn.
  • Docker image-ek: gyakran x86_64-unknown-linux-musl target-re fordítunk, hogy a statikusan linkelt bináris minimális Alpine-image-ekbe kerülhessen.
  • WASM célok: wasm32-unknown-unknown target böngészőben futó kódhoz.
Megjegyzés

A natív fordítás (amikor a build gép és a célgép azonos) technikailag is egy "target", csak épp megegyezik a host triple-lel. A rustup ezt alapból már telepíti, ezért nem szoktunk vele foglalkozni.

A rustup target add használata és elérhető triple-ök

A Rust platformleírásokat úgynevezett target triple formában adja meg: architektúra-gyártó-operációsrendszer-abi. Például:

  • x86_64-unknown-linux-gnu — a legtöbb Linux disztribúció alap targetje
  • x86_64-unknown-linux-musl — statikusan linkelt Linux bináris
  • aarch64-unknown-linux-gnu — ARM64 szerverek, Raspberry Pi 4 (64 bites OS)
  • armv7-unknown-linux-gnueabihf — Raspberry Pi 3 és korábbi, 32 bites OS
  • x86_64-pc-windows-gnu / x86_64-pc-windows-msvc — Windows
  • aarch64-apple-darwin — Apple Silicon Mac
  • wasm32-unknown-unknown — WebAssembly

Ahhoz, hogy egy adott target-re fordíthass, előbb hozzá kell adnod a standard könyvtár prebuilt verzióját a toolchain-hez:

rustup target list
rustup target add aarch64-unknown-linux-gnu

A rustup target list kiírja az összes elérhető target-et, a telepítettek mellett (installed) jelzéssel. Fontos: ez csak a Rust std-t és a core komponenseket telepíti — a linkert és az esetleges C könyvtárakat neked kell biztosítanod (erről lentebb bővebben).

cargo build --target gyakorlati példákon

Ha a target telepítve van, a fordítás triviális:

cargo build --release --target aarch64-unknown-linux-gnu

A kimenet nem a szokásos target/release/ alá kerül, hanem target/<triple>/release/ alá, hogy a különböző platformok binárisai ne ütközzenek egymással.

Gyakori trükk, hogy a kódban cfg attribútumokkal ágazol el platform-specifikus logikára:

#[cfg(target_os = "linux")]
fn platform_info() -> &'static str {
    "Linux rendszeren futunk"
}

#[cfg(target_os = "windows")]
fn platform_info() -> &'static str {
    "Windows rendszeren futunk"
}

#[cfg(target_arch = "aarch64")]
fn arch_info() -> &'static str {
    "ARM64 architektúra"
}

#[cfg(target_arch = "x86_64")]
fn arch_info() -> &'static str {
    "x86_64 architektúra"
}

fn main() {
    println!("{} - {}", platform_info(), arch_info());
}

Ez ugyanazzal a forráskóddal fordul minden target-en, csak épp más ág aktiválódik a cfg feltételek miatt.

Ha a linkerbeállítást is meg kell adnod (lásd lentebb), azt a .cargo/config.toml fájlban tudod projekt- vagy globális szinten testre szabni:

[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
Tipp

Ha csak egyszer akarsz egy adott target-re fordítani, a CARGO_BUILD_TARGET környezeti változóval is beállíthatod alapértelmezettnek, elkerülve a --target flag ismételt beírását.

A cross eszköz bemutatása Docker-alapú fordításhoz

A rustup target add + natív linker kombó sok esetben elég, de amint natív C függőségekkel (pl. OpenSSL, libpq, vagy egyéb dinamikus linkelt könyvtárral) dolgozó crate-eket használsz, a helyzet bonyolulttá válhat — a host gépeden ugyanis nem feltétlenül áll rendelkezésre a célplatform C toolchain-je és fejlesztői headerei.

Itt jön képbe a cross nevű community eszköz, amely Docker (vagy Podman) konténereken belül futtatja a fordítást, előre elkészített image-ekkel, amikben minden szükséges cross linker és header már benne van.

Telepítés és használat:

cargo install cross --git https://github.com/cross-rs/cross
cross build --release --target aarch64-unknown-linux-gnu

A cross a háttérben letölt egy megfelelő Docker image-et (pl. ghcr.io/cross-rs/aarch64-unknown-linux-gnu), abban futtatja a cargo build-et, és a bináris ugyanúgy megjelenik a target/<triple>/release/ mappában, mintha lokálisan fordítottad volna.

Jó tudni

A cross használatához Docker (vagy Podman) daemon kell, hogy fusson a gépeden. CI környezetben (pl. GitHub Actions) ez általában adott, de lokális fejlesztésnél ellenőrizd, hogy a Docker szolgáltatás elindult-e, különben a cross build egy kapcsolódási hibával leáll.

Ha saját, testreszabott image-et akarsz (mondjuk extra system library-vel), a Cross.toml fájlban tudod megadni:

[target.aarch64-unknown-linux-gnu]
image = "my-registry/custom-cross-image:latest"

Gyakori buktatók: linker beállítások és natív függőségek

A kereszt-fordítás során a leggyakoribb hibaüzenetek valamelyike ezek közül szokott lenni:

  1. "linker cc not found" — a rustup target hozzáadása nem telepít linkert, csak a Rust std-t. Linux-on a legtöbb esetben egy megfelelő gcc cross toolchain csomagot kell telepítened (pl. gcc-aarch64-linux-gnu Ubuntu-n), és beírnod a .cargo/config.toml-ba, hogy melyik binárist használja linkerként.

  2. OpenSSL és egyéb dinamikus C függőségek — az openssl crate klasszikus fájdalompont, mert natívan linkeli a rendszer OpenSSL könyvtárát. Kereszt-fordításnál ez szinte sosem elérhető a célplatform verziójában a host gépen. Két gyakori megoldás:

    • a cross eszköz használata, ami a Docker image-ben már tartalmazza a megfelelő OpenSSL fejlesztői csomagokat,
    • vagy áttérés rustls-alapú TLS implementációra (pl. reqwest esetén a rustls-tls feature engedélyezése), amivel elkerülöd a natív OpenSSL linkelést teljesen.
  3. musl target és statikus linkelésx86_64-unknown-linux-musl target esetén figyelj arra, hogy a musl toolchain telepítve legyen (apt install musl-tools Debian-alapú rendszereken), különben a linker lépés hibázik.

  4. build.rs szkriptek platformfüggő logikával — ha a projektben van build.rs, ami natív parancsokat futtat (pl. cc crate-tel C kódot fordít), figyelj arra, hogy a build.rs a host gépen fut, nem a target-en, de a cc crate-nek is tudnia kell a megfelelő cross-toolchain-t megtalálni:

// build.rs
fn main() {
    let target = std::env::var("TARGET").unwrap();
    println!("cargo:warning=Fordítási cél: {target}");

    if target.contains("windows") {
        println!("cargo:rustc-link-lib=ws2_32");
    }
}

Ez a minta build script a TARGET környezeti változó alapján dönt arról, hogy szükség van-e egy platformspecifikus natív könyvtár linkelésére — ez a Cargo által automatikusan beállított változó minden build.rs futásnál elérhető.

Figyelem

Ne feledd, hogy a TARGET és a HOST környezeti változó eltérhet egymástól kereszt-fordításnál! Ha a build script feltételezi, hogy a kettő megegyezik (например natív parancsokat futtat, amik csak a host-on futnak), az konténerben vagy CI-ban rejtett hibákhoz vezethet.

Összefoglalás

A Rust kereszt-fordítási támogatása igazából két rétegből áll: a rustup target add biztosítja a szükséges std komponenseket, a cargo build --target pedig kiválasztja, mire fordulunk. Egyszerű, natív függőség nélküli projekteknél ez elég is. Amint viszont C könyvtárak, OpenSSL vagy egyéb rendszer-specifikus binding kerül képbe, a cross eszköz Docker-alapú megközelítése rengeteg fejfájástól kímél meg — cserébe egy Docker daemon futtatásáért. Érdemes már a projekt elején eldönteni, hogy melyik útra lépsz, mert a natív függőségek utólagos migrálása (pl. OpenSSL → rustls) sokkal fájdalmasabb, mint elsőre jól megválasztani a stacket.