zig/lib/std / Build.zig

const std = @import("std.zig");
const builtin = @import("builtin");
const io = std.io;
const fs = std.fs;
const mem = std.mem;
const debug = std.debug;
const panic = std.debug.panic;
const assert = debug.assert;
const log = std.log;
const ArrayList = std.ArrayList;
const StringHashMap = std.StringHashMap;
const Allocator = mem.Allocator;
const process = std.process;
const EnvMap = std.process.EnvMap;
const fmt_lib = std.fmt;
const File = std.fs.File;
const CrossTarget = std.zig.CrossTarget;
const NativeTargetInfo = std.zig.system.NativeTargetInfo;
const Sha256 = std.crypto.hash.sha2.Sha256;
const Build = @This();

Cache

Build/Cache.zig
pub const Cache = @import("Build/Cache.zig");

LibExeObjStep

deprecated: use Step.Compile.

pub const LibExeObjStep = Step.Compile;

Builder

deprecated: use Build.

pub const Builder = Build;

InstallDirectoryOptions

deprecated: use Step.InstallDir.Options

pub const InstallDirectoryOptions = Step.InstallDir.Options;

Step

Build/Step.zig
pub const Step = @import("Build/Step.zig");

CheckFileStep

Build/Step/CheckFile.zig

deprecated: use Step.CheckFile.

pub const CheckFileStep = @import("Build/Step/CheckFile.zig");

CheckObjectStep

Build/Step/CheckObject.zig

deprecated: use Step.CheckObject.

pub const CheckObjectStep = @import("Build/Step/CheckObject.zig");

ConfigHeaderStep

Build/Step/ConfigHeader.zig

deprecated: use Step.ConfigHeader.

pub const ConfigHeaderStep = @import("Build/Step/ConfigHeader.zig");

FmtStep

Build/Step/Fmt.zig

deprecated: use Step.Fmt.

pub const FmtStep = @import("Build/Step/Fmt.zig");

InstallArtifactStep

Build/Step/InstallArtifact.zig

deprecated: use Step.InstallArtifact.

pub const InstallArtifactStep = @import("Build/Step/InstallArtifact.zig");

InstallDirStep

Build/Step/InstallDir.zig

deprecated: use Step.InstallDir.

pub const InstallDirStep = @import("Build/Step/InstallDir.zig");

InstallFileStep

Build/Step/InstallFile.zig

deprecated: use Step.InstallFile.

pub const InstallFileStep = @import("Build/Step/InstallFile.zig");

ObjCopyStep

Build/Step/ObjCopy.zig

deprecated: use Step.ObjCopy.

pub const ObjCopyStep = @import("Build/Step/ObjCopy.zig");

CompileStep

Build/Step/Compile.zig

deprecated: use Step.Compile.

pub const CompileStep = @import("Build/Step/Compile.zig");

OptionsStep

Build/Step/Options.zig

deprecated: use Step.Options.

pub const OptionsStep = @import("Build/Step/Options.zig");

RemoveDirStep

Build/Step/RemoveDir.zig

deprecated: use Step.RemoveDir.

pub const RemoveDirStep = @import("Build/Step/RemoveDir.zig");

RunStep

Build/Step/Run.zig

deprecated: use Step.Run.

pub const RunStep = @import("Build/Step/Run.zig");

TranslateCStep

Build/Step/TranslateC.zig

deprecated: use Step.TranslateC.

pub const TranslateCStep = @import("Build/Step/TranslateC.zig");

WriteFileStep

Build/Step/WriteFile.zig

deprecated: use Step.WriteFile.

pub const WriteFileStep = @import("Build/Step/WriteFile.zig");

FileSource

deprecated: use LazyPath.

pub const FileSource = LazyPath;

install_tls: TopLevelStep,
uninstall_tls: TopLevelStep,
allocator: Allocator,
user_input_options: UserInputOptionsMap,
available_options_map: AvailableOptionsMap,
available_options_list: ArrayList(AvailableOption),
verbose: bool,
verbose_link: bool,
verbose_cc: bool,
verbose_air: bool,
verbose_llvm_ir: ?[]const u8,
verbose_llvm_bc: ?[]const u8,
verbose_cimport: bool,
verbose_llvm_cpu_features: bool,
reference_trace: ?u32 = null,
invalid_user_input: bool,
zig_exe: [:0]const u8,
default_step: *Step,
env_map: *EnvMap,
top_level_steps: std.StringArrayHashMapUnmanaged(*TopLevelStep),
install_prefix: []const u8,
dest_dir: ?[]const u8,
lib_dir: []const u8,
exe_dir: []const u8,
h_dir: []const u8,
install_path: []const u8,
sysroot: ?[]const u8 = null,
search_prefixes: ArrayList([]const u8),
libc_file: ?[]const u8 = null,
installed_files: ArrayList(InstalledFile),
build_root: Cache.Directory,
cache_root: Cache.Directory,
global_cache_root: Cache.Directory,
cache: *Cache,
zig_lib_dir: ?LazyPath,
vcpkg_root: VcpkgRoot = .unattempted,
pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
args: ?[][]const u8 = null,
debug_log_scopes: []const []const u8 = &.{},
debug_compile_errors: bool = false,
debug_pkg_config: bool = false,
debug_stack_frames_count: u8 = 8,

enable_darling: bool = false,
enable_qemu: bool = false,
enable_rosetta: bool = false,
enable_wasmtime: bool = false,
enable_wine: bool = false,
glibc_runtimes_dir: ?[]const u8 = null,

host: NativeTargetInfo,

dep_prefix: []const u8 = "",

modules: std.StringArrayHashMap(*Module),
initialized_deps: *InitializedDepMap,
available_deps: AvailableDeps,

const AvailableDeps = []const struct { []const u8, []const u8 };

const InitializedDepMap = std.HashMap(InitializedDepKey, *Dependency, InitializedDepContext, std.hash_map.default_max_load_percentage);
const InitializedDepKey = struct {
    build_root_string: []const u8,
    user_input_options: UserInputOptionsMap,
};

const InitializedDepContext = struct {
    allocator: Allocator,

hash()

Path to the directory containing build.zig. Number of stack frames captured when a StackTrace is recorded for debug purposes, in particular at Step creation. Set to 0 to disable stack collection. Experimental. Use system Darling installation to run cross compiled macOS build artifacts. Use system QEMU installation to run cross compiled foreign architecture build artifacts. Darwin. Use Rosetta to run x86_64 macOS build artifacts on arm64 macOS. Use system Wasmtime installation to run cross compiled wasm/wasi build artifacts. Use system Wine installation to run cross compiled Windows build artifacts. After following the steps in https://github.com/ziglang/zig/wiki/Updating-libc#glibc, this will be the directory $glibc-build-dir/install/glibcs Given the example of the aarch64 target, this is the directory that contains the path aarch64-linux-gnu/lib/ld-linux-aarch64.so.1. Information about the native target. Computed before build() is invoked. A map from build root dirs to the corresponding *Dependency. This is shared with all child Builds. A mapping from dependency names to package hashes.

    pub fn hash(self: @This(), k: InitializedDepKey) u64 {
        var hasher = std.hash.Wyhash.init(0);
        hasher.update(k.build_root_string);
        hashUserInputOptionsMap(self.allocator, k.user_input_options, &hasher);
        return hasher.final();
    }

eql()

    pub fn eql(self: @This(), lhs: InitializedDepKey, rhs: InitializedDepKey) bool {
        _ = self;
        if (!std.mem.eql(u8, lhs.build_root_string, rhs.build_root_string))
            return false;

        if (lhs.user_input_options.count() != rhs.user_input_options.count())
            return false;

        var it = lhs.user_input_options.iterator();
        while (it.next()) |lhs_entry| {
            const rhs_value = rhs.user_input_options.get(lhs_entry.key_ptr.*) orelse return false;
            if (!userValuesAreSame(lhs_entry.value_ptr.*.value, rhs_value.value))
                return false;
        }

        return true;
    }
};

RunError

pub const RunError = error{
    ReadFailure,
    ExitCodeFailure,
    ProcessTerminated,
    ExecNotSupported,
} || std.ChildProcess.SpawnError;

PkgConfigError

pub const PkgConfigError = error{
    PkgConfigCrashed,
    PkgConfigFailed,
    PkgConfigNotInstalled,
    PkgConfigInvalidOutput,
};

PkgConfigPkg

pub const PkgConfigPkg = struct {
    name: []const u8,
    desc: []const u8,
};

CStd

pub const CStd = enum {
    C89,
    C99,
    C11,
};

const UserInputOptionsMap = StringHashMap(UserInputOption);
const AvailableOptionsMap = StringHashMap(AvailableOption);

const AvailableOption = struct {
    name: []const u8,
    type_id: TypeId,
    description: []const u8,
    enum_options: ?[]const []const u8,
};

const UserInputOption = struct {
    name: []const u8,
    value: UserValue,
    used: bool,
};

const UserValue = union(enum) {
    flag: void,
    scalar: []const u8,
    list: ArrayList([]const u8),
    map: StringHashMap(*const UserValue),
};

const TypeId = enum {
    bool,
    int,
    float,
    @"enum",
    string,
    list,
    build_id,
};

const TopLevelStep = struct {
    pub const base_id = .top_level;

    step: Step,
    description: []const u8,
};

DirList

If the type_id is enum this provides the list of enum options

pub const DirList = struct {
    lib_dir: ?[]const u8 = null,
    exe_dir: ?[]const u8 = null,
    include_dir: ?[]const u8 = null,
};

