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 "cpu-os-abi" (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,
// configure custom options
.dep_option_foo = true,
// pass string options as `[]const u8`
.dep_option_bar = @as([]const u8, "bar_cfg_string"),
});
// Declare dependency's artifact with the name from its build script
const johny_art = johny_dep.artifact("johny");
// Bundle artifact
b.installArtifact(johny_art);
exe.step.dependOn(&johny_art.step);
// 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 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(b.path("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
// Create private module
const hello_mod = b.createModule(.{
.root_source_file = b.path("src/hello.zig"),
});
// Add private module as import
exe.root_module.addImport("hello", hello_mod);
// Create and add private module in one go
exe.root_module.addAnonymousImport("hello", .{
.root_source_file = b.path("src/hello.zig"),
});
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 = b.path(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 fmt_step = b.step("fmt", "Run formatting checks");
const fmt = b.addFmt(.{
.paths = &.{
"src",
"build.zig",
},
.check = true,
});
fmt_step.dependOn(&fmt.step);
b.default_step.dependOn(fmt_step);
7) System tool tricks
// Check if a system tool exists
const echo_program = try b.findProgram(&.{"echo"}, &.{"check/this/path/", "and/this/one"});
// Get string output from a system tool
const string = b.run(&.{echo_program, "hello/world"});
const some_path = b.fmt("/my/other/path/{s}", .{string});
// Specify a system command as a build dependency
// Note: currently will not run as nothing depends on it!
const sys_command_build_dependency = b.addSystemCommand(&.{ echo_program, "I'm generating some text!" });
// Add a dependency on this command that takes the command's stdout and puts it in a file `zig-out/output.txt`
// Note: Now this does something!
b.default_step.dependOn(&b.addInstallFile(sys_command_build_dependency.captureStdOut(), "output.txt").step);
8) Code generation tricks
// Generating a .zig file from a system tool for use in build
const generate_zig_file_cmd = b.addSystemCommand(&.{ "python", "really_complex_zig_generator.py" });
// Passes "output_file.zig" as an arg to our really_complex_zig_generator.py script
const generated_zig_file = generate_zig_file_cmd.addOutputFileArg("output_file.zig");
// Making this .zig file importable in our main code
exe.root_module.addAnonymousImport("generated", .{
.root_source_file = generated_zig_file,
});
// Let's do another step of processing! This time "additional_processing.py" takes in our previously generated .zig file,
// and generates an additional .zig file
const additional_generation_cmd = b.addSystemCommand(&.{ "python", "additional_processing.py" });
// Passes "output_file.zig" to the python script + establishes that this depends on the previous step completing
additional_generation_cmd.addFileArg(generate_zig_file_cmd);
// Same as before, passes "another_output_file.zig" as an arg
const additional_generated_zig_file = additional_generation_cmd.addOutputFileArg("another_output_file.zig");
// Making this additional .zig file importable in our main code
exe.root_module.addAnonymousImport("additionalGenerated", .{
.root_source_file = additional_generated_zig_file,
});
// Generating a .zig file from another zig program in the workspace
// Assume another_exe was created with b.addExecutable(...), and takes the file path to generate as an arg
const another_exe_run_artifact = b.addRunArtifact(another_exe);
const zig_generated_zig_file = another_exe_run_artifact.addOutputFileArg("zig_generated_zig.zig");
exe.root_module.addAnonymousImport("zigGenerated", .{
.root_source_file = zig_generated_zig_file,
});
9) 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(b.path(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(b: *std.Build, obj: *std.Build.Step.Compile) void {
// Link a static library (archive file)
obj.addObjectFile(b.path("src/lib/mp_kernels.a"));
// Enable searching for libraries prefixed with "lib",
// such as "libcudart.so" or "libfoo.so"
obj.addLibraryPath(b.path("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
For the following examples, assume the following files exist:
cmake_style_cfg_header.h.in
:
#cmakedefine SOME_VAR
static const char *variable = "@SOME_STRING@";
autoconf_style_cfg_header.h.in
:
// Autoconf should fill in this #define for us
#undef SOME_VAR
// See previous "Code generation tricks" section for using system commands to generate files
// Assuming instead of generating a .zig file we generate a .c file:
// exe.root_module.addAnonymousImport("generated", .{
// .root_source_file = generated_zig_file,
// });
// Becomes:
exe.addCSourceFile(.{ .file = generated_c_file, .flags = &.{"-fyour", "-fflags"} });
// Generating a header file in the style of autoconf
// See: https://www.gnu.org/software/autoconf/manual/autoconf-2.68/html_node/Header-Templates.html#Header-Templates
const autoconf_cfg_header_cmd = b.addConfigHeader(.{ .style = .{ .autoconf = b.path("autoconf_style_cfg_header.h.in") } }, .{ .SOME_VAR = 12345 });
const autoconf_cfg_header_out = b.addInstallFile(autoconf_cfg_header_cmd.getOutput(), "autoconf_style_cfg_header.h");
b.default_step.dependOn(&autoconf_cfg_header_out.step);
// Generating a header file in the style of Cmake
// See: https://cmake.org/cmake/help/v3.30/command/configure_file.html
const cmake_cfg_header_cmd = b.addConfigHeader(.{ .style = .{ .cmake = b.path("cmake_style_cfg_header.h.in") } }, .{ .SOME_VAR = true, .SOME_STRING = "hello_world", .SOME_OTHER_STRING = "from_zig" });
const cmake_cfg_header_out = b.addInstallFile(cmake_cfg_header_cmd.getOutput(), "cmake_style_cfg_header.h");
b.default_step.dependOn(&cmake_cfg_header_out.step);
// Generating a header from scratch, with no "template" input file
const scratch_cfg_header_cmd = b.addConfigHeader(.{ .style = .blank }, .{ .SOME_VAR = true, .SOME_STRING = "hello_world", .SOME_OTHER_STRING = "from_zig" });
const scratch_cfg_header_out = b.addInstallFile(scratch_cfg_header_cmd.getOutput(), "scratch_style_cfg_header.h");
b.default_step.dependOn(&scratch_cfg_header_out.step);
The generated headers content should be:
zig-out/autoconf_style_cfg_header.h
:
/* This file was generated by ConfigHeader using the Zig Build System. */
// Autoconf should fill in this #define for us
#define SOME_VAR 12345
zig-out/cmake_style_cfg_header.h
:
/* This file was generated by ConfigHeader using the Zig Build System. */
#define SOME_VAR
static const char *variable = "hello_world";
static const char *some_other_variable = "from_zig";
zig-out/scratch_style_cfg_header.h
:
/* This file was generated by ConfigHeader using the Zig Build System. */
#ifndef CONFIG_H
#define CONFIG_H
#define SOME_VAR 1
#define SOME_STRING "hello_world"
#define SOME_OTHER_STRING "from_zig"
#endif /* CONFIG_H */