cargo-zigbuild

repository·main·Indexed 25 days ago

https://github.com/rust-cross/cargo-zigbuild

A cargo subcommand that uses the Zig compiler as a linker to simplify cross-compilation for Rust projects, specifically for macOS and Linux (glibc targeting). It supports specifying minimum glibc versions, building macOS universal2 binaries, and provides wrappers for tools like ar, lib, and dlltool. Version 0.23.0.

Tokens
2.3K
Snippets
5
Records
13
Agent score
82%

What's inside cargo-zigbuild

  1. Build macOS universal2 binaries

    main

    For Rust 1.64.0 and later, cargo-zigbuild supports the universal2-apple-darwin target to create macOS universal2 binaries (supporting both x86_64 and aarch64).

    1. Add both architectures via rustup:
      rustup target add x86_64-apple-darwin
      rustup target add aarch64-apple-darwin
    2. Build using the special target:
      cargo zigbuild --target universal2-apple-darwin

    Note: The Cargo --message-format option is currently not supported when using the universal2 target.

    rustup target add x86_64-apple-darwin
    rustup target add aarch64-apple-darwin
    cargo zigbuild --target universal2-apple-darwin
  2. Specify a minimum glibc version for Linux targets

    main

    When targeting *-gnu Linux platforms, you can specify a minimum glibc version by appending it as a suffix to the --target value. This ensures the resulting binary is compatible with older systems.

    Example: To compile for aarch64-unknown-linux-gnu with glibc 2.17 compatibility:

    cargo zigbuild --target aarch64-unknown-linux-gnu.2.17

    Caveats:

    • If no --target is provided, cargo zigbuild falls back to a regular cargo build and does not use Zig.
    • Invalid glibc versions may not trigger a warning from zig cc about the fallback version selected.
    • -C target-feature=+crt-static is not supported for statically linking to a glibc version.
  3. Integrate Bindgen with Cargo Zigbuild

    main

    When using cargo zigbuild, the target triple is provided to your Rust program (including build.rs) via the TARGET environment variable. You can use this variable to pass the correct target to bindgen using the --target clang argument, ensuring that the generated C bindings match the cross-compilation target being built by Zig.

    To implement this, retrieve the target in your build.rs using std::env::var("TARGET") and pass it to the bindgen builder via .clang_arg(format!("--target={target}")).

    // build.rs
    use std::{env, path::PathBuf};
    
    fn main() {
        let target = env::var("TARGET").unwrap();
    
        // Re-run this build script if the C source or header changes
        println!("cargo:rerun-if-changed=c-src/example.c");
        println!("cargo:rerun-if-changed=c-src/example.h");
    
        cc::Build::new().file("c-src/example.c").compile("example");
    
        let bindings = bindgen::builder()
            .header("c-src/example.h")
            .use_core()
            .clang_arg(format!("--target={target}"))
            .generate().unwrap();
    
        let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
        bindings.write_to_file(out_path.join("example.rs")).unwrap();
    }
  4. Use cargo-zigbuild for cross-compilation

    main

    To use cargo-zigbuild, follow these steps:

    1. Install Zig: Follow the official documentation or install via pip: pip3 install ziglang.
    2. Add Rust Target: Use rustup to add the target you wish to build for (e.g., rustup target add aarch64-unknown-linux-gnu).
    3. Run Build: Execute cargo zigbuild with the --target flag.

    Example:

    cargo zigbuild --target aarch64-unknown-linux-gnu
  5. Install cargo-zigbuild

    main

    You can install cargo-zigbuild using Cargo or pip. Installing via pip also automatically installs the ziglang package.

    Via Cargo

    cargo install --locked cargo-zigbuild

    Via pip

    pip install cargo-zigbuild

    Via Docker

    For environments requiring macOS SDKs (e.g., to build for x86_64 macOS), use the provided Docker images:

    docker run --rm -it -v $(pwd):/io -w /io ghcr.io/rust-cross/cargo-zigbuild \
      cargo zigbuild --release --target x86_64-apple-darwin
  6. Install a Rust binary using `cargo zigbuild install`

    main
    The cargo zigbuild install command allows you to install a Rust binary using zig as the linker. This is useful for cross-compiling and ensuring the binary is linked against specific glibc versions or other target-specific requirements. By default, the installed binary is placed in $HOME/.cargo/bin.
  7. Resolve missing headers or libraries in cargo-zigbuild

    main

    Because cargo zigbuild uses zig cc -nostdinc, it excludes standard system header locations like /usr/include. If your project depends on system headers or libraries, you must explicitly provide the paths.

    Missing Header Files (*.h)

    To allow Zig to find system headers (like libelf.h) without mixing them with Zig's own glibc headers, use CFLAGS with the -isystem flag:

    CFLAGS='-isystem /usr/include' cargo zigbuild --target <target>

    Missing Shared Libraries

    To allow build.rs scripts to find system libraries (like libelf) located in paths like /usr/lib64, use RUSTFLAGS with the -L flag:

    RUSTFLAGS='-L /usr/lib64' cargo zigbuild --target <target>
  8. Verify minimum GLIBC version required by a binary

    main

    If you have a compiled Linux binary and want to find the highest versioned glibc symbol it uses (which represents the minimum glibc version required to run it), you can use this script.

    1. Create /usr/local/bin/get-min-glibc with the following content:
    #!/bin/bash
    
    FILE_NAME=$1
    LANG=C readelf -W --version-info --dyn-syms ${FILE_NAME} \
      | grep 'Name: GLIBC' \
      | sed -re 's/.*GLIBC_(.+) Flags.*/\1/g' \
      | sort -t . -k1,1n -k2,2n \
      | tail -n 1
    1. Make it executable:
    chmod +x /usr/local/bin/get-min-glibc
    1. Run it against your binary:
    get-min-glibc target/x86_64-unknown-linux-gnu/release/my-app
  9. Reference: cargo-zigbuild environment variables

    main

    The following environment variables can be used to configure cargo-zigbuild behavior:

    VariableDescription
    CARGO_ZIGBUILD_PYTHON_PATHPython executable path when using Python ziglang package (default: python3)
    CARGO_ZIGBUILD_ZIG_PATHZig executable path (default: zig)
    CARGO_ZIGBUILD_CACHE_DIRCache directory for zig tools and wrappers
    CARGO_ZIGBUILD_RUSTC_VERSIONOverride detected rustc version
    SDKROOTPath to macOS SDK (auto-detected on macOS)
    CMAKE_TOOLCHAIN_FILEPath to CMake toolchain file (also CMAKE_TOOLCHAIN_FILE_<target>, TARGET_CMAKE_TOOLCHAIN_FILE)
    BINDGEN_EXTRA_CLANG_ARGSExtra clang arguments for bindgen (also BINDGEN_EXTRA_CLANG_ARGS_<target>)
    PKG_CONFIG_SYSROOT_DIRSystem root for pkg-config (auto-set to SDKROOT for Apple targets)
    OHOS_NDK_HOMEPath to OpenHarmony NDK (required for ohos targets)
    CFLAGSAdditional C compiler flags
    RUSTFLAGSAdditional Rust compiler flags
  10. Run cargo clippy via cargo-zigbuild

    main
    You can run cargo clippy through cargo-zigbuild to leverage Zig's cross-compilation capabilities while performing linting. This command wraps the standard cargo clippy subcommand and applies the necessary Zig environment configurations to ensure the linting process respects the target architecture and toolchain settings configured for your build.
  11. Use cargo-zigbuild subcommands

    main

    The cargo-zigbuild CLI provides several subcommands that mirror standard Cargo commands but integrate Zig's capabilities for cross-compilation.

    Available subcommands include:

    • zigbuild (aliases: build, b): Performs the build process.
    • clippy: Runs Clippy lints.
    • check (alias: c): Checks the project without building.
    • doc: Generates documentation.
    • install: Installs the package.
    • rustc: Invokes the Rust compiler.
    • run (alias: r): Runs the resulting binary.
    • test (alias: t): Runs tests.
    • zig: Provides direct access to Zig-related subcommands.

    Note that for most standard Cargo-like commands (build, clippy, check, doc, install, rustc, run, test), cargo-zigbuild automatically enables zig-ar to assist with cross-compilation tasks.

  12. Run a binary or example with cargo zigbuild run

    main
    The cargo zigbuild run command allows you to execute a binary or example of the local package using the cross-compilation settings configured via zigbuild. It wraps the standard cargo run command, applying the necessary Zig-based environment configurations (like linker settings) before spawning the process. If the underlying cargo process exits with a non-zero status, cargo zigbuild run will exit with that same status code.