create()

pub fn create(
    allocator: Allocator,
    zig_exe: [:0]const u8,
    build_root: Cache.Directory,
    cache_root: Cache.Directory,
    global_cache_root: Cache.Directory,
    host: NativeTargetInfo,
    cache: *Cache,
    available_deps: AvailableDeps,
) !*Build {
    const env_map = try allocator.create(EnvMap);
    env_map.* = try process.getEnvMap(allocator);

    const initialized_deps = try allocator.create(InitializedDepMap);
    initialized_deps.* = InitializedDepMap.initContext(allocator, .{ .allocator = allocator });

    const self = try allocator.create(Build);
    self.* = .{
        .zig_exe = zig_exe,
        .build_root = build_root,
        .cache_root = cache_root,
        .global_cache_root = global_cache_root,
        .cache = cache,
        .verbose = false,
        .verbose_link = false,
        .verbose_cc = false,
        .verbose_air = false,
        .verbose_llvm_ir = null,
        .verbose_llvm_bc = null,
        .verbose_cimport = false,
        .verbose_llvm_cpu_features = false,
        .invalid_user_input = false,
        .allocator = allocator,
        .user_input_options = UserInputOptionsMap.init(allocator),
        .available_options_map = AvailableOptionsMap.init(allocator),
        .available_options_list = ArrayList(AvailableOption).init(allocator),
        .top_level_steps = .{},
        .default_step = undefined,
        .env_map = env_map,
        .search_prefixes = ArrayList([]const u8).init(allocator),
        .install_prefix = undefined,
        .lib_dir = undefined,
        .exe_dir = undefined,
        .h_dir = undefined,
        .dest_dir = env_map.get("DESTDIR"),
        .installed_files = ArrayList(InstalledFile).init(allocator),
        .install_tls = .{
            .step = Step.init(.{
                .id = .top_level,
                .name = "install",
                .owner = self,
            }),
            .description = "Copy build artifacts to prefix path",
        },
        .uninstall_tls = .{
            .step = Step.init(.{
                .id = .top_level,
                .name = "uninstall",
                .owner = self,
                .makeFn = makeUninstall,
            }),
            .description = "Remove build artifacts from prefix path",
        },
        .zig_lib_dir = null,
        .install_path = undefined,
        .args = null,
        .host = host,
        .modules = std.StringArrayHashMap(*Module).init(allocator),
        .initialized_deps = initialized_deps,
        .available_deps = available_deps,
    };
    try self.top_level_steps.put(allocator, self.install_tls.step.name, &self.install_tls);
    try self.top_level_steps.put(allocator, self.uninstall_tls.step.name, &self.uninstall_tls);
    self.default_step = &self.install_tls.step;
    return self;
}

fn createChild(
    parent: *Build,
    dep_name: []const u8,
    build_root: Cache.Directory,
    pkg_deps: AvailableDeps,
    user_input_options: UserInputOptionsMap,
) !*Build {
    const child = try createChildOnly(parent, dep_name, build_root, pkg_deps, user_input_options);
    try determineAndApplyInstallPrefix(child);
    return child;
}

fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Directory, pkg_deps: AvailableDeps, user_input_options: UserInputOptionsMap) !*Build {
    const allocator = parent.allocator;
    const child = try allocator.create(Build);
    child.* = .{
        .allocator = allocator,
        .install_tls = .{
            .step = Step.init(.{
                .id = .top_level,
                .name = "install",
                .owner = child,
            }),
            .description = "Copy build artifacts to prefix path",
        },
        .uninstall_tls = .{
            .step = Step.init(.{
                .id = .top_level,
                .name = "uninstall",
                .owner = child,
                .makeFn = makeUninstall,
            }),
            .description = "Remove build artifacts from prefix path",
        },
        .user_input_options = user_input_options,
        .available_options_map = AvailableOptionsMap.init(allocator),
        .available_options_list = ArrayList(AvailableOption).init(allocator),
        .verbose = parent.verbose,
        .verbose_link = parent.verbose_link,
        .verbose_cc = parent.verbose_cc,
        .verbose_air = parent.verbose_air,
        .verbose_llvm_ir = parent.verbose_llvm_ir,
        .verbose_llvm_bc = parent.verbose_llvm_bc,
        .verbose_cimport = parent.verbose_cimport,
        .verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features,
        .reference_trace = parent.reference_trace,
        .invalid_user_input = false,
        .zig_exe = parent.zig_exe,
        .default_step = undefined,
        .env_map = parent.env_map,
        .top_level_steps = .{},
        .install_prefix = undefined,
        .dest_dir = parent.dest_dir,
        .lib_dir = parent.lib_dir,
        .exe_dir = parent.exe_dir,
        .h_dir = parent.h_dir,
        .install_path = parent.install_path,
        .sysroot = parent.sysroot,
        .search_prefixes = parent.search_prefixes,
        .libc_file = parent.libc_file,
        .installed_files = ArrayList(InstalledFile).init(allocator),
        .build_root = build_root,
        .cache_root = parent.cache_root,
        .global_cache_root = parent.global_cache_root,
        .cache = parent.cache,
        .zig_lib_dir = parent.zig_lib_dir,
        .debug_log_scopes = parent.debug_log_scopes,
        .debug_compile_errors = parent.debug_compile_errors,
        .debug_pkg_config = parent.debug_pkg_config,
        .enable_darling = parent.enable_darling,
        .enable_qemu = parent.enable_qemu,
        .enable_rosetta = parent.enable_rosetta,
        .enable_wasmtime = parent.enable_wasmtime,
        .enable_wine = parent.enable_wine,
        .glibc_runtimes_dir = parent.glibc_runtimes_dir,
        .host = parent.host,
        .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }),
        .modules = std.StringArrayHashMap(*Module).init(allocator),
        .initialized_deps = parent.initialized_deps,
        .available_deps = pkg_deps,
    };
    try child.top_level_steps.put(allocator, child.install_tls.step.name, &child.install_tls);
    try child.top_level_steps.put(allocator, child.uninstall_tls.step.name, &child.uninstall_tls);
    child.default_step = &child.install_tls.step;
    return child;
}

fn userInputOptionsFromArgs(allocator: Allocator, args: anytype) UserInputOptionsMap {
    var user_input_options = UserInputOptionsMap.init(allocator);
    inline for (@typeInfo(@TypeOf(args)).Struct.fields) |field| {
        const v = @field(args, field.name);
        const T = @TypeOf(v);
        switch (T) {
            CrossTarget => {
                user_input_options.put(field.name, .{
                    .name = field.name,
                    .value = .{ .scalar = v.zigTriple(allocator) catch @panic("OOM") },
                    .used = false,
                }) catch @panic("OOM");
                user_input_options.put("cpu", .{
                    .name = "cpu",
                    .value = .{
                        .scalar = if (v.isNativeCpu())
                            "native"
                        else
                            serializeCpu(allocator, v.getCpu()) catch unreachable,
                    },
                    .used = false,
                }) catch @panic("OOM");
            },
            []const u8 => {
                user_input_options.put(field.name, .{
                    .name = field.name,
                    .value = .{ .scalar = v },
                    .used = false,
                }) catch @panic("OOM");
            },
            else => switch (@typeInfo(T)) {
                .Bool => {
                    user_input_options.put(field.name, .{
                        .name = field.name,
                        .value = .{ .scalar = if (v) "true" else "false" },
                        .used = false,
                    }) catch @panic("OOM");
                },
                .Enum, .EnumLiteral => {
                    user_input_options.put(field.name, .{
                        .name = field.name,
                        .value = .{ .scalar = @tagName(v) },
                        .used = false,
                    }) catch @panic("OOM");
                },
                .Int => {
                    user_input_options.put(field.name, .{
                        .name = field.name,
                        .value = .{ .scalar = std.fmt.allocPrint(allocator, "{d}", .{v}) catch @panic("OOM") },
                        .used = false,
                    }) catch @panic("OOM");
                },
                else => @compileError("option '" ++ field.name ++ "' has unsupported type: " ++ @typeName(T)),
            },
        }
    }

    return user_input_options;
}

const OrderedUserValue = union(enum) {
    flag: void,
    scalar: []const u8,
    list: ArrayList([]const u8),
    map: ArrayList(Pair),

    const Pair = struct {
        name: []const u8,
        value: OrderedUserValue,
        fn lessThan(_: void, lhs: Pair, rhs: Pair) bool {
            return std.ascii.lessThanIgnoreCase(lhs.name, rhs.name);
        }
    };

    fn hash(self: OrderedUserValue, hasher: *std.hash.Wyhash) void {
        switch (self) {
            .flag => {},
            .scalar => |scalar| hasher.update(scalar),
            // lists are already ordered
            .list => |list| for (list.items) |list_entry|
                hasher.update(list_entry),
            .map => |map| for (map.items) |map_entry| {
                hasher.update(map_entry.name);
                map_entry.value.hash(hasher);
            },
        }
    }

    fn mapFromUnordered(allocator: Allocator, unordered: std.StringHashMap(*const UserValue)) ArrayList(Pair) {
        var ordered = ArrayList(Pair).init(allocator);
        var it = unordered.iterator();
        while (it.next()) |entry| {
            ordered.append(.{
                .name = entry.key_ptr.*,
                .value = OrderedUserValue.fromUnordered(allocator, entry.value_ptr.*.*),
            }) catch @panic("OOM");
        }

        std.mem.sortUnstable(Pair, ordered.items, {}, Pair.lessThan);
        return ordered;
    }

    fn fromUnordered(allocator: Allocator, unordered: UserValue) OrderedUserValue {
        return switch (unordered) {
            .flag => .{ .flag = {} },
            .scalar => |scalar| .{ .scalar = scalar },
            .list => |list| .{ .list = list },
            .map => |map| .{ .map = OrderedUserValue.mapFromUnordered(allocator, map) },
        };
    }
};

