This community-driven doc is an attempt to expand on the official Zig Build System documentation as well as the past community contributions by xq, Sobeston and others.
Here you’ll find a list of tricks on how to use every part of the build system while making sure to conveniently name and lay out your build steps.
I. Basic Usage
1) Declare commonly used information upfront
// Resolve a query for a custom target
const my_target = b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.os_tag = .wasi,
});
// Resolve target triplet of "arch-os-libc" (native is default)
const target = b.standardTargetOptions(.{});
// Resolve optimization mode (debug is default)
const optimize = b.standardOptimizeOption(.{});
// Specify common lazy paths relative to the build script
const root_source_file = b.path("src/main.zig");
// Define single semantic version for your library, examples, etc.
const version = std.SemanticVersion{ .major = 0, .minor = 1, .patch = 0 };
2) Declare user options upfront
const is_enabled = b.option(bool, "is_enabled", "Enable some capability") orelse false;
const options = b.addOptions();
options.addOption(bool, "is_enabled", is_enabled);
3) Declare dependencies as well as their artifacts and modules upfront
// Declare package dependency called "johny" with your info
const johny_dep = b.dependency("johny", .{
.target = target,
.optimize = optimize,
});
// Declare dependency's artifact with the name from its build script
const johny_art = johny_dep.artifact("johny");
// Declare dependency's module with the name from its build script
const johny_mod = johny_dep.module("johny");
4) Declare your library’s module and expose it to your users
const lib_mod = b.addModule("my_mod", .{ .root_source_file = root_source_file });
5) Declare every major Compile
or Run
step upfront
const exe_step = b.step("exe", "Run executable");
const exe = b.addExecutable(.{
.name = "my_exe",
.target = target,
.version = version,
.optimize = optimize,
.root_source_file = root_source_file,
});
// Add dependency module as the root module's import
exe.root_module.addImport("my_johny_import", johny_mod);
// Add user options as the root module's import
exe.root_module.addOptions("config", options);
6) Add build artifacts to depend on
// Add install artifact and depend the default step on it in one go
b.installArtifact(exe);
// Declare a separate run or install artifact step
const exe_run = b.addRunArtifact(exe);
const exe_install = b.addInstallArtifact(exe, .{});
// Pass CLI arguments to the run artifact
if (b.args) |args| {
exe_run.addArgs(args);
}
// Optionally change the directory path to run the artifact in from current to custom
exe_run.setCwd(std.Build.LazyPath.relative("my-exe/"));
// Depend your pre-declared custom step (`zig build exe`) on the build artifact step
exe_step.dependOn(&exe_run.step);
// Depend the default step (`zig build`) on your custom step
b.default_step.dependOn(exe_step);
7) Add private modules for use only within your project
addModule
provides the module for users of your project so they can import it in their projects. If you just want to use it privately in your own project, use createModule
instead:
const hello_mod = b.createModule(.{
.root_source_file = .{ .path = "src/hello.zig" },
});
// Add the private module as an anonymous import
exe.root_module.addAnonymousImport("hello", hello_mod);
II. Extra Steps
1) Emit docs into docs
directory installed in prefix directory zig-out
const docs_step = b.step("docs", "Emit docs");
const docs_install = b.addInstallDirectory(.{
.install_dir = .prefix,
.install_subdir = "docs",
.source_dir = lib.getEmittedDocs(),
});
docs_step.dependOn(&docs_install.step);
b.default_step.dependOn(docs_step);
2) Put static strings as global constants after the build
function
const std = @import("std");
pub fn build(b: *std.Build) void {
...
}
const EXAMPLES_DIR = "examples/";
const EXAMPLE_NAMES = &.{
"example1",
"example2",
};
3) Run your library’s example suite
const examples_step = b.step("examples", "Run examples");
inline for (EXAMPLE_NAMES) |EXAMPLE_NAME| {
const example = b.addExecutable(.{
.name = EXAMPLE_NAME,
.target = target,
.version = version,
.optimize = optimize,
.root_source_file = std.Build.LazyPath.relative(EXAMPLES_DIR ++ EXAMPLE_NAME ++ "/main.zig"),
});
example.root_module.addImport("my_import", lib_mod);
const example_run = b.addRunArtifact(example);
examples_step.dependOn(&example_run.step);
}
b.default_step.dependOn(examples_step);
4) Run your test suite exposed with std.testing.refAllDecls
const tests_step = b.step("tests", "Run tests");
const tests = b.addTest(.{
.target = target,
.root_source_file = root_source_file,
});
const tests_run = b.addRunArtifact(tests);
tests_step.dependOn(&tests_run.step);
b.default_step.dependOn(tests_step);
5) Generate code coverage report with the kcov
system dependency
const cov_step = b.step("cov", "Generate coverage");
const cov_run = b.addSystemCommand(&.{ "kcov", "--clean", "--include-pattern=src/", "kcov-output" });
cov_run.addArtifactArg(tests);
cov_step.dependOn(&cov_run.step);
b.default_step.dependOn(cov_step);
6) Run formatting checks
const lints_step = b.step("lints", "Run lints");
const lints = b.addFmt(.{
.paths = &.{ "src", "build.zig" },
.check = true,
});
lints_step.dependOn(&lints.step);
b.default_step.dependOn(lints_step);
7) Code generation tricks
// TODO
b.addWriteFiles()
8) Clean zig-out
and zig-cache
directories
const clean_step = b.step("clean", "Clean up");
clean_step.dependOn(&b.addRemoveDirTree(b.install_path).step);
if (@import("builtin").os.tag != .windows) {
clean_step.dependOn(&b.addRemoveDirTree(b.pathFromRoot("zig-cache")).step);
}
III. C Dependencies
1) Add C-specific stuff to a Compile
step
lib.addCSourceFiles(.{
.root = C_ROOT_DIR,
.files = &(C_CORE_FILES ++ C_LIB_FILES),
.flags = C_FLAGS,
});
lib.addIncludePath(std.Build.LazyPath.relative(C_LIB_DIR));
lib.defineCMacro("MY_C_MACRO", null);
lib.linkSystemLibrary("readline");
lib.linkLibCpp();
lib.linkLibC();
Static string data goes after the build
function to avoid cluttering the main script path.
const C_ROOT_DIR = "deps/my-c-lib/";
const C_CORE_FILES = .{
"file1.c",
"file2.c",
};
const C_LIB_FILES = .{
"libfile1.c",
"libfile2.c",
};
const C_FLAGS = &.{
"-Wall",
"-O2",
};
2) Link dynamic and static libraries
// If you are compiling multiple objects that have the same
// library dependencies, make a helper function to attach them in one go.
fn linkLibraries(obj: *std.Build.Step.Compile) void {
// Link a static library (archive file)
obj.addObjectFile(std.Build.LazyPath.relative("src/lib/mp_kernels.a"));
// Enable searching for libraries prefixed with "lib",
// such as "libcudart.so" or "libfoo.so"
obj.addLibraryPath(std.Build.LazyPath.relative("deps/cuda/lib64"));
// Perform a search for libraries in specified paths.
// Note that the "lib" prefix has been omitted, such as in "cudart",
// which actually has the name "libcudart.so"
obj.linkSystemLibrary("cudart");
obj.linkSystemLibrary("nvrtc");
obj.linkSystemLibrary("cuda");
// Link system libc
obj.linkLibC();
}
3) Dependence on the Zig Compiler version
const builtin = @import("builtin");
comptime {
const required_zig = "0.12.0-dev.3302";
const current_zig = builtin.zig_version;
const min_zig = std.SemanticVersion.parse(required_zig) catch unreachable;
if (current_zig.order(min_zig) == .lt) {
const error_message =
\\Sorry, it looks like your version of zig is too old. :-(
\\
\\(Insert your program name here) requires development build {}
\\
\\Please download a development ("master") build from
\\
\\https://ziglang.org/download/
\\
\\
;
@compileError(std.fmt.comptimePrint(error_message, .{min_zig}));
}
}
4) Code generation tricks
// TODO
lib.installHeader()