Skip to main content
Zig’s build system is powered by build.zig, a Zig source file that defines how your project is built. Unlike traditional build systems that use declarative configuration files, build.zig is executable Zig code that provides full programmatic control over the build process.

Overview

The build system is centered around the std.Build API, which provides a rich set of functions for creating executables, libraries, tests, and managing dependencies. Every build.zig file exports a public build function that receives a *std.Build parameter.

Basic Structure

const std = @import("std");

pub fn build(b: *std.Build) void {
    // Get standard target and optimization options
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Define build artifacts and steps here
}

Standard Options

Target Options

The standardTargetOptions function allows users to specify the target platform from the command line:
const target = b.standardTargetOptions(.{
    .default_target = .{
        .ofmt = .c,  // Default output format
    },
});
Users can override with:
zig build -Dtarget=x86_64-linux-gnu
zig build -Dtarget=aarch64-macos

Optimize Options

The standardOptimizeOption controls optimization mode:
const optimize = b.standardOptimizeOption(.{});
Available modes:
  • Debug - No optimizations, safety checks enabled
  • ReleaseSafe - Optimizations + safety checks
  • ReleaseFast - Optimizations, no safety checks
  • ReleaseSmall - Size optimizations
Users can specify:
zig build -Doptimize=ReleaseFast

Custom Build Options

Define custom build options using b.option():
const single_threaded = b.option(
    bool,
    "single-threaded",
    "Build artifacts that run in single threaded mode"
) orelse false;

Using Build Options in Code

Create an options module to expose build-time values to your code:
const exe_options = b.addOptions();
exe.root_module.addOptions("build_options", exe_options);

exe_options.addOption(u32, "mem_leak_frames", mem_leak_frames);
exe_options.addOption(bool, "have_llvm", enable_llvm);
exe_options.addOption([:0]const u8, "version", version);
In your source code:
const build_options = @import("build_options");

pub fn main() void {
    std.debug.print("Version: {s}\n", .{build_options.version});
    if (build_options.have_llvm) {
        // Use LLVM features
    }
}

Creating Build Artifacts

Executables

const exe = b.addExecutable(.{
    .name = "my-app",
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    }),
});

// Install the executable
b.installArtifact(exe);

Static Libraries

const lib = b.addLibrary(.{
    .name = "mylib",
    .linkage = .static,
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/lib.zig"),
        .target = target,
        .optimize = optimize,
    }),
});

b.installArtifact(lib);

Dynamic Libraries

const lib = b.addLibrary(.{
    .name = "mylib",
    .linkage = .dynamic,
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/lib.zig"),
        .target = target,
        .optimize = optimize,
    }),
    .version = .{ .major = 1, .minor = 0, .patch = 0 },
});

Object Files

const obj = b.addObject(.{
    .name = "module",
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/module.zig"),
        .target = target,
        .optimize = optimize,
    }),
});

Test Executables

const unit_tests = b.addTest(.{
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    }),
});

const run_unit_tests = b.addRunArtifact(unit_tests);

const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);

Build Steps

Steps define actions in the build process. Create custom build steps:
// Create a named step
const docs_step = b.step("docs", "Build documentation");

// Create substeps
const langref_step = b.step("langref", "Build language reference");
const std_docs_step = b.step("std-docs", "Build standard library docs");

// Compose steps
docs_step.dependOn(langref_step);
docs_step.dependOn(std_docs_step);

Running Commands

const run_cmd = b.addSystemCommand(&.{
    "wasm-opt",
    "-Oz",
    "--enable-bulk-memory",
});
run_cmd.addArtifactArg(exe);
run_cmd.addArg("-o");
const output = run_cmd.addOutputFileArg("output.wasm");

Module Configuration

Configure module properties:
const compiler_mod = b.createModule(.{
    .root_source_file = b.path("src/main.zig"),
    .target = target,
    .optimize = optimize,
    .strip = strip,
    .sanitize_thread = sanitize_thread,
    .single_threaded = single_threaded,
    .valgrind = valgrind,
});

// Add module imports
const aro_mod = b.createModule(.{
    .root_source_file = b.path("lib/compiler/aro/aro.zig"),
});
compiler_mod.addImport("aro", aro_mod);

Linking Libraries

System Libraries

// Link system libraries
exe.root_module.linkSystemLibrary("z", .{});
exe.root_module.linkSystemLibrary("zstd", .{});
exe.root_module.link_libc = true;
exe.root_module.link_libcpp = true;

Object Files

exe.root_module.addObjectFile(.{ .cwd_relative = "/path/to/lib.a" });

C Source Files

const cflags = [_][]const u8{
    "-std=c++17",
    "-D__STDC_CONSTANT_MACROS",
    "-fno-exceptions",
};

exe.root_module.addCSourceFile(.{
    .file = b.path("src/zig_llvm.cpp"),
    .flags = &cflags,
});

Installing Files

Install Artifacts

const install_exe = b.addInstallArtifact(exe, .{
    .dest_dir = .{ .override = .prefix },
});
b.getInstallStep().dependOn(&install_exe.step);

Install Files

// Install single file
b.installFile("LICENSE", "LICENSE");

// Install file with custom directory
const install_langref = b.addInstallFileWithDir(
    langref_file,
    .prefix,
    "doc/langref.html"
);

Install Directories

b.installDirectory(.{
    .source_dir = b.path("lib"),
    .install_dir = .lib,
    .install_subdir = "zig",
    .exclude_extensions = &[_][]const u8{
        ".gz",
        ".tar",
        "README.md",
    },
    .blank_extensions = &[_][]const u8{
        "test.zig",
    },
});

Advanced Examples

Conditional Compilation

const enable_llvm = b.option(
    bool,
    "enable-llvm",
    "Build with LLVM backend enabled"
) orelse false;

if (enable_llvm) {
    const llvm_libs = [_][]const u8{
        "LLVMCore",
        "LLVMSupport",
        "LLVMTarget",
    };
    
    for (llvm_libs) |lib_name| {
        exe.root_module.linkSystemLibrary(lib_name, .{});
    }
}

Version String Generation

lib/std/Build.zig:245-302
const zig_version: std.SemanticVersion = .{ .major = 0, .minor = 16, .patch = 0 };

const version_string = b.fmt("{d}.{d}.{d}", .{
    zig_version.major,
    zig_version.minor,
    zig_version.patch,
});

var code: u8 = undefined;
const git_describe = b.runAllowFail(&[_][]const u8{
    "git", "describe", "--match", "*.*.*",
    "--tags", "--abbrev=9",
}, &code, .Ignore) catch {
    break :v version_string;
};

Platform-Specific Configuration

if (target.result.os.tag == .windows) {
    exe.root_module.linkSystemLibrary("ws2_32", .{});
    exe.root_module.linkSystemLibrary("version", .{});
    exe.root_module.linkSystemLibrary("uuid", .{});
}

if (target.result.os.tag.isDarwin()) {
    exe.headerpad_max_install_names = true;
}

Reference

Key std.Build functions:
  • path() - Create a path relative to build root
  • pathJoin() - Join path components
  • fmt() - Format a string
  • dupe() - Duplicate a string
  • allocator - Access the build allocator
  • getInstallStep() - Get the default install step
  • step() - Create a named build step
See std.Build for the complete API.

Build docs developers (and LLMs) love