const OrderedUserInputOption = struct {
    name: []const u8,
    value: OrderedUserValue,
    used: bool,

    fn hash(self: OrderedUserInputOption, hasher: *std.hash.Wyhash) void {
        hasher.update(self.name);
        self.value.hash(hasher);
    }

    fn fromUnordered(allocator: Allocator, user_input_option: UserInputOption) OrderedUserInputOption {
        return OrderedUserInputOption{
            .name = user_input_option.name,
            .used = user_input_option.used,
            .value = OrderedUserValue.fromUnordered(allocator, user_input_option.value),
        };
    }

    fn lessThan(_: void, lhs: OrderedUserInputOption, rhs: OrderedUserInputOption) bool {
        return std.ascii.lessThanIgnoreCase(lhs.name, rhs.name);
    }
};

// The hash should be consistent with the same values given a different order.
// This function takes a user input map, orders it, then hashes the contents.
fn hashUserInputOptionsMap(allocator: Allocator, user_input_options: UserInputOptionsMap, hasher: *std.hash.Wyhash) void {
    var ordered = ArrayList(OrderedUserInputOption).init(allocator);
    var it = user_input_options.iterator();
    while (it.next()) |entry|
        ordered.append(OrderedUserInputOption.fromUnordered(allocator, entry.value_ptr.*)) catch @panic("OOM");

    std.mem.sortUnstable(OrderedUserInputOption, ordered.items, {}, OrderedUserInputOption.lessThan);

    // juice it
    for (ordered.items) |user_option|
        user_option.hash(hasher);
}

fn determineAndApplyInstallPrefix(b: *Build) !void {
    // Create an installation directory local to this package. This will be used when
    // dependant packages require a standard prefix, such as include directories for C headers.
    var hash = b.cache.hash;
    // Random bytes to make unique. Refresh this with new random bytes when
    // implementation is modified in a non-backwards-compatible way.
    hash.add(@as(u32, 0xd8cb0055));
    hash.addBytes(b.dep_prefix);

    var wyhash = std.hash.Wyhash.init(0);
    hashUserInputOptionsMap(b.allocator, b.user_input_options, &wyhash);
    hash.add(wyhash.final());

    const digest = hash.final();
    const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &digest });
    b.resolveInstallPrefix(install_prefix, .{});
}

destroy()

pub fn destroy(b: *Build) void {
    b.env_map.deinit();
    b.top_level_steps.deinit(b.allocator);
    b.allocator.destroy(b);
}

resolveInstallPrefix()

This function is intended to be called by lib/build_runner.zig, not a build.zig file.

pub fn resolveInstallPrefix(self: *Build, install_prefix: ?[]const u8, dir_list: DirList) void {
    if (self.dest_dir) |dest_dir| {
        self.install_prefix = install_prefix orelse "/usr";
        self.install_path = self.pathJoin(&.{ dest_dir, self.install_prefix });
    } else {
        self.install_prefix = install_prefix orelse
            (self.build_root.join(self.allocator, &.{"zig-out"}) catch @panic("unhandled error"));
        self.install_path = self.install_prefix;
    }

    var lib_list = [_][]const u8{ self.install_path, "lib" };
    var exe_list = [_][]const u8{ self.install_path, "bin" };
    var h_list = [_][]const u8{ self.install_path, "include" };

    if (dir_list.lib_dir) |dir| {
        if (std.fs.path.isAbsolute(dir)) lib_list[0] = self.dest_dir orelse "";
        lib_list[1] = dir;
    }

    if (dir_list.exe_dir) |dir| {
        if (std.fs.path.isAbsolute(dir)) exe_list[0] = self.dest_dir orelse "";
        exe_list[1] = dir;
    }

    if (dir_list.include_dir) |dir| {
        if (std.fs.path.isAbsolute(dir)) h_list[0] = self.dest_dir orelse "";
        h_list[1] = dir;
    }

    self.lib_dir = self.pathJoin(&lib_list);
    self.exe_dir = self.pathJoin(&exe_list);
    self.h_dir = self.pathJoin(&h_list);
}

addOptions()

pub fn addOptions(self: *Build) *Step.Options {
    return Step.Options.create(self);
}

ExecutableOptions

pub const ExecutableOptions = struct {
    name: []const u8,
    root_source_file: ?LazyPath = null,
    version: ?std.SemanticVersion = null,
    target: CrossTarget = .{},
    optimize: std.builtin.OptimizeMode = .Debug,
    linkage: ?Step.Compile.Linkage = null,
    max_rss: usize = 0,
    link_libc: ?bool = null,
    single_threaded: ?bool = null,
    use_llvm: ?bool = null,
    use_lld: ?bool = null,
    zig_lib_dir: ?LazyPath = null,
    main_mod_path: ?LazyPath = null,
    win32_manifest: ?LazyPath = null,

    main_pkg_path: ?LazyPath = null,
};

addExecutable()

Embed a .manifest file in the compilation if the object format supports it. https://learn.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference Manifest files must have the extension .manifest. Can be set regardless of target. The .manifest file will be ignored if the target object format does not support embedded manifests. Deprecated; use main_mod_path.

pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile {
    return Step.Compile.create(b, .{
        .name = options.name,
        .root_source_file = options.root_source_file,
        .version = options.version,
        .target = options.target,
        .optimize = options.optimize,
        .kind = .exe,
        .linkage = options.linkage,
        .max_rss = options.max_rss,
        .link_libc = options.link_libc,
        .single_threaded = options.single_threaded,
        .use_llvm = options.use_llvm,
        .use_lld = options.use_lld,
        .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
        .main_mod_path = options.main_mod_path orelse options.main_pkg_path,
        .win32_manifest = options.win32_manifest,
    });
}

ObjectOptions

pub const ObjectOptions = struct {
    name: []const u8,
    root_source_file: ?LazyPath = null,
    target: CrossTarget,
    optimize: std.builtin.OptimizeMode,
    max_rss: usize = 0,
    link_libc: ?bool = null,
    single_threaded: ?bool = null,
    use_llvm: ?bool = null,
    use_lld: ?bool = null,
    zig_lib_dir: ?LazyPath = null,
    main_mod_path: ?LazyPath = null,

    main_pkg_path: ?LazyPath = null,
};

addObject()

Deprecated; use main_mod_path.

pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile {
    return Step.Compile.create(b, .{
        .name = options.name,
        .root_source_file = options.root_source_file,
        .target = options.target,
        .optimize = options.optimize,
        .kind = .obj,
        .max_rss = options.max_rss,
        .link_libc = options.link_libc,
        .single_threaded = options.single_threaded,
        .use_llvm = options.use_llvm,
        .use_lld = options.use_lld,
        .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
        .main_mod_path = options.main_mod_path orelse options.main_pkg_path,
    });
}

SharedLibraryOptions

pub const SharedLibraryOptions = struct {
    name: []const u8,
    root_source_file: ?LazyPath = null,
    version: ?std.SemanticVersion = null,
    target: CrossTarget,
    optimize: std.builtin.OptimizeMode,
    max_rss: usize = 0,
    link_libc: ?bool = null,
    single_threaded: ?bool = null,
    use_llvm: ?bool = null,
    use_lld: ?bool = null,
    zig_lib_dir: ?LazyPath = null,
    main_mod_path: ?LazyPath = null,
    win32_manifest: ?LazyPath = null,

    main_pkg_path: ?LazyPath = null,
};

addSharedLibrary()

Embed a .manifest file in the compilation if the object format supports it. https://learn.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference Manifest files must have the extension .manifest. Can be set regardless of target. The .manifest file will be ignored if the target object format does not support embedded manifests. Deprecated; use main_mod_path.

pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *Step.Compile {
    return Step.Compile.create(b, .{
        .name = options.name,
        .root_source_file = options.root_source_file,
        .kind = .lib,
        .linkage = .dynamic,
        .version = options.version,
        .target = options.target,
        .optimize = options.optimize,
        .max_rss = options.max_rss,
        .link_libc = options.link_libc,
        .single_threaded = options.single_threaded,
        .use_llvm = options.use_llvm,
        .use_lld = options.use_lld,
        .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
        .main_mod_path = options.main_mod_path orelse options.main_pkg_path,
        .win32_manifest = options.win32_manifest,
    });
}

StaticLibraryOptions

pub const StaticLibraryOptions = struct {
    name: []const u8,
    root_source_file: ?LazyPath = null,
    target: CrossTarget,
    optimize: std.builtin.OptimizeMode,
    version: ?std.SemanticVersion = null,
    max_rss: usize = 0,
    link_libc: ?bool = null,
    single_threaded: ?bool = null,
    use_llvm: ?bool = null,
    use_lld: ?bool = null,
    zig_lib_dir: ?LazyPath = null,
    main_mod_path: ?LazyPath = null,

    main_pkg_path: ?LazyPath = null,
};

addStaticLibrary()

Deprecated; use main_mod_path.

pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *Step.Compile {
    return Step.Compile.create(b, .{
        .name = options.name,
        .root_source_file = options.root_source_file,
        .kind = .lib,
        .linkage = .static,
        .version = options.version,
        .target = options.target,
        .optimize = options.optimize,
        .max_rss = options.max_rss,
        .link_libc = options.link_libc,
        .single_threaded = options.single_threaded,
        .use_llvm = options.use_llvm,
        .use_lld = options.use_lld,
        .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
        .main_mod_path = options.main_mod_path orelse options.main_pkg_path,
    });
}

TestOptions

pub const TestOptions = struct {
    name: []const u8 = "test",
    root_source_file: LazyPath,
    target: CrossTarget = .{},
    optimize: std.builtin.OptimizeMode = .Debug,
    version: ?std.SemanticVersion = null,
    max_rss: usize = 0,
    filter: ?[]const u8 = null,
    test_runner: ?[]const u8 = null,
    link_libc: ?bool = null,
    single_threaded: ?bool = null,
    use_llvm: ?bool = null,
    use_lld: ?bool = null,
    zig_lib_dir: ?LazyPath = null,
    main_mod_path: ?LazyPath = null,

    main_pkg_path: ?LazyPath = null,
};

addTest()

Deprecated; use main_mod_path.

pub fn addTest(b: *Build, options: TestOptions) *Step.Compile {
    return Step.Compile.create(b, .{
        .name = options.name,
        .kind = .@"test",
        .root_source_file = options.root_source_file,
        .target = options.target,
        .optimize = options.optimize,
        .max_rss = options.max_rss,
        .filter = options.filter,
        .test_runner = options.test_runner,
        .link_libc = options.link_libc,
        .single_threaded = options.single_threaded,
        .use_llvm = options.use_llvm,
        .use_lld = options.use_lld,
        .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
        .main_mod_path = options.main_mod_path orelse options.main_pkg_path,
    });
}

AssemblyOptions

pub const AssemblyOptions = struct {
    name: []const u8,
    source_file: LazyPath,
    target: CrossTarget,
    optimize: std.builtin.OptimizeMode,
    max_rss: usize = 0,
    zig_lib_dir: ?LazyPath = null,
};

addAssembly()

pub fn addAssembly(b: *Build, options: AssemblyOptions) *Step.Compile {
    const obj_step = Step.Compile.create(b, .{
        .name = options.name,
        .kind = .obj,
        .root_source_file = null,
        .target = options.target,
        .optimize = options.optimize,
        .max_rss = options.max_rss,
        .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
    });
    obj_step.addAssemblyFile(options.source_file);
    return obj_step;
}

addModule()

This function creates a module and adds it to the package's module set, making it available to other packages which depend on this one. createModule can be used instead to create a private module.

pub fn addModule(b: *Build, name: []const u8, options: CreateModuleOptions) *Module {
    const module = b.createModule(options);
    b.modules.put(b.dupe(name), module) catch @panic("OOM");
    return module;
}

ModuleDependency

pub const ModuleDependency = struct {
    name: []const u8,
    module: *Module,
};

CreateModuleOptions

pub const CreateModuleOptions = struct {
    source_file: LazyPath,
    dependencies: []const ModuleDependency = &.{},
};

createModule()

This function creates a private module, to be used by the current package, but not exposed to other packages depending on this one. addModule can be used instead to create a public module.

pub fn createModule(b: *Build, options: CreateModuleOptions) *Module {
    const module = b.allocator.create(Module) catch @panic("OOM");
    module.* = .{
        .builder = b,
        .source_file = options.source_file,
        .dependencies = moduleDependenciesToArrayHashMap(b.allocator, options.dependencies),
    };
    return module;
}

fn moduleDependenciesToArrayHashMap(arena: Allocator, deps: []const ModuleDependency) std.StringArrayHashMap(*Module) {
    var result = std.StringArrayHashMap(*Module).init(arena);
    for (deps) |dep| {
        result.put(dep.name, dep.module) catch @panic("OOM");
    }
    return result;
}

addSystemCommand()

Initializes a Step.Run with argv, which must at least have the path to the executable. More command line arguments can be added with addArg, addArgs, and addArtifactArg. Be careful using this function, as it introduces a system dependency. To run an executable built with zig build, see Step.Compile.run.

pub fn addSystemCommand(self: *Build, argv: []const []const u8) *Step.Run {
    assert(argv.len >= 1);
    const run_step = Step.Run.create(self, self.fmt("run {s}", .{argv[0]}));
    run_step.addArgs(argv);
    return run_step;
}

addRunArtifact()

Creates a Step.Run with an executable built with addExecutable. Add command line arguments with methods of Step.Run.

pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
    // It doesn't have to be native. We catch that if you actually try to run it.
    // Consider that this is declarative; the run step may not be run unless a user
    // option is supplied.
    const run_step = Step.Run.create(b, b.fmt("run {s}", .{exe.name}));
    run_step.addArtifactArg(exe);

    if (exe.kind == .@"test" and exe.test_server_mode) {
        run_step.enableTestRunnerMode();
    }

    if (exe.vcpkg_bin_path) |path| {
        run_step.addPathDir(path);
    }

    return run_step;
}

addConfigHeader()

Using the values provided, produces a C header file, possibly based on a template input file (e.g. config.h.in). When an input template file is provided, this function will fail the build when an option not found in the input file is provided in values, and when an option found in the input file is missing from values.

pub fn addConfigHeader(
    b: *Build,
    options: Step.ConfigHeader.Options,
    values: anytype,
) *Step.ConfigHeader {
    var options_copy = options;
    if (options_copy.first_ret_addr == null)
        options_copy.first_ret_addr = @returnAddress();

    const config_header_step = Step.ConfigHeader.create(b, options_copy);
    config_header_step.addValues(values);
    return config_header_step;
}

dupe()

Allocator.dupe without the need to handle out of memory.

pub fn dupe(self: *Build, bytes: []const u8) []u8 {
    return self.allocator.dupe(u8, bytes) catch @panic("OOM");
}

dupeStrings()

Duplicates an array of strings without the need to handle out of memory.

pub fn dupeStrings(self: *Build, strings: []const []const u8) [][]u8 {
    const array = self.allocator.alloc([]u8, strings.len) catch @panic("OOM");
    for (strings, 0..) |s, i| {
        array[i] = self.dupe(s);
    }
    return array;
}

dupePath()

Duplicates a path and converts all slashes to the OS's canonical path separator.

pub fn dupePath(self: *Build, bytes: []const u8) []u8 {
    const the_copy = self.dupe(bytes);
    for (the_copy) |*byte| {
        switch (byte.*) {
            '/', '\\' => byte.* = fs.path.sep,
            else => {},
        }
    }
    return the_copy;
}

addWriteFile()

pub fn addWriteFile(self: *Build, file_path: []const u8, data: []const u8) *Step.WriteFile {
    const write_file_step = self.addWriteFiles();
    _ = write_file_step.add(file_path, data);
    return write_file_step;
}

addWriteFiles()

pub fn addWriteFiles(b: *Build) *Step.WriteFile {
    return Step.WriteFile.create(b);
}

addRemoveDirTree()

pub fn addRemoveDirTree(self: *Build, dir_path: []const u8) *Step.RemoveDir {
    return Step.RemoveDir.create(self, dir_path);
}

addFmt()

pub fn addFmt(b: *Build, options: Step.Fmt.Options) *Step.Fmt {
    return Step.Fmt.create(b, options);
}

addTranslateC()

pub fn addTranslateC(self: *Build, options: Step.TranslateC.Options) *Step.TranslateC {
    return Step.TranslateC.create(self, options);
}

getInstallStep()

pub fn getInstallStep(self: *Build) *Step {
    return &self.install_tls.step;
}

getUninstallStep()

pub fn getUninstallStep(self: *Build) *Step {
    return &self.uninstall_tls.step;
}

fn makeUninstall(uninstall_step: *Step, prog_node: *std.Progress.Node) anyerror!void {
    _ = prog_node;
    const uninstall_tls = @fieldParentPtr(TopLevelStep, "step", uninstall_step);
    const self = @fieldParentPtr(Build, "uninstall_tls", uninstall_tls);

    for (self.installed_files.items) |installed_file| {
        const full_path = self.getInstallPath(installed_file.dir, installed_file.path);
        if (self.verbose) {
            log.info("rm {s}", .{full_path});
        }
        fs.cwd().deleteTree(full_path) catch {};
    }

    // TODO remove empty directories
}

option()

pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T {
    const name = self.dupe(name_raw);
    const description = self.dupe(description_raw);
    const type_id = comptime typeToEnum(T);
    const enum_options = if (type_id == .@"enum") blk: {
        const fields = comptime std.meta.fields(T);
        var options = ArrayList([]const u8).initCapacity(self.allocator, fields.len) catch @panic("OOM");

        inline for (fields) |field| {
            options.appendAssumeCapacity(field.name);
        }

        break :blk options.toOwnedSlice() catch @panic("OOM");
    } else null;
    const available_option = AvailableOption{
        .name = name,
        .type_id = type_id,
        .description = description,
        .enum_options = enum_options,
    };
    if ((self.available_options_map.fetchPut(name, available_option) catch @panic("OOM")) != null) {
        panic("Option '{s}' declared twice", .{name});
    }
    self.available_options_list.append(available_option) catch @panic("OOM");

    const option_ptr = self.user_input_options.getPtr(name) orelse return null;
    option_ptr.used = true;
    switch (type_id) {
        .bool => switch (option_ptr.value) {
            .flag => return true,
            .scalar => |s| {
                if (mem.eql(u8, s, "true")) {
                    return true;
                } else if (mem.eql(u8, s, "false")) {
                    return false;
                } else {
                    log.err("Expected -D{s} to be a boolean, but received '{s}'", .{ name, s });
                    self.markInvalidUserInput();
                    return null;
                }
            },
            .list, .map => {
                log.err("Expected -D{s} to be a boolean, but received a {s}.", .{
                    name, @tagName(option_ptr.value),
                });
                self.markInvalidUserInput();
                return null;
            },
        },
        .int => switch (option_ptr.value) {
            .flag, .list, .map => {
                log.err("Expected -D{s} to be an integer, but received a {s}.", .{
                    name, @tagName(option_ptr.value),
                });
                self.markInvalidUserInput();
                return null;
            },
            .scalar => |s| {
                const n = std.fmt.parseInt(T, s, 10) catch |err| switch (err) {
                    error.Overflow => {
                        log.err("-D{s} value {s} cannot fit into type {s}.", .{ name, s, @typeName(T) });
                        self.markInvalidUserInput();
                        return null;
                    },
                    else => {
                        log.err("Expected -D{s} to be an integer of type {s}.", .{ name, @typeName(T) });
                        self.markInvalidUserInput();
                        return null;
                    },
                };
                return n;
            },
        },
        .float => switch (option_ptr.value) {
            .flag, .map, .list => {
                log.err("Expected -D{s} to be a float, but received a {s}.", .{
                    name, @tagName(option_ptr.value),
                });
                self.markInvalidUserInput();
                return null;
            },
            .scalar => |s| {
                const n = std.fmt.parseFloat(T, s) catch {
                    log.err("Expected -D{s} to be a float of type {s}.", .{ name, @typeName(T) });
                    self.markInvalidUserInput();
                    return null;
                };
                return n;
            },
        },
        .@"enum" => switch (option_ptr.value) {
            .flag, .map, .list => {
                log.err("Expected -D{s} to be an enum, but received a {s}.", .{
                    name, @tagName(option_ptr.value),
                });
                self.markInvalidUserInput();
                return null;
            },
            .scalar => |s| {
                if (std.meta.stringToEnum(T, s)) |enum_lit| {
                    return enum_lit;
                } else {
                    log.err("Expected -D{s} to be of type {s}.", .{ name, @typeName(T) });
                    self.markInvalidUserInput();
                    return null;
                }
            },
        },
        .string => switch (option_ptr.value) {
            .flag, .list, .map => {
                log.err("Expected -D{s} to be a string, but received a {s}.", .{
                    name, @tagName(option_ptr.value),
                });
                self.markInvalidUserInput();
                return null;
            },
            .scalar => |s| return s,
        },
        .build_id => switch (option_ptr.value) {
            .flag, .map, .list => {
                log.err("Expected -D{s} to be an enum, but received a {s}.", .{
                    name, @tagName(option_ptr.value),
                });
                self.markInvalidUserInput();
                return null;
            },
            .scalar => |s| {
                if (Step.Compile.BuildId.parse(s)) |build_id| {
                    return build_id;
                } else |err| {
                    log.err("unable to parse option '-D{s}': {s}", .{ name, @errorName(err) });
                    self.markInvalidUserInput();
                    return null;
                }
            },
        },
        .list => switch (option_ptr.value) {
            .flag, .map => {
                log.err("Expected -D{s} to be a list, but received a {s}.", .{
                    name, @tagName(option_ptr.value),
                });
                self.markInvalidUserInput();
                return null;
            },
            .scalar => |s| {
                return self.allocator.dupe([]const u8, &[_][]const u8{s}) catch @panic("OOM");
            },
            .list => |lst| return lst.items,
        },
    }
}

step()

pub fn step(self: *Build, name: []const u8, description: []const u8) *Step {
    const step_info = self.allocator.create(TopLevelStep) catch @panic("OOM");
    step_info.* = .{
        .step = Step.init(.{
            .id = .top_level,
            .name = name,
            .owner = self,
        }),
        .description = self.dupe(description),
    };
    const gop = self.top_level_steps.getOrPut(self.allocator, name) catch @panic("OOM");
    if (gop.found_existing) std.debug.panic("A top-level step with name \"{s}\" already exists", .{name});

    gop.key_ptr.* = step_info.step.name;
    gop.value_ptr.* = step_info;

    return &step_info.step;
}

StandardOptimizeOptionOptions

pub const StandardOptimizeOptionOptions = struct {
    preferred_optimize_mode: ?std.builtin.OptimizeMode = null,
};

standardOptimizeOption()

pub fn standardOptimizeOption(self: *Build, options: StandardOptimizeOptionOptions) std.builtin.OptimizeMode {
    if (options.preferred_optimize_mode) |mode| {
        if (self.option(bool, "release", "optimize for end users") orelse false) {
            return mode;
        } else {
            return .Debug;
        }
    } else {
        return self.option(
            std.builtin.OptimizeMode,
            "optimize",
            "Prioritize performance, safety, or binary size (-O flag)",
        ) orelse .Debug;
    }
}

StandardTargetOptionsArgs

pub const StandardTargetOptionsArgs = struct {
    whitelist: ?[]const CrossTarget = null,

    default_target: CrossTarget = CrossTarget{},
};

standardTargetOptions()

Exposes standard zig build options for choosing a target.

pub fn standardTargetOptions(self: *Build, args: StandardTargetOptionsArgs) CrossTarget {
    const maybe_triple = self.option(
        []const u8,
        "target",
        "The CPU architecture, OS, and ABI to build for",
    );
    const mcpu = self.option([]const u8, "cpu", "Target CPU features to add or subtract");

    if (maybe_triple == null and mcpu == null) {
        return args.default_target;
    }

    const triple = maybe_triple orelse "native";

    var diags: CrossTarget.ParseOptions.Diagnostics = .{};
    const selected_target = CrossTarget.parse(.{
        .arch_os_abi = triple,
        .cpu_features = mcpu,
        .diagnostics = &diags,
    }) catch |err| switch (err) {
        error.UnknownCpuModel => {
            log.err("Unknown CPU: '{s}'\nAvailable CPUs for architecture '{s}':", .{
                diags.cpu_name.?,
                @tagName(diags.arch.?),
            });
            for (diags.arch.?.allCpuModels()) |cpu| {
                log.err(" {s}", .{cpu.name});
            }
            self.markInvalidUserInput();
            return args.default_target;
        },
        error.UnknownCpuFeature => {
            log.err(
                \\Unknown CPU feature: '{s}'
                \\Available CPU features for architecture '{s}':
                \\
            , .{
                diags.unknown_feature_name.?,
                @tagName(diags.arch.?),
            });
            for (diags.arch.?.allFeaturesList()) |feature| {
                log.err(" {s}: {s}", .{ feature.name, feature.description });
            }
            self.markInvalidUserInput();
            return args.default_target;
        },
        error.UnknownOperatingSystem => {
            log.err(
                \\Unknown OS: '{s}'
                \\Available operating systems:
                \\
            , .{diags.os_name.?});
            inline for (std.meta.fields(std.Target.Os.Tag)) |field| {
                log.err(" {s}", .{field.name});
            }
            self.markInvalidUserInput();
            return args.default_target;
        },
        else => |e| {
            log.err("Unable to parse target '{s}': {s}\n", .{ triple, @errorName(e) });
            self.markInvalidUserInput();
            return args.default_target;
        },
    };

    const selected_canonicalized_triple = selected_target.zigTriple(self.allocator) catch @panic("OOM");

    if (args.whitelist) |list| whitelist_check: {
        // Make sure it's a match of one of the list.
        var mismatch_triple = true;
        var mismatch_cpu_features = true;
        var whitelist_item = CrossTarget{};
        for (list) |t| {
            mismatch_cpu_features = true;
            mismatch_triple = true;

            const t_triple = t.zigTriple(self.allocator) catch @panic("OOM");
            if (mem.eql(u8, t_triple, selected_canonicalized_triple)) {
                mismatch_triple = false;
                whitelist_item = t;
                if (t.getCpuFeatures().isSuperSetOf(selected_target.getCpuFeatures())) {
                    mismatch_cpu_features = false;
                    break :whitelist_check;
                } else {
                    break;
                }
            }
        }
        if (mismatch_triple) {
            log.err("Chosen target '{s}' does not match one of the supported targets:", .{
                selected_canonicalized_triple,
            });
            for (list) |t| {
                const t_triple = t.zigTriple(self.allocator) catch @panic("OOM");
                log.err(" {s}", .{t_triple});
            }
        } else {
            assert(mismatch_cpu_features);
            const whitelist_cpu = whitelist_item.getCpu();
            const selected_cpu = selected_target.getCpu();
            log.err("Chosen CPU model '{s}' does not match one of the supported targets:", .{
                selected_cpu.model.name,
            });
            log.err("  Supported feature Set: ", .{});
            const all_features = whitelist_cpu.arch.allFeaturesList();
            var populated_cpu_features = whitelist_cpu.model.features;
            populated_cpu_features.populateDependencies(all_features);
            for (all_features, 0..) |feature, i_usize| {
                const i = @as(std.Target.Cpu.Feature.Set.Index, @intCast(i_usize));
                const in_cpu_set = populated_cpu_features.isEnabled(i);
                if (in_cpu_set) {
                    log.err("{s} ", .{feature.name});
                }
            }
            log.err("  Remove: ", .{});
            for (all_features, 0..) |feature, i_usize| {
                const i = @as(std.Target.Cpu.Feature.Set.Index, @intCast(i_usize));
                const in_cpu_set = populated_cpu_features.isEnabled(i);
                const in_actual_set = selected_cpu.features.isEnabled(i);
                if (in_actual_set and !in_cpu_set) {
                    log.err("{s} ", .{feature.name});
                }
            }
        }
        self.markInvalidUserInput();
        return args.default_target;
    }

    return selected_target;
}

