zig-cross

    Cross-compile MoonBit native binaries with zig

    command-line
    moonx
    wasm
    zig
    cross-compile
    Download zip
    Author
    Version
    0.3.2
    License
    Apache-2.0
    Last updated
    5 days ago
    Downloads
    97

    #zig-cross

    zig-cross cross-compiles MoonBit native binaries with zig. It builds for Linux, macOS and Windows from one machine. It runs with moonx, so you do not install it.

    Maintained in moonrockz/moonx-tools. Moved from moonrockz/smithy apps/zig-cross; package name moonrockz/zig-cross, Existing build/exec commands, target names and output paths are preserved.

    #Requirements

    • Zig 0.17.0 on PATH: mise use zig@0.17.0.
    • The moon command.
    • Linux or macOS as the build machine. The tool stops on Windows.

    #Usage

    moonx moonrockz/zig-cross@<version> doctor linux-x64 darwin-arm64 moonx moonrockz/zig-cross@<version> build linux-x64 -- apps/myapp ZIG_CROSS_RUNTIME=sync moonx moonrockz/zig-cross@<version> build darwin-arm64 -- apps/myapp moonx moonrockz/zig-cross@<version> exec host -- moon test --target native moonx moonrockz/zig-cross@<version> --help

    build [--target-dir DIR] <target>... [-- <path>...]

    1. It writes the wrappers cc and ar to DIR/zig/<target>. DIR is _build/cross by default.
    2. It runs moon build --target native --release --target-dir DIR/<target> [paths] with MOON_CC and MOON_AR set to the wrappers.
    3. It reads the header of each *.exe under DIR/<target>/native/release/build. A binary with the wrong architecture is a failure.
    4. It prints a table (target, path, size, result). It exits with 1 when one target fails.

    exec <target> -- <command> <arg>... runs a command with MOON_CC and MOON_AR set to the wrappers of one target. Use host to compile the tests of a package with zig.

    #Targets

    NameZig tripleHeader check
    linux-x64x86_64-linux-muslELF, machine 62
    linux-arm64aarch64-linux-muslELF, machine 183
    darwin-arm64aarch64-macosMach-O, cputype 16777228
    darwin-x64x86_64-macosMach-O, cputype 16777223
    windows-x64x86_64-windows-gnuPE, machine 0x8664
    hostnone (the host)none

    #Do not ship a cross-built Windows binary

    A windows-x64 binary that zig cross-builds is wrong for MoonBit code that uses OsString: arguments, environment variables and paths. The core library is prebuilt for the host. Its #cfg(platform="windows") setting is resolved from the host OS, not from the target. A Windows binary built on Linux or macOS therefore reads its arguments wrongly. Build Windows binaries on Windows until moon can set the target platform (moonbitlang/moon#2320).

    zig-cross itself runs on Linux/macOS only.

    #What the wrappers change, and why

    Two upstream bugs make a plain MOON_CC="zig cc -target ..." fail.

    • Every wrapper adds -D_CRT_RAND_S. The MoonBit runtime file env.c includes <stdlib.h> before it defines _CRT_RAND_S, so rand_s is not declared on Windows. The flag defines it before the first include. Upstream: moonbitlang/moonbit-native-runtime#2.
    • A cross wrapper drops -DMOONBIT_USE_SIMDUTF and the arguments that end in /moonbit_simdutf.o, /simdutf.o and /libbacktrace.a. moon links these prebuilt objects for the machine that runs moon. They have the wrong architecture for the target. The ar wrapper drops the two simdutf objects. The host wrapper drops nothing. Upstream: moonbitlang/moon#2320.
    • The host wrapper adds -lunwind on Linux, for link steps only. A debug build there compiles backtrace.c, which needs the unwinder. gcc links its unwinder by itself, but zig cc does not. macOS needs nothing.

    #Diagnose before upgrading or building

    doctor [<target>...] [--json] runs outside a project without building, writing wrappers or changing a toolchain. No targets selects all six targets, including host. Repeated targets are removed. Tool probes capture output and time out after three seconds each. Help and version do not probe tools.

    moonx moonrockz/zig-cross@<version> doctor moonx moonrockz/zig-cross@<version> doctor windows-x64 --json > report.json ZIG_CROSS_RUNTIME=sync moonx moonrockz/zig-cross@<version> doctor windows-x64 --json

    For agents, stdout is one JSON document with schema_version: 1, environment, targets, status and findings. Environment includes host, Moon/compiler/Zig versions, detected toolchain root, runtime fingerprint, runtime mode and detected macOS SDK and toolchain override names. Each finding has a stable code, severity, affected targets, message, applicability, evidence and next_steps. Steps distinguish command from instruction and state prerequisites. Replace placeholders such as <package-path> before running commands. When using moonx, replace the zig-cross executable in a suggested command with moonx moonrockz/zig-cross@<version>.

    Statuses are error, warning, unknown or ok, in that precedence. Finding severities are error, warning or unverified. The status is error if a finding is error, else warning if a finding is warning, else unknown if a finding is unverified, else ok. Errors exit 1; other reports exit 0. Invalid arguments exit 2 and retain JSON output when --json is present. Treat prose as explanatory and consume codes/statuses. Additive fields are allowed within schema version 1.

    Build and exec emit applicable findings once per invocation to stderr. Child stdin/stdout/stderr and exit mapping remain inherited. A failed build adds zig-cross doctor <failed-target> without replacing compiler diagnostics. For example:

    moonx moonrockz/zig-cross@<version> doctor darwin-arm64 ZIG_CROSS_RUNTIME=sync moonx moonrockz/zig-cross@<version> doctor darwin-arm64 ZIG_CROSS_RUNTIME=sync moonx moonrockz/zig-cross@<version> build darwin-arm64 -- apps/myapp

    #MoonBit 0.10.17 and later compatibility

    Assessed stock versions are MoonBit 0.10.17+d971670cd with Moon 0.1.20261023, and MoonBit 0.10.18+6c83a7f62 with Moon 0.1.20261028, each with Zig 0.17.0, on Linux and macOS hosts. The previous 0.10.15+943464bfa runtime also has an exact assessment. Runtime C contents are checked by SHA-256, using a manifest of name:sha256 lines sorted in MoonBit String order, length then code units. Unknown versions or customized runtimes produce ZC_TOOLCHAIN_UNVERIFIED; they do not inherit a whitelist or an assertion of failure.

    The new Moon tool detects cross-target compilers and avoids host-only objects, but also compiles every shipped runtime C file. The wrappers retain the older object filtering for 0.10.15 compatibility and _CRT_RAND_S for MinGW. Full runtime remains the default.

    #Header fix for the MoonBit runtime

    The runtime files dir.c and fs.c, and the moonbitlang/async C stubs src/fs/dir.c and src/internal/event_loop/fs.c, do not compile with Zig's libc headers. The cc wrapper fixes this for the full runtime, without changes to the MoonBit files:

    • Linux targets, including host on Linux, receive -include limits.h, because dir.c uses NAME_MAX and does not include limits.h. ZC_LINUX_NAME_MAX reports the active fix.
    • Darwin targets, including host on macOS, receive -idirafter <target-dir>/zig/<target>/include. zig-cross writes a minimal sys/vnode.h there: enum vtype with the values of the macOS SDK (VNON = 0 to VCPLX = 10), which is all these files use from that header. Zig's macOS headers do not have sys/vnode.h. A real sys/vnode.h comes first on the include path. No macOS SDK is necessary, so Linux hosts can build darwin targets. ZC_DARWIN_VNODE_HEADER reports the active fix.
    • Windows full-runtime sources contain explicit MSVC-only checks and further MinGW incompatibilities. ZC_WINDOWS_MSVC_RUNTIME reports the blocker. Do not fake _MSC_VER or suppress compiler errors.

    The wrapper adds the flags only when an argument matches */lib/runtime/*.c (the toolchain runtime) or */moonbitlang/async/*.c. Your own C stubs and other C dependencies compile without them. The fix applies to the full runtime of each toolchain except the old 0.10.15 pin, which does not need it. For an unassessed toolchain, ZC_TOOLCHAIN_UNVERIFIED reports the fix. Remove the fix when the MoonBit runtime and moonbitlang/async include limits.h and no longer include sys/vnode.h, or when Zig ships that header.

    Doctor reports what it detected and what the wrapper will select; it does not compile a probe project. environment.darwin_sdk stays in the JSON report for information; the wrapper does not use it.

    #Explicit experimental synchronous runtime

    Set ZIG_CROSS_RUNTIME=sync for consumers whose required runtime symbols fit this subset:

    runtime.c, env.c, sync_io.c, utf.c, backtrace.c, c_buffer.c.

    The selection requires the exact 0.10.17 compiler/build-tool/Zig versions and stock runtime fingerprint; 0.10.18 has no sync assessment. An unknown/custom runtime is refused before child execution. ZIG_CROSS_RUNTIME=full, or an unset variable, uses full runtime. Other values fail with ZC_RUNTIME_MODE_INVALID.

    zig-cross copies these six files from the detected current installation into a temporary toolchain view, symlinks the matching compiler/core/headers and other libraries, and sets MOON_TOOLCHAIN_ROOT for the child. It cleans up the view after the child exits. It does not edit the global installation, change MOON_HOME, use old runtime sources or supply substitute stubs. Run build processes synchronously inside exec; background children cannot retain the temporary view after exec exits.

    ZC_COMPATIBILITY_MODE identifies actual sync selection and its limits. The mode excludes the runtime's event loop, asynchronous filesystem, networking, processes, TLS, thread pools and watchers. A dependency requiring those excluded symbols fails at link time, with raw linker output and a doctor hint. To use those APIs, unset ZIG_CROSS_RUNTIME and build on the target OS with a native compiler supported by MoonBit. That native build should invoke Moon directly, outside zig-cross.

    Validation covers the consumer's ELF/Mach-O/PE headers for all five targets, a dependency exercising synchronous runtime and environment/UTF APIs on the host, host execution, and expected failure for an excluded symbol. Header/link success alone does not establish behavior for every library or OS. Linux/macOS integration is gated in CI. Windows is link/header checked only and retains ZC_WINDOWS_OSSTRING; use a Windows-native build for programs using OsString.

    Command

    pub(all) enum Command {
    Build(target_dir~ : String, targets~ : Array[Target], paths~ : Array[String])
    Exec(target~ : Target, argv~ : Array[String])
    Doctor(targets~ : Array[Target], json~ : Bool)
    Help
    Version
    } derive(Eq,
    Debug
    )

    Format

    pub(all) enum Format {
    Elf(Int)
    MachO(Int)
    Pe(Int)
    Unknown
    } derive(Eq,
    Debug
    )

    The executable format of a binary, from its header. The payload is the machine field: e_machine for ELF, cputype for Mach-O and Machine for PE.

    Target

    pub(all) enum Target {
    LinuxX64
    LinuxArm64
    DarwinArm64
    DarwinX64
    WindowsX64
    Host
    } derive(Eq,
    Debug
    )

    A target that zig-cross builds for. Host is the machine itself: zig only compiles for it, and the wrappers do not strip anything.

    Target::name

    fn Target::name(self : Target) -> String

    Target::parse

    fn Target::parse(text : String) -> Target?

    ar_wrapper

    fn ar_wrapper(target : Target) -> String

    The ar wrapper for MOON_AR. moon puts the prebuilt simdutf objects into the runtime archive; a cross build must leave them out (see cc_wrapper).

    cc_wrapper

    fn cc_wrapper(target : Target, header_fix? : Array[String]) -> String

    The Host wrapper has one more rule. On Linux, a debug build compiles backtrace.c, which calls _Unwind_Backtrace. gcc links its unwinder (libgcc) by itself, but zig cc does not, so a link step needs zig's bundled -lunwind. macOS has the unwinder in libSystem and needs nothing. The tool can run on wasm, so the wrapper decides at run time with uname -s. A compile step (-c) does not link, so it gets no library.

    The cc wrapper for MOON_CC: a /bin/sh script that runs zig cc.

    A cross build needs three workarounds for upstream bugs in moon and in the MoonBit runtime:

    1. The runtime file env.c includes <stdlib.h> before it defines _CRT_RAND_S. On Windows (mingw), rand_s is then not declared and the compile fails. A -D_CRT_RAND_S on the command line defines it before the first include. The flag does no harm on other targets, so every wrapper has it. Upstream: https://github.com/moonbitlang/moonbit-native-runtime/issues/2
    2. moon passes -DMOONBIT_USE_SIMDUTF and links the prebuilt objects moonbit_simdutf.o, simdutf.o and libbacktrace.a. These objects are built for the machine that runs moon, not for MOON_CC's target, so the link fails or the binary gets the wrong architecture. A cross wrapper removes the flag and the three files. The Host wrapper removes nothing. Upstream: https://github.com/moonbitlang/moon/issues/2320
    3. header_fix holds the flags of the MoonBit header fix (see header_flags and headers.mbt). The wrapper adds them only when an argument is a MoonBit runtime or moonbitlang/async C source, so other C code compiles as before.

    check_format

    fn check_format(target : Target, bytes : Bytes) -> String?

    None when the header in bytes is right for target, else the message for the summary.

    default_target_dir

    let default_target_dir : String

    The default value of --target-dir.

    detect_format

    fn detect_format(bytes : Bytes) -> Format

    Read the format from the first bytes of a file. A short or unknown input gives Unknown.

    expected_format

    fn expected_format(target : Target) -> Format?

    The format that a binary for target must have. Host has none, so it is not checked.

    help_text

    fn help_text() -> String

    parse_args

    fn parse_args(args : Array[String]) -> Result[Command, String]

    Parse the arguments, without argv[0].

    version

    let version : String

    The version of this tool. scripts/versions.mbtx keeps it equal to version in moon.mod.

    version_text

    fn version_text() -> String

    zig_triple

    fn zig_triple(target : Target) -> String?

    The zig target triple. Host has none: zig then compiles for the machine it runs on.