Cross compilation means building a program for a different machine than the one you are using.
Cross compilation means building a program for a different machine than the one you are using.
For example, you might write code on macOS but build a Linux executable. Or you might write code on an x86_64 laptop but build for an ARM server, a Raspberry Pi, or WebAssembly.
Zig is especially strong at cross compilation because the compiler has built-in target support. In many cases, you do not need a separate cross compiler.
Native Compilation
Native compilation means the build target is your current machine.
If you are using Linux on x86_64 and run:
zig buildZig usually builds a Linux x86_64 program.
That is native compilation.
Cross Compilation
Cross compilation means you choose another target explicitly:
zig build -Dtarget=x86_64-linuxor:
zig build -Dtarget=aarch64-linuxor:
zig build -Dtarget=x86_64-windowsThe program is built for that target, even if your current machine is different.
Why Cross Compilation Matters
Cross compilation is useful when deployment differs from development.
You may develop on macOS but deploy to Linux servers.
You may develop on a fast desktop but deploy to a small ARM board.
You may need to produce binaries for Windows, Linux, and macOS from one build machine.
You may want to build a WebAssembly module from the same codebase.
Without cross compilation, you often need one build machine per platform. With cross compilation, one machine can produce many outputs.
The Target Option
Most build.zig files include this line:
const target = b.standardTargetOptions(.{});This enables the standard target option.
Then you can pass a target from the command line:
zig build -Dtarget=x86_64-linuxYour executable uses that target here:
const exe = b.addExecutable(.{
.name = "hello",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});The important field is:
.target = targetThat connects the command-line target to the executable.
Target Names
A Zig target name describes the machine and operating system.
Examples:
x86_64-linux
aarch64-linux
x86_64-windows
aarch64-macos
wasm32-wasiThe first part is usually the CPU architecture.
The second part is usually the operating system.
So:
x86_64-linuxmeans a 64-bit Intel or AMD CPU running Linux.
aarch64-linuxmeans a 64-bit ARM CPU running Linux.
x86_64-windowsmeans a 64-bit Intel or AMD CPU running Windows.
Common Build Commands
Build for the current machine:
zig buildBuild for Linux x86_64:
zig build -Dtarget=x86_64-linuxBuild for Linux ARM64:
zig build -Dtarget=aarch64-linuxBuild for Windows x86_64:
zig build -Dtarget=x86_64-windowsBuild for WebAssembly with WASI:
zig build -Dtarget=wasm32-wasiBuild a release binary for Linux x86_64:
zig build -Dtarget=x86_64-linux -Doptimize=ReleaseFastCross Compilation and File Names
The output file may change depending on the target.
On Linux and macOS, an executable might be named:
helloOn Windows, it usually has:
hello.exeIf you build for Windows from Linux or macOS, Zig still produces the Windows-style executable.
That is normal.
Cross Compilation and the Standard Library
Zig’s standard library is designed with targets in mind.
Some APIs are portable across many systems. Others are platform-specific.
For example, basic memory allocation and formatting work almost everywhere.
But operating system APIs can differ. File paths, process behavior, networking details, terminal behavior, and dynamic linking rules may not be identical on every target.
Portable code should avoid assuming one operating system unless the program is meant only for that system.
Detecting the Target in Code
Sometimes your source code needs different behavior on different operating systems.
Zig provides compile-time information through builtin data:
const builtin = @import("builtin");You can inspect the target:
const builtin = @import("builtin");
pub fn main() void {
if (builtin.os.tag == .windows) {
// Windows-specific code
} else {
// Non-Windows code
}
}This condition is known at compile time. The compiler can remove the branch that does not apply to the selected target.
Target-Specific Code
A simple example:
const builtin = @import("builtin");
const std = @import("std");
pub fn main() void {
if (builtin.os.tag == .windows) {
std.debug.print("Running on Windows\n", .{});
} else if (builtin.os.tag == .linux) {
std.debug.print("Running on Linux\n", .{});
} else if (builtin.os.tag == .macos) {
std.debug.print("Running on macOS\n", .{});
} else {
std.debug.print("Running on another OS\n", .{});
}
}If you build with:
zig build -Dtarget=x86_64-linuxthe Linux branch is the relevant branch.
If you build with:
zig build -Dtarget=x86_64-windowsthe Windows branch is the relevant branch.
Cross Compilation and C Libraries
Cross compilation becomes more complicated when your Zig program depends on C libraries.
Pure Zig code is usually easier to cross compile.
But if your project links to a C library, Zig needs a version of that C library for the target platform.
For example, if you build on macOS but target Linux, a macOS .dylib cannot be used in a Linux executable. You need the Linux version of the library.
This is a general cross-compilation rule, not only a Zig rule.
The target executable must link against target-compatible libraries.
Static Linking
Static linking can make deployment easier.
A statically linked binary includes library code inside the executable. That can reduce runtime dependency problems on the target machine.
In Zig, release builds for server tools often aim for simple deployment:
zig build -Dtarget=x86_64-linux -Doptimize=ReleaseFastDepending on the program and libraries, you may also configure static linking in build.zig.
Static linking is useful, but it is not magic. Some libraries and platforms have rules that make static linking harder.
Cross Compilation in build.zig
A normal cross-compilation-ready build file starts like this:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "hello",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
b.installArtifact(exe);
}This file does not hard-code the platform.
That is the key design.
The target is selected by the command line, not by editing the build file.
Building Several Targets
Sometimes you want one build command to produce several binaries.
For example:
hello-linux-x86_64
hello-linux-aarch64
hello-windows-x86_64.exeYou can create several executable artifacts in build.zig, each with a different target.
The beginner pattern is:
const linux_x64 = b.resolveTargetQuery(.{
.cpu_arch = .x86_64,
.os_tag = .linux,
});
const exe_linux_x64 = b.addExecutable(.{
.name = "hello-linux-x86_64",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = linux_x64,
.optimize = optimize,
}),
});
b.installArtifact(exe_linux_x64);You can repeat that for other targets.
For most beginner projects, using -Dtarget=... is enough. Multi-target build files are useful later, when you want release automation.
Cross Compilation and Tests
Be careful with tests.
This command:
zig build test -Dtarget=x86_64-linuxmay compile tests for Linux. But if you are not on Linux, the build machine may not be able to run those tests directly.
Compilation and execution are different.
You can often compile for another platform, but running the result requires that platform, an emulator, or a compatible runtime.
This matters especially for WebAssembly, embedded targets, and operating systems different from your own.
Practical Workflow
A common workflow is:
zig build test
zig build -Doptimize=ReleaseFast
zig build -Dtarget=x86_64-linux -Doptimize=ReleaseFast
zig build -Dtarget=aarch64-linux -Doptimize=ReleaseFast
zig build -Dtarget=x86_64-windows -Doptimize=ReleaseFastFirst test locally.
Then build release binaries.
Then cross compile for deployment targets.
Common Mistakes
A common mistake is forgetting this line in the executable module:
.target = targetIf you create the target option but never pass it to the executable, -Dtarget=... will not affect that artifact.
Another mistake is assuming cross-compiled tests can always run on the build machine.
They cannot.
Another mistake is linking to host libraries when building for another target.
If your target is Linux, your linked libraries must be for Linux. If your target is Windows, your linked libraries must be for Windows.
The Important Idea
Cross compilation means building for a different target than the current machine.
In Zig, the normal pattern is:
const target = b.standardTargetOptions(.{});then:
.target = targetinside the module or artifact you are building.
From the command line, use:
zig build -Dtarget=x86_64-linuxor another target.
For pure Zig programs, cross compilation is usually straightforward. For programs that link C libraries or use platform-specific APIs, you must make sure those dependencies and APIs exist for the target platform.