addUserInputOption()

pub fn addUserInputOption(self: *Build, name_raw: []const u8, value_raw: []const u8) !bool {
    const name = self.dupe(name_raw);
    const value = self.dupe(value_raw);
    const gop = try self.user_input_options.getOrPut(name);
    if (!gop.found_existing) {
        gop.value_ptr.* = UserInputOption{
            .name = name,
            .value = .{ .scalar = value },
            .used = false,
        };
        return false;
    }

    // option already exists
    switch (gop.value_ptr.value) {
        .scalar => |s| {
            // turn it into a list
            var list = ArrayList([]const u8).init(self.allocator);
            try list.append(s);
            try list.append(value);
            try self.user_input_options.put(name, .{
                .name = name,
                .value = .{ .list = list },
                .used = false,
            });
        },
        .list => |*list| {
            // append to the list
            try list.append(value);
            try self.user_input_options.put(name, .{
                .name = name,
                .value = .{ .list = list.* },
                .used = false,
            });
        },
        .flag => {
            log.warn("Option '-D{s}={s}' conflicts with flag '-D{s}'.", .{ name, value, name });
            return true;
        },
        .map => |*map| {
            _ = map;
            log.warn("TODO maps as command line arguments is not implemented yet.", .{});
            return true;
        },
    }
    return false;
}

addUserInputFlag()

pub fn addUserInputFlag(self: *Build, name_raw: []const u8) !bool {
    const name = self.dupe(name_raw);
    const gop = try self.user_input_options.getOrPut(name);
    if (!gop.found_existing) {
        gop.value_ptr.* = .{
            .name = name,
            .value = .{ .flag = {} },
            .used = false,
        };
        return false;
    }

    // option already exists
    switch (gop.value_ptr.value) {
        .scalar => |s| {
            log.err("Flag '-D{s}' conflicts with option '-D{s}={s}'.", .{ name, name, s });
            return true;
        },
        .list, .map => {
            log.err("Flag '-D{s}' conflicts with multiple options of the same name.", .{name});
            return true;
        },
        .flag => {},
    }
    return false;
}

fn typeToEnum(comptime T: type) TypeId {
    return switch (T) {
        Step.Compile.BuildId => .build_id,
        else => return switch (@typeInfo(T)) {
            .Int => .int,
            .Float => .float,
            .Bool => .bool,
            .Enum => .@"enum",
            else => switch (T) {
                []const u8 => .string,
                []const []const u8 => .list,
                else => @compileError("Unsupported type: " ++ @typeName(T)),
            },
        },
    };
}

fn markInvalidUserInput(self: *Build) void {
    self.invalid_user_input = true;
}

validateUserInputDidItFail()

pub fn validateUserInputDidItFail(self: *Build) bool {
    // make sure all args are used
    var it = self.user_input_options.iterator();
    while (it.next()) |entry| {
        if (!entry.value_ptr.used) {
            log.err("Invalid option: -D{s}", .{entry.key_ptr.*});
            self.markInvalidUserInput();
        }
    }

    return self.invalid_user_input;
}

fn allocPrintCmd(ally: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) ![]u8 {
    var buf = ArrayList(u8).init(ally);
    if (opt_cwd) |cwd| try buf.writer().print("cd {s} && ", .{cwd});
    for (argv) |arg| {
        try buf.writer().print("{s} ", .{arg});
    }
    return buf.toOwnedSlice();
}

fn printCmd(ally: Allocator, cwd: ?[]const u8, argv: []const []const u8) void {
    const text = allocPrintCmd(ally, cwd, argv) catch @panic("OOM");
    std.debug.print("{s}\n", .{text});
}

installArtifact()

This creates the install step and adds it to the dependencies of the top-level install step, using all the default options. See addInstallArtifact for a more flexible function.

pub fn installArtifact(self: *Build, artifact: *Step.Compile) void {
    self.getInstallStep().dependOn(&self.addInstallArtifact(artifact, .{}).step);
}

addInstallArtifact()

This merely creates the step; it does not add it to the dependencies of the top-level install step.

pub fn addInstallArtifact(
    self: *Build,
    artifact: *Step.Compile,
    options: Step.InstallArtifact.Options,
) *Step.InstallArtifact {
    return Step.InstallArtifact.create(self, artifact, options);
}

installFile()

dest_rel_path is relative to prefix path

pub fn installFile(self: *Build, src_path: []const u8, dest_rel_path: []const u8) void {
    self.getInstallStep().dependOn(&self.addInstallFileWithDir(.{ .path = src_path }, .prefix, dest_rel_path).step);
}

installDirectory()

pub fn installDirectory(self: *Build, options: InstallDirectoryOptions) void {
    self.getInstallStep().dependOn(&self.addInstallDirectory(options).step);
}

installBinFile()

dest_rel_path is relative to bin path

pub fn installBinFile(self: *Build, src_path: []const u8, dest_rel_path: []const u8) void {
    self.getInstallStep().dependOn(&self.addInstallFileWithDir(.{ .path = src_path }, .bin, dest_rel_path).step);
}

installLibFile()

dest_rel_path is relative to lib path

pub fn installLibFile(self: *Build, src_path: []const u8, dest_rel_path: []const u8) void {
    self.getInstallStep().dependOn(&self.addInstallFileWithDir(.{ .path = src_path }, .lib, dest_rel_path).step);
}

addObjCopy()

pub fn addObjCopy(b: *Build, source: LazyPath, options: Step.ObjCopy.Options) *Step.ObjCopy {
    return Step.ObjCopy.create(b, source, options);
}

addInstallFile()

dest_rel_path is relative to install prefix path

pub fn addInstallFile(self: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
    return self.addInstallFileWithDir(source.dupe(self), .prefix, dest_rel_path);
}

addInstallBinFile()

dest_rel_path is relative to bin path

pub fn addInstallBinFile(self: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
    return self.addInstallFileWithDir(source.dupe(self), .bin, dest_rel_path);
}

addInstallLibFile()

dest_rel_path is relative to lib path

pub fn addInstallLibFile(self: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
    return self.addInstallFileWithDir(source.dupe(self), .lib, dest_rel_path);
}

addInstallHeaderFile()

pub fn addInstallHeaderFile(b: *Build, src_path: []const u8, dest_rel_path: []const u8) *Step.InstallFile {
    return b.addInstallFileWithDir(.{ .path = src_path }, .header, dest_rel_path);
}

addInstallFileWithDir()

pub fn addInstallFileWithDir(
    self: *Build,
    source: LazyPath,
    install_dir: InstallDir,
    dest_rel_path: []const u8,
) *Step.InstallFile {
    return Step.InstallFile.create(self, source.dupe(self), install_dir, dest_rel_path);
}

addInstallDirectory()

pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *Step.InstallDir {
    return Step.InstallDir.create(self, options);
}

addCheckFile()

pub fn addCheckFile(
    b: *Build,
    file_source: LazyPath,
    options: Step.CheckFile.Options,
) *Step.CheckFile {
    return Step.CheckFile.create(b, file_source, options);
}

pushInstalledFile()

deprecated: https://github.com/ziglang/zig/issues/14943

pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u8) void {
    const file = InstalledFile{
        .dir = dir,
        .path = dest_rel_path,
    };
    self.installed_files.append(file.dupe(self)) catch @panic("OOM");
}

truncateFile()

pub fn truncateFile(self: *Build, dest_path: []const u8) !void {
    if (self.verbose) {
        log.info("truncate {s}", .{dest_path});
    }
    const cwd = fs.cwd();
    var src_file = cwd.createFile(dest_path, .{}) catch |err| switch (err) {
        error.FileNotFound => blk: {
            if (fs.path.dirname(dest_path)) |dirname| {
                try cwd.makePath(dirname);
            }
            break :blk try cwd.createFile(dest_path, .{});
        },
        else => |e| return e,
    };
    src_file.close();
}

pathFromRoot()

pub fn pathFromRoot(b: *Build, p: []const u8) []u8 {
    return fs.path.resolve(b.allocator, &.{ b.build_root.path orelse ".", p }) catch @panic("OOM");
}

fn pathFromCwd(b: *Build, p: []const u8) []u8 {
    const cwd = process.getCwdAlloc(b.allocator) catch @panic("OOM");
    return fs.path.resolve(b.allocator, &.{ cwd, p }) catch @panic("OOM");
}

pathJoin()

pub fn pathJoin(self: *Build, paths: []const []const u8) []u8 {
    return fs.path.join(self.allocator, paths) catch @panic("OOM");
}

fmt()

pub fn fmt(self: *Build, comptime format: []const u8, args: anytype) []u8 {
    return fmt_lib.allocPrint(self.allocator, format, args) catch @panic("OOM");
}

findProgram()

