Zig Build System
Zig includes its own build system written entirely in Zig. Instead of Makefiles, CMake scripts, or shell scripts, you write a build.zig file that describes how to compile, link, and test your project. The build system uses the same Zig language you already know — no separate syntax to learn.
Project Structure
my-project/
├── build.zig ← Build instructions (Zig code)
├── build.zig.zon ← Package metadata and dependencies
└── src/
├── main.zig ← Application entry point
└── lib.zig ← Library code
Run zig init inside a new folder to generate this structure automatically.
A Minimal build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "my-app",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
}
build() function:
|
standardTargetOptions ← lets user set --target from command line
|
standardOptimizeOption ← lets user set -Doptimize=ReleaseFast etc.
|
addExecutable ← defines what to compile
|
installArtifact ← copies the result to zig-out/bin/
Build Modes
Mode | Command flag | What it does ─────────────────┼────────────────────────────┼────────────────────── Debug (default) | (none) | Fast compile, safety checks on ReleaseSafe | -Doptimize=ReleaseSafe | Optimized, safety checks on ReleaseFast | -Doptimize=ReleaseFast | Max speed, no safety checks ReleaseSmall | -Doptimize=ReleaseSmall | Smallest binary size
zig build ← Debug build zig build -Doptimize=ReleaseFast ← Fastest binary zig build -Doptimize=ReleaseSmall ← Smallest binary
Adding a Run Step
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
const run_step = b.step("run", "Run the application");
run_step.dependOn(&run_cmd.step);
After adding this, zig build run compiles and immediately runs your program. Arguments after -- are forwarded to your program: zig build run -- arg1 arg2.
zig build run
|
compile exe
|
run exe in terminal
|
output appears
Adding Tests
const unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const run_tests = b.addRunArtifact(unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_tests.step);
Now zig build test compiles your test blocks and runs them. Tests are written directly in your source files using the test keyword.
Building a Library
const lib = b.addStaticLibrary(.{
.name = "mylib",
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(lib);
addStaticLibrary → produces libmylib.a addSharedLibrary → produces libmylib.so (Linux) / mylib.dll (Windows) addExecutable → produces my-app (binary)
Cross-Compilation
Zig cross-compiles without any extra tools. Pass a --target flag to build for a different platform from your current machine:
zig build -Dtarget=aarch64-linux ← ARM 64-bit Linux zig build -Dtarget=x86_64-windows-gnu ← Windows 64-bit zig build -Dtarget=wasm32-freestanding ← WebAssembly zig build -Dtarget=riscv64-linux ← RISC-V Linux
Your machine (x86_64 Linux)
|
zig build -Dtarget=aarch64-linux
|
┌────▼────────────────────────────┐
│ Zig cross-compiler │
│ (bundled, no extra tools) │
└────┬────────────────────────────┘
|
aarch64 binary → copy to ARM device and run
Adding Dependencies (build.zig.zon)
// build.zig.zon
.{
.name = "my-project",
.version = "0.1.0",
.dependencies = .{
.zig_clap = .{
.url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.9.1.tar.gz",
.hash = "12209d2b01...",
},
},
.paths = .{""},
}
Fetch dependencies with zig fetch --save <url>. Zig downloads, verifies the hash, and makes the package available in your build.
Using a Dependency in build.zig
const zig_clap = b.dependency("zig_clap", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("clap", zig_clap.module("clap"));
build.zig.zon defines: "zig_clap" → URL + hash
build.zig fetches it: b.dependency("zig_clap", ...)
exe imports it: addImport("clap", ...)
src/main.zig uses it: const clap = @import("clap");
Custom Build Steps
You can add any custom step to the build graph — generating code, running external tools, copying assets:
const gen_step = b.addSystemCommand(&.{ "python3", "codegen.py" });
exe.step.dependOn(&gen_step.step);
const copy_step = b.addInstallFile(
b.path("assets/icon.png"),
"bin/icon.png",
);
b.getInstallStep().dependOn(©_step.step);
Build Graph Visualization
zig build run
│
▼
[run_step]
│ dependsOn
▼
[install_step]
│ dependsOn
▼
[compile exe]
│ dependsOn
▼
[compile modules] [fetch deps]
Zig builds the dependency graph from your build.zig and executes steps in the correct order, in parallel where possible. Steps that do not depend on each other run concurrently, making builds fast on multi-core machines.
Common Build Commands
zig build ← compile (debug mode) zig build run ← compile and run zig build test ← compile and run tests zig build install ← compile and install to zig-out/ zig build --help ← list all available steps zig build -p /usr/local ← install to custom prefix
