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

Leave a Comment

Your email address will not be published. Required fields are marked *