pub fn findProgram(self: *Build, names: []const []const u8, paths: []const []const u8) ![]const u8 {
    // TODO report error for ambiguous situations
    const exe_extension = @as(CrossTarget, .{}).exeFileExt();
    for (self.search_prefixes.items) |search_prefix| {
        for (names) |name| {
            if (fs.path.isAbsolute(name)) {
                return name;
            }
            const full_path = self.pathJoin(&.{
                search_prefix,
                "bin",
                self.fmt("{s}{s}", .{ name, exe_extension }),
            });
            return fs.realpathAlloc(self.allocator, full_path) catch continue;
        }
    }
    if (self.env_map.get("PATH")) |PATH| {
        for (names) |name| {
            if (fs.path.isAbsolute(name)) {
                return name;
            }
            var it = mem.tokenizeScalar(u8, PATH, fs.path.delimiter);
            while (it.next()) |path| {
                const full_path = self.pathJoin(&.{
                    path,
                    self.fmt("{s}{s}", .{ name, exe_extension }),
                });
                return fs.realpathAlloc(self.allocator, full_path) catch continue;
            }
        }
    }
    for (names) |name| {
        if (fs.path.isAbsolute(name)) {
            return name;
        }
        for (paths) |path| {
            const full_path = self.pathJoin(&.{
                path,
                self.fmt("{s}{s}", .{ name, exe_extension }),
            });
            return fs.realpathAlloc(self.allocator, full_path) catch continue;
        }
    }
    return error.FileNotFound;
}

runAllowFail()

pub fn runAllowFail(
    self: *Build,
    argv: []const []const u8,
    out_code: *u8,
    stderr_behavior: std.ChildProcess.StdIo,
) RunError![]u8 {
    assert(argv.len != 0);

    if (!process.can_spawn)
        return error.ExecNotSupported;

    const max_output_size = 400 * 1024;
    var child = std.ChildProcess.init(argv, self.allocator);
    child.stdin_behavior = .Ignore;
    child.stdout_behavior = .Pipe;
    child.stderr_behavior = stderr_behavior;
    child.env_map = self.env_map;

    try child.spawn();

    const stdout = child.stdout.?.reader().readAllAlloc(self.allocator, max_output_size) catch {
        return error.ReadFailure;
    };
    errdefer self.allocator.free(stdout);

    const term = try child.wait();
    switch (term) {
        .Exited => |code| {
            if (code != 0) {
                out_code.* = @as(u8, @truncate(code));
                return error.ExitCodeFailure;
            }
            return stdout;
        },
        .Signal, .Stopped, .Unknown => |code| {
            out_code.* = @as(u8, @truncate(code));
            return error.ProcessTerminated;
        },
    }
}

run()

This is a helper function to be called from build.zig scripts, *not* from inside step make() functions. If any errors occur, it fails the build with a helpful message.

pub fn run(b: *Build, argv: []const []const u8) []u8 {
    if (!process.can_spawn) {
        std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{
            try allocPrintCmd(b.allocator, null, argv),
        });
        process.exit(1);
    }

    var code: u8 = undefined;
    return b.runAllowFail(argv, &code, .Inherit) catch |err| {
        const printed_cmd = allocPrintCmd(b.allocator, null, argv) catch @panic("OOM");
        std.debug.print("unable to spawn the following command: {s}\n{s}\n", .{
            @errorName(err), printed_cmd,
        });
        process.exit(1);
    };
}

addSearchPrefix()

pub fn addSearchPrefix(self: *Build, search_prefix: []const u8) void {
    self.search_prefixes.append(self.dupePath(search_prefix)) catch @panic("OOM");
}

getInstallPath()

pub fn getInstallPath(self: *Build, dir: InstallDir, dest_rel_path: []const u8) []const u8 {
    assert(!fs.path.isAbsolute(dest_rel_path)); // Install paths must be relative to the prefix
    const base_dir = switch (dir) {
        .prefix => self.install_path,
        .bin => self.exe_dir,
        .lib => self.lib_dir,
        .header => self.h_dir,
        .custom => |path| self.pathJoin(&.{ self.install_path, path }),
    };
    return fs.path.resolve(
        self.allocator,
        &[_][]const u8{ base_dir, dest_rel_path },
    ) catch @panic("OOM");
}

Dependency

pub const Dependency = struct {
    builder: *Build,

artifact()

    pub fn artifact(d: *Dependency, name: []const u8) *Step.Compile {
        var found: ?*Step.Compile = null;
        for (d.builder.install_tls.step.dependencies.items) |dep_step| {
            const inst = dep_step.cast(Step.InstallArtifact) orelse continue;
            if (mem.eql(u8, inst.artifact.name, name)) {
                if (found != null) panic("artifact name '{s}' is ambiguous", .{name});
                found = inst.artifact;
            }
        }
        return found orelse {
            for (d.builder.install_tls.step.dependencies.items) |dep_step| {
                const inst = dep_step.cast(Step.InstallArtifact) orelse continue;
                log.info("available artifact: '{s}'", .{inst.artifact.name});
            }
            panic("unable to find artifact '{s}'", .{name});
        };
    }

module()

    pub fn module(d: *Dependency, name: []const u8) *Module {
        return d.builder.modules.get(name) orelse {
            panic("unable to find module '{s}'", .{name});
        };
    }

path()

    pub fn path(d: *Dependency, sub_path: []const u8) LazyPath {
        return .{
            .dependency = .{
                .dependency = d,
                .sub_path = sub_path,
            },
        };
    }
};

dependency()

pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {
    const build_runner = @import("root");
    const deps = build_runner.dependencies;

    const pkg_hash = for (b.available_deps) |dep| {
        if (mem.eql(u8, dep[0], name)) break dep[1];
    } else {
        const full_path = b.pathFromRoot("build.zig.zon");
        std.debug.print("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file.\n", .{ name, full_path });
        process.exit(1);
    };

    inline for (@typeInfo(deps.packages).Struct.decls) |decl| {
        if (mem.eql(u8, decl.name, pkg_hash)) {
            const pkg = @field(deps.packages, decl.name);
            return dependencyInner(b, name, pkg.build_root, if (@hasDecl(pkg, "build_zig")) pkg.build_zig else null, pkg.deps, args);
        }
    }

    unreachable; // Bad @dependencies source
}

anonymousDependency()

pub fn anonymousDependency(
    b: *Build,
    relative_build_root: []const u8,
    comptime build_zig: type,
    args: anytype,
) *Dependency {
    const arena = b.allocator;
    const build_root = b.build_root.join(arena, &.{relative_build_root}) catch @panic("OOM");
    const name = arena.dupe(u8, relative_build_root) catch @panic("OOM");
    for (name) |*byte| switch (byte.*) {
        '/', '\\' => byte.* = '.',
        else => continue,
    };
    return dependencyInner(b, name, build_root, build_zig, &.{}, args);
}

fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool {
    switch (lhs) {
        .flag => {},
        .scalar => |lhs_scalar| {
            const rhs_scalar = switch (rhs) {
                .scalar => |scalar| scalar,
                else => return false,
            };

            if (!std.mem.eql(u8, lhs_scalar, rhs_scalar))
                return false;
        },
        .list => |lhs_list| {
            const rhs_list = switch (rhs) {
                .list => |list| list,
                else => return false,
            };

            if (lhs_list.items.len != rhs_list.items.len)
                return false;

            for (lhs_list.items, rhs_list.items) |lhs_list_entry, rhs_list_entry| {
                if (!std.mem.eql(u8, lhs_list_entry, rhs_list_entry))
                    return false;
            }
        },
        .map => |lhs_map| {
            const rhs_map = switch (rhs) {
                .map => |map| map,
                else => return false,
            };

            if (lhs_map.count() != rhs_map.count())
                return false;

            var lhs_it = lhs_map.iterator();
            while (lhs_it.next()) |lhs_entry| {
                const rhs_value = rhs_map.get(lhs_entry.key_ptr.*) orelse return false;
                if (!userValuesAreSame(lhs_entry.value_ptr.*.*, rhs_value.*))
                    return false;
            }
        },
    }

    return true;
}

dependencyInner()

The path to the directory containing the dependency's build.zig file, relative to the current package's build.zig. A direct @import of the build.zig of the dependency.

pub fn dependencyInner(
    b: *Build,
    name: []const u8,
    build_root_string: []const u8,
    comptime build_zig: ?type,
    pkg_deps: AvailableDeps,
    args: anytype,
) *Dependency {
    const user_input_options = userInputOptionsFromArgs(b.allocator, args);
    if (b.initialized_deps.get(.{
        .build_root_string = build_root_string,
        .user_input_options = user_input_options,
    })) |dep|
        return dep;

    const build_root: std.Build.Cache.Directory = .{
        .path = build_root_string,
        .handle = std.fs.cwd().openDir(build_root_string, .{}) catch |err| {
            std.debug.print("unable to open '{s}': {s}\n", .{
                build_root_string, @errorName(err),
            });
            process.exit(1);
        },
    };

    const sub_builder = b.createChild(name, build_root, pkg_deps, user_input_options) catch @panic("unhandled error");
    if (build_zig) |bz| {
        sub_builder.runBuild(bz) catch @panic("unhandled error");

        if (sub_builder.validateUserInputDidItFail()) {
            std.debug.dumpCurrentStackTrace(@returnAddress());
        }
    }

    const dep = b.allocator.create(Dependency) catch @panic("OOM");
    dep.* = .{ .builder = sub_builder };

    b.initialized_deps.put(.{
        .build_root_string = build_root_string,
        .user_input_options = user_input_options,
    }, dep) catch @panic("OOM");
    return dep;
}

runBuild()

pub fn runBuild(b: *Build, build_zig: anytype) anyerror!void {
    switch (@typeInfo(@typeInfo(@TypeOf(build_zig.build)).Fn.return_type.?)) {
        .Void => build_zig.build(b),
        .ErrorUnion => try build_zig.build(b),
        else => @compileError("expected return type of build to be 'void' or '!void'"),
    }
}

Module

pub const Module = struct {
    builder: *Build,
    source_file: LazyPath,
    dependencies: std.StringArrayHashMap(*Module),
};

GeneratedFile

This could either be a generated file, in which case the module contains exactly one file, or it could be a path to the root source file of directory of files which constitute the module. A file that is generated by a build step. This struct is an interface that is meant to be used with @fieldParentPtr to implement the actual path logic.

pub const GeneratedFile = struct {
    step: *Step,

    path: ?[]const u8 = null,

getPath()

The step that generates the file The path to the generated file. Must be either absolute or relative to the build root. This value must be set in the fn make() of the step and must not be null afterwards.

    pub fn getPath(self: GeneratedFile) []const u8 {
        return self.path orelse std.debug.panic(
            "getPath() was called on a GeneratedFile that wasn't built yet. Is there a missing Step dependency on step '{s}'?",
            .{self.step.name},
        );
    }
};

LazyPath

A reference to an existing or future path.

pub const LazyPath = union(enum) {
    path: []const u8,

    generated: *const GeneratedFile,

    cwd_relative: []const u8,

    dependency: struct {
        dependency: *Dependency,
        sub_path: []const u8,
    },

relative()

A source file path relative to build root. This should not be an absolute path, but in an older iteration of the zig build system API, it was allowed to be absolute. Absolute paths should use cwd_relative. A file that is generated by an interface. Those files usually are not available until built by a build step. An absolute path or a path relative to the current working directory of the build runner process. This is uncommon but used for system environment paths such as --zig-lib-dir which ignore the file system path of build.zig and instead are relative to the directory from which zig build was invoked. Use of this tag indicates a dependency on the host system. Returns a new file source that will have a relative path to the build root guaranteed. Asserts the parameter is not an absolute path.

    pub fn relative(path: []const u8) LazyPath {
        std.debug.assert(!std.fs.path.isAbsolute(path));
        return LazyPath{ .path = path };
    }

getDisplayName()

Returns a string that can be shown to represent the file source. Either returns the path or "generated".

    pub fn getDisplayName(self: LazyPath) []const u8 {
        return switch (self) {
            .path, .cwd_relative => self.path,
            .generated => "generated",
            .dependency => "dependency",
        };
    }

addStepDependencies()

Adds dependencies this file source implies to the given step.

    pub fn addStepDependencies(self: LazyPath, other_step: *Step) void {
        switch (self) {
            .path, .cwd_relative, .dependency => {},
            .generated => |gen| other_step.dependOn(gen.step),
        }
    }

getPath()

Returns an absolute path. Intended to be used during the make phase only.

    pub fn getPath(self: LazyPath, src_builder: *Build) []const u8 {
        return getPath2(self, src_builder, null);
    }

getPath2()

Returns an absolute path. Intended to be used during the make phase only.

asking_step is only used for debugging purposes; it's the step being run that is asking for the path.

    pub fn getPath2(self: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 {
        switch (self) {
            .path => |p| return src_builder.pathFromRoot(p),
            .cwd_relative => |p| return src_builder.pathFromCwd(p),
            .generated => |gen| return gen.path orelse {
                std.debug.getStderrMutex().lock();
                const stderr = std.io.getStdErr();
                dumpBadGetPathHelp(gen.step, stderr, src_builder, asking_step) catch {};
                @panic("misconfigured build script");
            },
            .dependency => |dep| {
                return dep.dependency.builder.pathJoin(&[_][]const u8{
                    dep.dependency.builder.build_root.path.?,
                    dep.sub_path,
                });
            },
        }
    }

dupe()

Duplicates the file source for a given builder.

    pub fn dupe(self: LazyPath, b: *Build) LazyPath {
        return switch (self) {
            .path => |p| .{ .path = b.dupePath(p) },
            .cwd_relative => |p| .{ .cwd_relative = b.dupePath(p) },
            .generated => |gen| .{ .generated = gen },
            .dependency => |dep| .{ .dependency = dep },
        };
    }
};

dumpBadGetPathHelp()

In this function the stderr mutex has already been locked.

pub fn dumpBadGetPathHelp(
    s: *Step,
    stderr: fs.File,
    src_builder: *Build,
    asking_step: ?*Step,
) anyerror!void {
    const w = stderr.writer();
    try w.print(
        \\getPath() was called on a GeneratedFile that wasn't built yet.
        \\  source package path: {s}
        \\  Is there a missing Step dependency on step '{s}'?
        \\
    , .{
        src_builder.build_root.path orelse ".",
        s.name,
    });

    const tty_config = std.io.tty.detectConfig(stderr);
    tty_config.setColor(w, .red) catch {};
    try stderr.writeAll("    The step was created by this stack trace:\n");
    tty_config.setColor(w, .reset) catch {};

    s.dump(stderr);
    if (asking_step) |as| {
        tty_config.setColor(w, .red) catch {};
        try stderr.writer().print("    The step '{s}' that is missing a dependency on the above step was created by this stack trace:\n", .{as.name});
        tty_config.setColor(w, .reset) catch {};

        as.dump(stderr);
    }
    tty_config.setColor(w, .red) catch {};
    try stderr.writeAll("    Hope that helps. Proceeding to panic.\n");
    tty_config.setColor(w, .reset) catch {};
}

constructCMacro()

Allocates a new string for assigning a value to a named macro. If the value is omitted, it is set to 1. name and value need not live longer than the function call.

pub fn constructCMacro(allocator: Allocator, name: []const u8, value: ?[]const u8) []const u8 {
    var macro = allocator.alloc(
        u8,
        name.len + if (value) |value_slice| value_slice.len + 1 else 0,
    ) catch |err| if (err == error.OutOfMemory) @panic("Out of memory") else unreachable;
    @memcpy(macro[0..name.len], name);
    if (value) |value_slice| {
        macro[name.len] = '=';
        @memcpy(macro[name.len + 1 ..][0..value_slice.len], value_slice);
    }
    return macro;
}

VcpkgRoot

pub const VcpkgRoot = union(VcpkgRootStatus) {
    unattempted: void,
    not_found: void,
    found: []const u8,
};

VcpkgRootStatus

pub const VcpkgRootStatus = enum {
    unattempted,
    not_found,
    found,
};

InstallDir

pub const InstallDir = union(enum) {
    prefix: void,
    lib: void,
    bin: void,
    header: void,
    custom: []const u8,

dupe()

A path relative to the prefix Duplicates the install directory including the path if set to custom.

    pub fn dupe(self: InstallDir, builder: *Build) InstallDir {
        if (self == .custom) {
            return .{ .custom = builder.dupe(self.custom) };
        } else {
            return self;
        }
    }
};

InstalledFile

pub const InstalledFile = struct {
    dir: InstallDir,
    path: []const u8,

dupe()

Duplicates the installed file path and directory.

    pub fn dupe(self: InstalledFile, builder: *Build) InstalledFile {
        return .{
            .dir = self.dir.dupe(builder),
            .path = builder.dupe(self.path),
        };
    }
};

serializeCpu()

pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 {
    // TODO this logic can disappear if cpu model + features becomes part of the target triple
    const all_features = cpu.arch.allFeaturesList();
    var populated_cpu_features = cpu.model.features;
    populated_cpu_features.populateDependencies(all_features);

    if (populated_cpu_features.eql(cpu.features)) {
        // The CPU name alone is sufficient.
        return cpu.model.name;
    } else {
        var mcpu_buffer = ArrayList(u8).init(allocator);
        try mcpu_buffer.appendSlice(cpu.model.name);

        for (all_features, 0..) |feature, i_usize| {
            const i = @as(std.Target.Cpu.Feature.Set.Index, @intCast(i_usize));
            const in_cpu_set = populated_cpu_features.isEnabled(i);
            const in_actual_set = cpu.features.isEnabled(i);
            if (in_cpu_set and !in_actual_set) {
                try mcpu_buffer.writer().print("-{s}", .{feature.name});
            } else if (!in_cpu_set and in_actual_set) {
                try mcpu_buffer.writer().print("+{s}", .{feature.name});
            }
        }

        return try mcpu_buffer.toOwnedSlice();
    }
}

makeTempPath()

This function is intended to be called in the configure phase only. It returns an absolute directory path, which is potentially going to be a source of API breakage in the future, so keep that in mind when using this function.

pub fn makeTempPath(b: *Build) []const u8 {
    const rand_int = std.crypto.random.int(u64);
    const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ hex64(rand_int);
    const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM");
    b.cache_root.handle.makePath(tmp_dir_sub_path) catch |err| {
        std.debug.print("unable to make tmp path '{s}': {s}\n", .{
            result_path, @errorName(err),
        });
    };
    return result_path;
}

hex64()

There are a few copies of this function in miscellaneous places. Would be nice to find a home for them.

pub fn hex64(x: u64) [16]u8 {
    const hex_charset = "0123456789abcdef";
    var result: [16]u8 = undefined;
    var i: usize = 0;
    while (i < 8) : (i += 1) {
        const byte: u8 = @truncate(x >> @as(u6, @intCast(8 * i)));
        result[i * 2 + 0] = hex_charset[byte >> 4];
        result[i * 2 + 1] = hex_charset[byte & 15];
    }
    return result;
}

test {
    _ = Cache;
    _ = Step;
}