master
   1const builtin = @import("builtin");
   2
   3const std = @import("std.zig");
   4const Io = std.Io;
   5const fs = std.fs;
   6const mem = std.mem;
   7const debug = std.debug;
   8const panic = std.debug.panic;
   9const assert = debug.assert;
  10const log = std.log;
  11const StringHashMap = std.StringHashMap;
  12const Allocator = mem.Allocator;
  13const Target = std.Target;
  14const process = std.process;
  15const EnvMap = std.process.EnvMap;
  16const File = fs.File;
  17const Sha256 = std.crypto.hash.sha2.Sha256;
  18const Build = @This();
  19const ArrayList = std.ArrayList;
  20
  21pub const Cache = @import("Build/Cache.zig");
  22pub const Step = @import("Build/Step.zig");
  23pub const Module = @import("Build/Module.zig");
  24pub const Watch = @import("Build/Watch.zig");
  25pub const Fuzz = @import("Build/Fuzz.zig");
  26pub const WebServer = @import("Build/WebServer.zig");
  27pub const abi = @import("Build/abi.zig");
  28
  29/// Shared state among all Build instances.
  30graph: *Graph,
  31install_tls: TopLevelStep,
  32uninstall_tls: TopLevelStep,
  33allocator: Allocator,
  34user_input_options: UserInputOptionsMap,
  35available_options_map: AvailableOptionsMap,
  36available_options_list: std.array_list.Managed(AvailableOption),
  37verbose: bool,
  38verbose_link: bool,
  39verbose_cc: bool,
  40verbose_air: bool,
  41verbose_llvm_ir: ?[]const u8,
  42verbose_llvm_bc: ?[]const u8,
  43verbose_cimport: bool,
  44verbose_llvm_cpu_features: bool,
  45reference_trace: ?u32 = null,
  46invalid_user_input: bool,
  47default_step: *Step,
  48top_level_steps: std.StringArrayHashMapUnmanaged(*TopLevelStep),
  49install_prefix: []const u8,
  50dest_dir: ?[]const u8,
  51lib_dir: []const u8,
  52exe_dir: []const u8,
  53h_dir: []const u8,
  54install_path: []const u8,
  55sysroot: ?[]const u8 = null,
  56search_prefixes: ArrayList([]const u8),
  57libc_file: ?[]const u8 = null,
  58/// Path to the directory containing build.zig.
  59build_root: Cache.Directory,
  60cache_root: Cache.Directory,
  61pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
  62args: ?[]const []const u8 = null,
  63debug_log_scopes: []const []const u8 = &.{},
  64debug_compile_errors: bool = false,
  65debug_incremental: bool = false,
  66debug_pkg_config: bool = false,
  67/// Number of stack frames captured when a `StackTrace` is recorded for debug purposes,
  68/// in particular at `Step` creation.
  69/// Set to 0 to disable stack collection.
  70debug_stack_frames_count: u8 = 8,
  71
  72/// Experimental. Use system Darling installation to run cross compiled macOS build artifacts.
  73enable_darling: bool = false,
  74/// Use system QEMU installation to run cross compiled foreign architecture build artifacts.
  75enable_qemu: bool = false,
  76/// Darwin. Use Rosetta to run x86_64 macOS build artifacts on arm64 macOS.
  77enable_rosetta: bool = false,
  78/// Use system Wasmtime installation to run cross compiled wasm/wasi build artifacts.
  79enable_wasmtime: bool = false,
  80/// Use system Wine installation to run cross compiled Windows build artifacts.
  81enable_wine: bool = false,
  82/// After following the steps in https://github.com/ziglang/zig/wiki/Updating-libc#glibc,
  83/// this will be the directory $glibc-build-dir/install/glibcs
  84/// Given the example of the aarch64 target, this is the directory
  85/// that contains the path `aarch64-linux-gnu/lib/ld-linux-aarch64.so.1`.
  86/// Also works for dynamic musl.
  87libc_runtimes_dir: ?[]const u8 = null,
  88
  89dep_prefix: []const u8 = "",
  90
  91modules: std.StringArrayHashMap(*Module),
  92
  93named_writefiles: std.StringArrayHashMap(*Step.WriteFile),
  94named_lazy_paths: std.StringArrayHashMap(LazyPath),
  95/// The hash of this instance's package. `""` means that this is the root package.
  96pkg_hash: []const u8,
  97/// A mapping from dependency names to package hashes.
  98available_deps: AvailableDeps,
  99
 100release_mode: ReleaseMode,
 101
 102build_id: ?std.zig.BuildId = null,
 103
 104pub const ReleaseMode = enum {
 105    off,
 106    any,
 107    fast,
 108    safe,
 109    small,
 110};
 111
 112/// Shared state among all Build instances.
 113/// Settings that are here rather than in Build are not configurable per-package.
 114pub const Graph = struct {
 115    io: Io,
 116    arena: Allocator,
 117    system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .empty,
 118    system_package_mode: bool = false,
 119    debug_compiler_runtime_libs: bool = false,
 120    cache: Cache,
 121    zig_exe: [:0]const u8,
 122    env_map: EnvMap,
 123    global_cache_root: Cache.Directory,
 124    zig_lib_directory: Cache.Directory,
 125    needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .empty,
 126    /// Information about the native target. Computed before build() is invoked.
 127    host: ResolvedTarget,
 128    incremental: ?bool = null,
 129    random_seed: u32 = 0,
 130    dependency_cache: InitializedDepMap = .empty,
 131    allow_so_scripts: ?bool = null,
 132    time_report: bool,
 133};
 134
 135const AvailableDeps = []const struct { []const u8, []const u8 };
 136
 137const SystemLibraryMode = enum {
 138    /// User asked for the library to be disabled.
 139    /// The build runner has not confirmed whether the setting is recognized yet.
 140    user_disabled,
 141    /// User asked for the library to be enabled.
 142    /// The build runner has not confirmed whether the setting is recognized yet.
 143    user_enabled,
 144    /// The build runner has confirmed that this setting is recognized.
 145    /// System integration with this library has been resolved to off.
 146    declared_disabled,
 147    /// The build runner has confirmed that this setting is recognized.
 148    /// System integration with this library has been resolved to on.
 149    declared_enabled,
 150};
 151
 152const InitializedDepMap = std.HashMapUnmanaged(InitializedDepKey, *Dependency, InitializedDepContext, std.hash_map.default_max_load_percentage);
 153const InitializedDepKey = struct {
 154    build_root_string: []const u8,
 155    user_input_options: UserInputOptionsMap,
 156};
 157
 158const InitializedDepContext = struct {
 159    allocator: Allocator,
 160
 161    pub fn hash(ctx: @This(), k: InitializedDepKey) u64 {
 162        var hasher = std.hash.Wyhash.init(0);
 163        hasher.update(k.build_root_string);
 164        hashUserInputOptionsMap(ctx.allocator, k.user_input_options, &hasher);
 165        return hasher.final();
 166    }
 167
 168    pub fn eql(_: @This(), lhs: InitializedDepKey, rhs: InitializedDepKey) bool {
 169        if (!std.mem.eql(u8, lhs.build_root_string, rhs.build_root_string))
 170            return false;
 171
 172        if (lhs.user_input_options.count() != rhs.user_input_options.count())
 173            return false;
 174
 175        var it = lhs.user_input_options.iterator();
 176        while (it.next()) |lhs_entry| {
 177            const rhs_value = rhs.user_input_options.get(lhs_entry.key_ptr.*) orelse return false;
 178            if (!userValuesAreSame(lhs_entry.value_ptr.*.value, rhs_value.value))
 179                return false;
 180        }
 181
 182        return true;
 183    }
 184};
 185
 186pub const RunError = error{
 187    ReadFailure,
 188    ExitCodeFailure,
 189    ProcessTerminated,
 190    ExecNotSupported,
 191} || std.process.Child.SpawnError;
 192
 193pub const PkgConfigError = error{
 194    PkgConfigCrashed,
 195    PkgConfigFailed,
 196    PkgConfigNotInstalled,
 197    PkgConfigInvalidOutput,
 198};
 199
 200pub const PkgConfigPkg = struct {
 201    name: []const u8,
 202    desc: []const u8,
 203};
 204
 205const UserInputOptionsMap = StringHashMap(UserInputOption);
 206const AvailableOptionsMap = StringHashMap(AvailableOption);
 207
 208const AvailableOption = struct {
 209    name: []const u8,
 210    type_id: TypeId,
 211    description: []const u8,
 212    /// If the `type_id` is `enum` or `enum_list` this provides the list of enum options
 213    enum_options: ?[]const []const u8,
 214};
 215
 216const UserInputOption = struct {
 217    name: []const u8,
 218    value: UserValue,
 219    used: bool,
 220};
 221
 222const UserValue = union(enum) {
 223    flag: void,
 224    scalar: []const u8,
 225    list: std.array_list.Managed([]const u8),
 226    map: StringHashMap(*const UserValue),
 227    lazy_path: LazyPath,
 228    lazy_path_list: std.array_list.Managed(LazyPath),
 229};
 230
 231const TypeId = enum {
 232    bool,
 233    int,
 234    float,
 235    @"enum",
 236    enum_list,
 237    string,
 238    list,
 239    build_id,
 240    lazy_path,
 241    lazy_path_list,
 242};
 243
 244const TopLevelStep = struct {
 245    pub const base_id: Step.Id = .top_level;
 246
 247    step: Step,
 248    description: []const u8,
 249};
 250
 251pub const DirList = struct {
 252    lib_dir: ?[]const u8 = null,
 253    exe_dir: ?[]const u8 = null,
 254    include_dir: ?[]const u8 = null,
 255};
 256
 257pub fn create(
 258    graph: *Graph,
 259    build_root: Cache.Directory,
 260    cache_root: Cache.Directory,
 261    available_deps: AvailableDeps,
 262) error{OutOfMemory}!*Build {
 263    const arena = graph.arena;
 264
 265    const b = try arena.create(Build);
 266    b.* = .{
 267        .graph = graph,
 268        .build_root = build_root,
 269        .cache_root = cache_root,
 270        .verbose = false,
 271        .verbose_link = false,
 272        .verbose_cc = false,
 273        .verbose_air = false,
 274        .verbose_llvm_ir = null,
 275        .verbose_llvm_bc = null,
 276        .verbose_cimport = false,
 277        .verbose_llvm_cpu_features = false,
 278        .invalid_user_input = false,
 279        .allocator = arena,
 280        .user_input_options = UserInputOptionsMap.init(arena),
 281        .available_options_map = AvailableOptionsMap.init(arena),
 282        .available_options_list = std.array_list.Managed(AvailableOption).init(arena),
 283        .top_level_steps = .{},
 284        .default_step = undefined,
 285        .search_prefixes = .empty,
 286        .install_prefix = undefined,
 287        .lib_dir = undefined,
 288        .exe_dir = undefined,
 289        .h_dir = undefined,
 290        .dest_dir = graph.env_map.get("DESTDIR"),
 291        .install_tls = .{
 292            .step = .init(.{
 293                .id = TopLevelStep.base_id,
 294                .name = "install",
 295                .owner = b,
 296            }),
 297            .description = "Copy build artifacts to prefix path",
 298        },
 299        .uninstall_tls = .{
 300            .step = .init(.{
 301                .id = TopLevelStep.base_id,
 302                .name = "uninstall",
 303                .owner = b,
 304                .makeFn = makeUninstall,
 305            }),
 306            .description = "Remove build artifacts from prefix path",
 307        },
 308        .install_path = undefined,
 309        .args = null,
 310        .modules = .init(arena),
 311        .named_writefiles = .init(arena),
 312        .named_lazy_paths = .init(arena),
 313        .pkg_hash = "",
 314        .available_deps = available_deps,
 315        .release_mode = .off,
 316    };
 317    try b.top_level_steps.put(arena, b.install_tls.step.name, &b.install_tls);
 318    try b.top_level_steps.put(arena, b.uninstall_tls.step.name, &b.uninstall_tls);
 319    b.default_step = &b.install_tls.step;
 320    return b;
 321}
 322
 323fn createChild(
 324    parent: *Build,
 325    dep_name: []const u8,
 326    build_root: Cache.Directory,
 327    pkg_hash: []const u8,
 328    pkg_deps: AvailableDeps,
 329    user_input_options: UserInputOptionsMap,
 330) error{OutOfMemory}!*Build {
 331    const child = try createChildOnly(parent, dep_name, build_root, pkg_hash, pkg_deps, user_input_options);
 332    try determineAndApplyInstallPrefix(child);
 333    return child;
 334}
 335
 336fn createChildOnly(
 337    parent: *Build,
 338    dep_name: []const u8,
 339    build_root: Cache.Directory,
 340    pkg_hash: []const u8,
 341    pkg_deps: AvailableDeps,
 342    user_input_options: UserInputOptionsMap,
 343) error{OutOfMemory}!*Build {
 344    const allocator = parent.allocator;
 345    const child = try allocator.create(Build);
 346    child.* = .{
 347        .graph = parent.graph,
 348        .allocator = allocator,
 349        .install_tls = .{
 350            .step = .init(.{
 351                .id = TopLevelStep.base_id,
 352                .name = "install",
 353                .owner = child,
 354            }),
 355            .description = "Copy build artifacts to prefix path",
 356        },
 357        .uninstall_tls = .{
 358            .step = .init(.{
 359                .id = TopLevelStep.base_id,
 360                .name = "uninstall",
 361                .owner = child,
 362                .makeFn = makeUninstall,
 363            }),
 364            .description = "Remove build artifacts from prefix path",
 365        },
 366        .user_input_options = user_input_options,
 367        .available_options_map = AvailableOptionsMap.init(allocator),
 368        .available_options_list = std.array_list.Managed(AvailableOption).init(allocator),
 369        .verbose = parent.verbose,
 370        .verbose_link = parent.verbose_link,
 371        .verbose_cc = parent.verbose_cc,
 372        .verbose_air = parent.verbose_air,
 373        .verbose_llvm_ir = parent.verbose_llvm_ir,
 374        .verbose_llvm_bc = parent.verbose_llvm_bc,
 375        .verbose_cimport = parent.verbose_cimport,
 376        .verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features,
 377        .reference_trace = parent.reference_trace,
 378        .invalid_user_input = false,
 379        .default_step = undefined,
 380        .top_level_steps = .{},
 381        .install_prefix = undefined,
 382        .dest_dir = parent.dest_dir,
 383        .lib_dir = parent.lib_dir,
 384        .exe_dir = parent.exe_dir,
 385        .h_dir = parent.h_dir,
 386        .install_path = parent.install_path,
 387        .sysroot = parent.sysroot,
 388        .search_prefixes = parent.search_prefixes,
 389        .libc_file = parent.libc_file,
 390        .build_root = build_root,
 391        .cache_root = parent.cache_root,
 392        .debug_log_scopes = parent.debug_log_scopes,
 393        .debug_compile_errors = parent.debug_compile_errors,
 394        .debug_incremental = parent.debug_incremental,
 395        .debug_pkg_config = parent.debug_pkg_config,
 396        .enable_darling = parent.enable_darling,
 397        .enable_qemu = parent.enable_qemu,
 398        .enable_rosetta = parent.enable_rosetta,
 399        .enable_wasmtime = parent.enable_wasmtime,
 400        .enable_wine = parent.enable_wine,
 401        .libc_runtimes_dir = parent.libc_runtimes_dir,
 402        .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }),
 403        .modules = .init(allocator),
 404        .named_writefiles = .init(allocator),
 405        .named_lazy_paths = .init(allocator),
 406        .pkg_hash = pkg_hash,
 407        .available_deps = pkg_deps,
 408        .release_mode = parent.release_mode,
 409    };
 410    try child.top_level_steps.put(allocator, child.install_tls.step.name, &child.install_tls);
 411    try child.top_level_steps.put(allocator, child.uninstall_tls.step.name, &child.uninstall_tls);
 412    child.default_step = &child.install_tls.step;
 413    return child;
 414}
 415
 416fn userInputOptionsFromArgs(arena: Allocator, args: anytype) UserInputOptionsMap {
 417    var map = UserInputOptionsMap.init(arena);
 418    inline for (@typeInfo(@TypeOf(args)).@"struct".fields) |field| {
 419        if (field.type == @TypeOf(null)) continue;
 420        addUserInputOptionFromArg(arena, &map, field, field.type, @field(args, field.name));
 421    }
 422    return map;
 423}
 424
 425fn addUserInputOptionFromArg(
 426    arena: Allocator,
 427    map: *UserInputOptionsMap,
 428    field: std.builtin.Type.StructField,
 429    comptime T: type,
 430    /// If null, the value won't be added, but `T` will still be type-checked.
 431    maybe_value: ?T,
 432) void {
 433    switch (T) {
 434        Target.Query => return if (maybe_value) |v| {
 435            map.put(field.name, .{
 436                .name = field.name,
 437                .value = .{ .scalar = v.zigTriple(arena) catch @panic("OOM") },
 438                .used = false,
 439            }) catch @panic("OOM");
 440            map.put("cpu", .{
 441                .name = "cpu",
 442                .value = .{ .scalar = v.serializeCpuAlloc(arena) catch @panic("OOM") },
 443                .used = false,
 444            }) catch @panic("OOM");
 445        },
 446        ResolvedTarget => return if (maybe_value) |v| {
 447            map.put(field.name, .{
 448                .name = field.name,
 449                .value = .{ .scalar = v.query.zigTriple(arena) catch @panic("OOM") },
 450                .used = false,
 451            }) catch @panic("OOM");
 452            map.put("cpu", .{
 453                .name = "cpu",
 454                .value = .{ .scalar = v.query.serializeCpuAlloc(arena) catch @panic("OOM") },
 455                .used = false,
 456            }) catch @panic("OOM");
 457        },
 458        std.zig.BuildId => return if (maybe_value) |v| {
 459            map.put(field.name, .{
 460                .name = field.name,
 461                .value = .{ .scalar = std.fmt.allocPrint(arena, "{f}", .{v}) catch @panic("OOM") },
 462                .used = false,
 463            }) catch @panic("OOM");
 464        },
 465        LazyPath => return if (maybe_value) |v| {
 466            map.put(field.name, .{
 467                .name = field.name,
 468                .value = .{ .lazy_path = v.dupeInner(arena) },
 469                .used = false,
 470            }) catch @panic("OOM");
 471        },
 472        []const LazyPath => return if (maybe_value) |v| {
 473            var list = std.array_list.Managed(LazyPath).initCapacity(arena, v.len) catch @panic("OOM");
 474            for (v) |lp| list.appendAssumeCapacity(lp.dupeInner(arena));
 475            map.put(field.name, .{
 476                .name = field.name,
 477                .value = .{ .lazy_path_list = list },
 478                .used = false,
 479            }) catch @panic("OOM");
 480        },
 481        []const u8 => return if (maybe_value) |v| {
 482            map.put(field.name, .{
 483                .name = field.name,
 484                .value = .{ .scalar = arena.dupe(u8, v) catch @panic("OOM") },
 485                .used = false,
 486            }) catch @panic("OOM");
 487        },
 488        []const []const u8 => return if (maybe_value) |v| {
 489            var list = std.array_list.Managed([]const u8).initCapacity(arena, v.len) catch @panic("OOM");
 490            for (v) |s| list.appendAssumeCapacity(arena.dupe(u8, s) catch @panic("OOM"));
 491            map.put(field.name, .{
 492                .name = field.name,
 493                .value = .{ .list = list },
 494                .used = false,
 495            }) catch @panic("OOM");
 496        },
 497        else => switch (@typeInfo(T)) {
 498            .bool => return if (maybe_value) |v| {
 499                map.put(field.name, .{
 500                    .name = field.name,
 501                    .value = .{ .scalar = if (v) "true" else "false" },
 502                    .used = false,
 503                }) catch @panic("OOM");
 504            },
 505            .@"enum", .enum_literal => return if (maybe_value) |v| {
 506                map.put(field.name, .{
 507                    .name = field.name,
 508                    .value = .{ .scalar = @tagName(v) },
 509                    .used = false,
 510                }) catch @panic("OOM");
 511            },
 512            .comptime_int, .int => return if (maybe_value) |v| {
 513                map.put(field.name, .{
 514                    .name = field.name,
 515                    .value = .{ .scalar = std.fmt.allocPrint(arena, "{d}", .{v}) catch @panic("OOM") },
 516                    .used = false,
 517                }) catch @panic("OOM");
 518            },
 519            .comptime_float, .float => return if (maybe_value) |v| {
 520                map.put(field.name, .{
 521                    .name = field.name,
 522                    .value = .{ .scalar = std.fmt.allocPrint(arena, "{x}", .{v}) catch @panic("OOM") },
 523                    .used = false,
 524                }) catch @panic("OOM");
 525            },
 526            .pointer => |ptr_info| switch (ptr_info.size) {
 527                .one => switch (@typeInfo(ptr_info.child)) {
 528                    .array => |array_info| {
 529                        addUserInputOptionFromArg(
 530                            arena,
 531                            map,
 532                            field,
 533                            @Pointer(.slice, .{ .@"const" = true }, array_info.child, null),
 534                            maybe_value orelse null,
 535                        );
 536                        return;
 537                    },
 538                    else => {},
 539                },
 540                .slice => switch (@typeInfo(ptr_info.child)) {
 541                    .@"enum" => return if (maybe_value) |v| {
 542                        var list = std.array_list.Managed([]const u8).initCapacity(arena, v.len) catch @panic("OOM");
 543                        for (v) |tag| list.appendAssumeCapacity(@tagName(tag));
 544                        map.put(field.name, .{
 545                            .name = field.name,
 546                            .value = .{ .list = list },
 547                            .used = false,
 548                        }) catch @panic("OOM");
 549                    },
 550                    else => {
 551                        addUserInputOptionFromArg(
 552                            arena,
 553                            map,
 554                            field,
 555                            @Pointer(ptr_info.size, .{ .@"const" = true }, ptr_info.child, null),
 556                            maybe_value orelse null,
 557                        );
 558                        return;
 559                    },
 560                },
 561                else => {},
 562            },
 563            .null => unreachable,
 564            .optional => |info| switch (@typeInfo(info.child)) {
 565                .optional => {},
 566                else => {
 567                    addUserInputOptionFromArg(
 568                        arena,
 569                        map,
 570                        field,
 571                        info.child,
 572                        maybe_value orelse null,
 573                    );
 574                    return;
 575                },
 576            },
 577            else => {},
 578        },
 579    }
 580    @compileError("option '" ++ field.name ++ "' has unsupported type: " ++ @typeName(field.type));
 581}
 582
 583const OrderedUserValue = union(enum) {
 584    flag: void,
 585    scalar: []const u8,
 586    list: std.array_list.Managed([]const u8),
 587    map: std.array_list.Managed(Pair),
 588    lazy_path: LazyPath,
 589    lazy_path_list: std.array_list.Managed(LazyPath),
 590
 591    const Pair = struct {
 592        name: []const u8,
 593        value: OrderedUserValue,
 594        fn lessThan(_: void, lhs: Pair, rhs: Pair) bool {
 595            return std.ascii.lessThanIgnoreCase(lhs.name, rhs.name);
 596        }
 597    };
 598
 599    fn hash(val: OrderedUserValue, hasher: *std.hash.Wyhash) void {
 600        hasher.update(&std.mem.toBytes(std.meta.activeTag(val)));
 601        switch (val) {
 602            .flag => {},
 603            .scalar => |scalar| hasher.update(scalar),
 604            // lists are already ordered
 605            .list => |list| for (list.items) |list_entry|
 606                hasher.update(list_entry),
 607            .map => |map| for (map.items) |map_entry| {
 608                hasher.update(map_entry.name);
 609                map_entry.value.hash(hasher);
 610            },
 611            .lazy_path => |lp| hashLazyPath(lp, hasher),
 612            .lazy_path_list => |lp_list| for (lp_list.items) |lp| {
 613                hashLazyPath(lp, hasher);
 614            },
 615        }
 616    }
 617
 618    fn hashLazyPath(lp: LazyPath, hasher: *std.hash.Wyhash) void {
 619        switch (lp) {
 620            .src_path => |sp| {
 621                hasher.update(sp.owner.pkg_hash);
 622                hasher.update(sp.sub_path);
 623            },
 624            .generated => |gen| {
 625                hasher.update(gen.file.step.owner.pkg_hash);
 626                hasher.update(std.mem.asBytes(&gen.up));
 627                hasher.update(gen.sub_path);
 628            },
 629            .cwd_relative => |rel_path| {
 630                hasher.update(rel_path);
 631            },
 632            .dependency => |dep| {
 633                hasher.update(dep.dependency.builder.pkg_hash);
 634                hasher.update(dep.sub_path);
 635            },
 636        }
 637    }
 638
 639    fn mapFromUnordered(allocator: Allocator, unordered: std.StringHashMap(*const UserValue)) std.array_list.Managed(Pair) {
 640        var ordered = std.array_list.Managed(Pair).init(allocator);
 641        var it = unordered.iterator();
 642        while (it.next()) |entry| {
 643            ordered.append(.{
 644                .name = entry.key_ptr.*,
 645                .value = OrderedUserValue.fromUnordered(allocator, entry.value_ptr.*.*),
 646            }) catch @panic("OOM");
 647        }
 648
 649        std.mem.sortUnstable(Pair, ordered.items, {}, Pair.lessThan);
 650        return ordered;
 651    }
 652
 653    fn fromUnordered(allocator: Allocator, unordered: UserValue) OrderedUserValue {
 654        return switch (unordered) {
 655            .flag => .{ .flag = {} },
 656            .scalar => |scalar| .{ .scalar = scalar },
 657            .list => |list| .{ .list = list },
 658            .map => |map| .{ .map = OrderedUserValue.mapFromUnordered(allocator, map) },
 659            .lazy_path => |lp| .{ .lazy_path = lp },
 660            .lazy_path_list => |list| .{ .lazy_path_list = list },
 661        };
 662    }
 663};
 664
 665const OrderedUserInputOption = struct {
 666    name: []const u8,
 667    value: OrderedUserValue,
 668    used: bool,
 669
 670    fn hash(opt: OrderedUserInputOption, hasher: *std.hash.Wyhash) void {
 671        hasher.update(opt.name);
 672        opt.value.hash(hasher);
 673    }
 674
 675    fn fromUnordered(allocator: Allocator, user_input_option: UserInputOption) OrderedUserInputOption {
 676        return OrderedUserInputOption{
 677            .name = user_input_option.name,
 678            .used = user_input_option.used,
 679            .value = OrderedUserValue.fromUnordered(allocator, user_input_option.value),
 680        };
 681    }
 682
 683    fn lessThan(_: void, lhs: OrderedUserInputOption, rhs: OrderedUserInputOption) bool {
 684        return std.ascii.lessThanIgnoreCase(lhs.name, rhs.name);
 685    }
 686};
 687
 688// The hash should be consistent with the same values given a different order.
 689// This function takes a user input map, orders it, then hashes the contents.
 690fn hashUserInputOptionsMap(allocator: Allocator, user_input_options: UserInputOptionsMap, hasher: *std.hash.Wyhash) void {
 691    var ordered = std.array_list.Managed(OrderedUserInputOption).init(allocator);
 692    var it = user_input_options.iterator();
 693    while (it.next()) |entry|
 694        ordered.append(OrderedUserInputOption.fromUnordered(allocator, entry.value_ptr.*)) catch @panic("OOM");
 695
 696    std.mem.sortUnstable(OrderedUserInputOption, ordered.items, {}, OrderedUserInputOption.lessThan);
 697
 698    // juice it
 699    for (ordered.items) |user_option|
 700        user_option.hash(hasher);
 701}
 702
 703fn determineAndApplyInstallPrefix(b: *Build) error{OutOfMemory}!void {
 704    // Create an installation directory local to this package. This will be used when
 705    // dependant packages require a standard prefix, such as include directories for C headers.
 706    var hash = b.graph.cache.hash;
 707    // Random bytes to make unique. Refresh this with new random bytes when
 708    // implementation is modified in a non-backwards-compatible way.
 709    hash.add(@as(u32, 0xd8cb0055));
 710    hash.addBytes(b.dep_prefix);
 711
 712    var wyhash = std.hash.Wyhash.init(0);
 713    hashUserInputOptionsMap(b.allocator, b.user_input_options, &wyhash);
 714    hash.add(wyhash.final());
 715
 716    const digest = hash.final();
 717    const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &digest });
 718    b.resolveInstallPrefix(install_prefix, .{});
 719}
 720
 721/// This function is intended to be called by lib/build_runner.zig, not a build.zig file.
 722pub fn resolveInstallPrefix(b: *Build, install_prefix: ?[]const u8, dir_list: DirList) void {
 723    if (b.dest_dir) |dest_dir| {
 724        b.install_prefix = install_prefix orelse "/usr";
 725        b.install_path = b.pathJoin(&.{ dest_dir, b.install_prefix });
 726    } else {
 727        b.install_prefix = install_prefix orelse
 728            (b.build_root.join(b.allocator, &.{"zig-out"}) catch @panic("unhandled error"));
 729        b.install_path = b.install_prefix;
 730    }
 731
 732    var lib_list = [_][]const u8{ b.install_path, "lib" };
 733    var exe_list = [_][]const u8{ b.install_path, "bin" };
 734    var h_list = [_][]const u8{ b.install_path, "include" };
 735
 736    if (dir_list.lib_dir) |dir| {
 737        if (fs.path.isAbsolute(dir)) lib_list[0] = b.dest_dir orelse "";
 738        lib_list[1] = dir;
 739    }
 740
 741    if (dir_list.exe_dir) |dir| {
 742        if (fs.path.isAbsolute(dir)) exe_list[0] = b.dest_dir orelse "";
 743        exe_list[1] = dir;
 744    }
 745
 746    if (dir_list.include_dir) |dir| {
 747        if (fs.path.isAbsolute(dir)) h_list[0] = b.dest_dir orelse "";
 748        h_list[1] = dir;
 749    }
 750
 751    b.lib_dir = b.pathJoin(&lib_list);
 752    b.exe_dir = b.pathJoin(&exe_list);
 753    b.h_dir = b.pathJoin(&h_list);
 754}
 755
 756/// Create a set of key-value pairs that can be converted into a Zig source
 757/// file and then inserted into a Zig compilation's module table for importing.
 758/// In other words, this provides a way to expose build.zig values to Zig
 759/// source code with `@import`.
 760/// Related: `Module.addOptions`.
 761pub fn addOptions(b: *Build) *Step.Options {
 762    return Step.Options.create(b);
 763}
 764
 765pub const ExecutableOptions = struct {
 766    name: []const u8,
 767    root_module: *Module,
 768    version: ?std.SemanticVersion = null,
 769    linkage: ?std.builtin.LinkMode = null,
 770    max_rss: usize = 0,
 771    use_llvm: ?bool = null,
 772    use_lld: ?bool = null,
 773    zig_lib_dir: ?LazyPath = null,
 774    /// Embed a `.manifest` file in the compilation if the object format supports it.
 775    /// https://learn.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference
 776    /// Manifest files must have the extension `.manifest`.
 777    /// Can be set regardless of target. The `.manifest` file will be ignored
 778    /// if the target object format does not support embedded manifests.
 779    win32_manifest: ?LazyPath = null,
 780};
 781
 782pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile {
 783    return .create(b, .{
 784        .name = options.name,
 785        .root_module = options.root_module,
 786        .version = options.version,
 787        .kind = .exe,
 788        .linkage = options.linkage,
 789        .max_rss = options.max_rss,
 790        .use_llvm = options.use_llvm,
 791        .use_lld = options.use_lld,
 792        .zig_lib_dir = options.zig_lib_dir,
 793        .win32_manifest = options.win32_manifest,
 794    });
 795}
 796
 797pub const ObjectOptions = struct {
 798    name: []const u8,
 799    root_module: *Module,
 800    max_rss: usize = 0,
 801    use_llvm: ?bool = null,
 802    use_lld: ?bool = null,
 803    zig_lib_dir: ?LazyPath = null,
 804};
 805
 806pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile {
 807    return .create(b, .{
 808        .name = options.name,
 809        .root_module = options.root_module,
 810        .kind = .obj,
 811        .max_rss = options.max_rss,
 812        .use_llvm = options.use_llvm,
 813        .use_lld = options.use_lld,
 814        .zig_lib_dir = options.zig_lib_dir,
 815    });
 816}
 817
 818pub const LibraryOptions = struct {
 819    linkage: std.builtin.LinkMode = .static,
 820    name: []const u8,
 821    root_module: *Module,
 822    version: ?std.SemanticVersion = null,
 823    max_rss: usize = 0,
 824    use_llvm: ?bool = null,
 825    use_lld: ?bool = null,
 826    zig_lib_dir: ?LazyPath = null,
 827    /// Embed a `.manifest` file in the compilation if the object format supports it.
 828    /// https://learn.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference
 829    /// Manifest files must have the extension `.manifest`.
 830    /// Can be set regardless of target. The `.manifest` file will be ignored
 831    /// if the target object format does not support embedded manifests.
 832    win32_manifest: ?LazyPath = null,
 833};
 834
 835pub fn addLibrary(b: *Build, options: LibraryOptions) *Step.Compile {
 836    return .create(b, .{
 837        .name = options.name,
 838        .root_module = options.root_module,
 839        .kind = .lib,
 840        .linkage = options.linkage,
 841        .version = options.version,
 842        .max_rss = options.max_rss,
 843        .use_llvm = options.use_llvm,
 844        .use_lld = options.use_lld,
 845        .zig_lib_dir = options.zig_lib_dir,
 846        .win32_manifest = options.win32_manifest,
 847    });
 848}
 849
 850pub const TestOptions = struct {
 851    name: []const u8 = "test",
 852    root_module: *Module,
 853    max_rss: usize = 0,
 854    filters: []const []const u8 = &.{},
 855    test_runner: ?Step.Compile.TestRunner = null,
 856    use_llvm: ?bool = null,
 857    use_lld: ?bool = null,
 858    zig_lib_dir: ?LazyPath = null,
 859    /// Emits an object file instead of a test binary.
 860    /// The object must be linked separately.
 861    /// Usually used in conjunction with a custom `test_runner`.
 862    emit_object: bool = false,
 863};
 864
 865/// Creates an executable containing unit tests.
 866///
 867/// Equivalent to running the command `zig test --test-no-exec ...`.
 868///
 869/// **This step does not run the unit tests**. Typically, the result of this
 870/// function will be passed to `addRunArtifact`, creating a `Step.Run`. These
 871/// two steps are separated because they are independently configured and
 872/// cached.
 873pub fn addTest(b: *Build, options: TestOptions) *Step.Compile {
 874    return .create(b, .{
 875        .name = options.name,
 876        .kind = if (options.emit_object) .test_obj else .@"test",
 877        .root_module = options.root_module,
 878        .max_rss = options.max_rss,
 879        .filters = b.dupeStrings(options.filters),
 880        .test_runner = options.test_runner,
 881        .use_llvm = options.use_llvm,
 882        .use_lld = options.use_lld,
 883        .zig_lib_dir = options.zig_lib_dir,
 884    });
 885}
 886
 887pub const AssemblyOptions = struct {
 888    name: []const u8,
 889    source_file: LazyPath,
 890    /// To choose the same computer as the one building the package, pass the
 891    /// `host` field of the package's `Build` instance.
 892    target: ResolvedTarget,
 893    optimize: std.builtin.OptimizeMode,
 894    max_rss: usize = 0,
 895    zig_lib_dir: ?LazyPath = null,
 896};
 897
 898/// This function creates a module and adds it to the package's module set, making
 899/// it available to other packages which depend on this one.
 900/// `createModule` can be used instead to create a private module.
 901pub fn addModule(b: *Build, name: []const u8, options: Module.CreateOptions) *Module {
 902    const module = Module.create(b, options);
 903    b.modules.put(b.dupe(name), module) catch @panic("OOM");
 904    return module;
 905}
 906
 907/// This function creates a private module, to be used by the current package,
 908/// but not exposed to other packages depending on this one.
 909/// `addModule` can be used instead to create a public module.
 910pub fn createModule(b: *Build, options: Module.CreateOptions) *Module {
 911    return Module.create(b, options);
 912}
 913
 914/// Initializes a `Step.Run` with argv, which must at least have the path to the
 915/// executable. More command line arguments can be added with `addArg`,
 916/// `addArgs`, and `addArtifactArg`.
 917/// Be careful using this function, as it introduces a system dependency.
 918/// To run an executable built with zig build, see `Step.Compile.run`.
 919pub fn addSystemCommand(b: *Build, argv: []const []const u8) *Step.Run {
 920    assert(argv.len >= 1);
 921    const run_step = Step.Run.create(b, b.fmt("run {s}", .{argv[0]}));
 922    run_step.addArgs(argv);
 923    return run_step;
 924}
 925
 926/// Creates a `Step.Run` with an executable built with `addExecutable`.
 927/// Add command line arguments with methods of `Step.Run`.
 928pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
 929    // It doesn't have to be native. We catch that if you actually try to run it.
 930    // Consider that this is declarative; the run step may not be run unless a user
 931    // option is supplied.
 932
 933    // Avoid the common case of the step name looking like "run test test".
 934    const step_name = if (exe.kind.isTest() and mem.eql(u8, exe.name, "test"))
 935        b.fmt("run {s}", .{@tagName(exe.kind)})
 936    else
 937        b.fmt("run {s} {s}", .{ @tagName(exe.kind), exe.name });
 938
 939    const run_step = Step.Run.create(b, step_name);
 940    run_step.producer = exe;
 941    if (exe.kind == .@"test") {
 942        if (exe.exec_cmd_args) |exec_cmd_args| {
 943            for (exec_cmd_args) |cmd_arg| {
 944                if (cmd_arg) |arg| {
 945                    run_step.addArg(arg);
 946                } else {
 947                    run_step.addArtifactArg(exe);
 948                }
 949            }
 950        } else {
 951            run_step.addArtifactArg(exe);
 952        }
 953
 954        const test_server_mode: bool = s: {
 955            if (exe.test_runner) |r| break :s r.mode == .server;
 956            if (exe.use_llvm == false) {
 957                // The default test runner does not use the server protocol if the selected backend
 958                // is too immature to support it. Keep this logic in sync with `need_simple` in the
 959                // default test runner implementation.
 960                switch (exe.rootModuleTarget().cpu.arch) {
 961                    // stage2_aarch64
 962                    .aarch64,
 963                    .aarch64_be,
 964                    // stage2_powerpc
 965                    .powerpc,
 966                    .powerpcle,
 967                    .powerpc64,
 968                    .powerpc64le,
 969                    // stage2_riscv64
 970                    .riscv64,
 971                    => break :s false,
 972
 973                    else => {},
 974                }
 975            }
 976            break :s true;
 977        };
 978        if (test_server_mode) {
 979            run_step.enableTestRunnerMode();
 980        } else if (exe.test_runner == null) {
 981            // If a test runner does not use the `std.zig.Server` protocol, it can instead
 982            // communicate failure via its exit code.
 983            run_step.expectExitCode(0);
 984        }
 985    } else {
 986        run_step.addArtifactArg(exe);
 987    }
 988
 989    return run_step;
 990}
 991
 992/// Using the `values` provided, produces a C header file, possibly based on a
 993/// template input file (e.g. config.h.in).
 994/// When an input template file is provided, this function will fail the build
 995/// when an option not found in the input file is provided in `values`, and
 996/// when an option found in the input file is missing from `values`.
 997pub fn addConfigHeader(
 998    b: *Build,
 999    options: Step.ConfigHeader.Options,
1000    values: anytype,
1001) *Step.ConfigHeader {
1002    var options_copy = options;
1003    if (options_copy.first_ret_addr == null)
1004        options_copy.first_ret_addr = @returnAddress();
1005
1006    const config_header_step = Step.ConfigHeader.create(b, options_copy);
1007    config_header_step.addValues(values);
1008    return config_header_step;
1009}
1010
1011/// Allocator.dupe without the need to handle out of memory.
1012pub fn dupe(b: *Build, bytes: []const u8) []u8 {
1013    return dupeInner(b.allocator, bytes);
1014}
1015
1016pub fn dupeInner(allocator: std.mem.Allocator, bytes: []const u8) []u8 {
1017    return allocator.dupe(u8, bytes) catch @panic("OOM");
1018}
1019
1020/// Duplicates an array of strings without the need to handle out of memory.
1021pub fn dupeStrings(b: *Build, strings: []const []const u8) [][]u8 {
1022    const array = b.allocator.alloc([]u8, strings.len) catch @panic("OOM");
1023    for (array, strings) |*dest, source| dest.* = b.dupe(source);
1024    return array;
1025}
1026
1027/// Duplicates a path and converts all slashes to the OS's canonical path separator.
1028pub fn dupePath(b: *Build, bytes: []const u8) []u8 {
1029    return dupePathInner(b.allocator, bytes);
1030}
1031
1032fn dupePathInner(allocator: std.mem.Allocator, bytes: []const u8) []u8 {
1033    const the_copy = dupeInner(allocator, bytes);
1034    for (the_copy) |*byte| {
1035        switch (byte.*) {
1036            '/', '\\' => byte.* = fs.path.sep,
1037            else => {},
1038        }
1039    }
1040    return the_copy;
1041}
1042
1043pub fn addWriteFile(b: *Build, file_path: []const u8, data: []const u8) *Step.WriteFile {
1044    const write_file_step = b.addWriteFiles();
1045    _ = write_file_step.add(file_path, data);
1046    return write_file_step;
1047}
1048
1049pub fn addNamedWriteFiles(b: *Build, name: []const u8) *Step.WriteFile {
1050    const wf = Step.WriteFile.create(b);
1051    b.named_writefiles.put(b.dupe(name), wf) catch @panic("OOM");
1052    return wf;
1053}
1054
1055pub fn addNamedLazyPath(b: *Build, name: []const u8, lp: LazyPath) void {
1056    b.named_lazy_paths.put(b.dupe(name), lp.dupe(b)) catch @panic("OOM");
1057}
1058
1059pub fn addWriteFiles(b: *Build) *Step.WriteFile {
1060    return Step.WriteFile.create(b);
1061}
1062
1063pub fn addUpdateSourceFiles(b: *Build) *Step.UpdateSourceFiles {
1064    return Step.UpdateSourceFiles.create(b);
1065}
1066
1067pub fn addRemoveDirTree(b: *Build, dir_path: LazyPath) *Step.RemoveDir {
1068    return Step.RemoveDir.create(b, dir_path);
1069}
1070
1071pub fn addFail(b: *Build, error_msg: []const u8) *Step.Fail {
1072    return Step.Fail.create(b, error_msg);
1073}
1074
1075pub fn addFmt(b: *Build, options: Step.Fmt.Options) *Step.Fmt {
1076    return Step.Fmt.create(b, options);
1077}
1078
1079pub fn addTranslateC(b: *Build, options: Step.TranslateC.Options) *Step.TranslateC {
1080    return Step.TranslateC.create(b, options);
1081}
1082
1083pub fn getInstallStep(b: *Build) *Step {
1084    return &b.install_tls.step;
1085}
1086
1087pub fn getUninstallStep(b: *Build) *Step {
1088    return &b.uninstall_tls.step;
1089}
1090
1091fn makeUninstall(uninstall_step: *Step, options: Step.MakeOptions) anyerror!void {
1092    _ = options;
1093    const uninstall_tls: *TopLevelStep = @fieldParentPtr("step", uninstall_step);
1094    const b: *Build = @fieldParentPtr("uninstall_tls", uninstall_tls);
1095
1096    _ = b;
1097    @panic("TODO implement https://github.com/ziglang/zig/issues/14943");
1098}
1099
1100/// Creates a configuration option to be passed to the build.zig script.
1101/// When a user directly runs `zig build`, they can set these options with `-D` arguments.
1102/// When a project depends on a Zig package as a dependency, it programmatically sets
1103/// these options when calling the dependency's build.zig script as a function.
1104/// `null` is returned when an option is left to default.
1105pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T {
1106    const name = b.dupe(name_raw);
1107    const description = b.dupe(description_raw);
1108    const type_id = comptime typeToEnum(T);
1109    const enum_options = if (type_id == .@"enum" or type_id == .enum_list) blk: {
1110        const EnumType = if (type_id == .enum_list) @typeInfo(T).pointer.child else T;
1111        const fields = comptime std.meta.fields(EnumType);
1112        var options = std.array_list.Managed([]const u8).initCapacity(b.allocator, fields.len) catch @panic("OOM");
1113
1114        inline for (fields) |field| {
1115            options.appendAssumeCapacity(field.name);
1116        }
1117
1118        break :blk options.toOwnedSlice() catch @panic("OOM");
1119    } else null;
1120    const available_option = AvailableOption{
1121        .name = name,
1122        .type_id = type_id,
1123        .description = description,
1124        .enum_options = enum_options,
1125    };
1126    if ((b.available_options_map.fetchPut(name, available_option) catch @panic("OOM")) != null) {
1127        panic("Option '{s}' declared twice", .{name});
1128    }
1129    b.available_options_list.append(available_option) catch @panic("OOM");
1130
1131    const option_ptr = b.user_input_options.getPtr(name) orelse return null;
1132    option_ptr.used = true;
1133    switch (type_id) {
1134        .bool => switch (option_ptr.value) {
1135            .flag => return true,
1136            .scalar => |s| {
1137                if (mem.eql(u8, s, "true")) {
1138                    return true;
1139                } else if (mem.eql(u8, s, "false")) {
1140                    return false;
1141                } else {
1142                    log.err("Expected -D{s} to be a boolean, but received '{s}'", .{ name, s });
1143                    b.markInvalidUserInput();
1144                    return null;
1145                }
1146            },
1147            .list, .map, .lazy_path, .lazy_path_list => {
1148                log.err("Expected -D{s} to be a boolean, but received a {s}.", .{
1149                    name, @tagName(option_ptr.value),
1150                });
1151                b.markInvalidUserInput();
1152                return null;
1153            },
1154        },
1155        .int => switch (option_ptr.value) {
1156            .flag, .list, .map, .lazy_path, .lazy_path_list => {
1157                log.err("Expected -D{s} to be an integer, but received a {s}.", .{
1158                    name, @tagName(option_ptr.value),
1159                });
1160                b.markInvalidUserInput();
1161                return null;
1162            },
1163            .scalar => |s| {
1164                const n = std.fmt.parseInt(T, s, 10) catch |err| switch (err) {
1165                    error.Overflow => {
1166                        log.err("-D{s} value {s} cannot fit into type {s}.", .{ name, s, @typeName(T) });
1167                        b.markInvalidUserInput();
1168                        return null;
1169                    },
1170                    else => {
1171                        log.err("Expected -D{s} to be an integer of type {s}.", .{ name, @typeName(T) });
1172                        b.markInvalidUserInput();
1173                        return null;
1174                    },
1175                };
1176                return n;
1177            },
1178        },
1179        .float => switch (option_ptr.value) {
1180            .flag, .map, .list, .lazy_path, .lazy_path_list => {
1181                log.err("Expected -D{s} to be a float, but received a {s}.", .{
1182                    name, @tagName(option_ptr.value),
1183                });
1184                b.markInvalidUserInput();
1185                return null;
1186            },
1187            .scalar => |s| {
1188                const n = std.fmt.parseFloat(T, s) catch {
1189                    log.err("Expected -D{s} to be a float of type {s}.", .{ name, @typeName(T) });
1190                    b.markInvalidUserInput();
1191                    return null;
1192                };
1193                return n;
1194            },
1195        },
1196        .@"enum" => switch (option_ptr.value) {
1197            .flag, .map, .list, .lazy_path, .lazy_path_list => {
1198                log.err("Expected -D{s} to be an enum, but received a {s}.", .{
1199                    name, @tagName(option_ptr.value),
1200                });
1201                b.markInvalidUserInput();
1202                return null;
1203            },
1204            .scalar => |s| {
1205                if (std.meta.stringToEnum(T, s)) |enum_lit| {
1206                    return enum_lit;
1207                } else {
1208                    log.err("Expected -D{s} to be of type {s}.", .{ name, @typeName(T) });
1209                    b.markInvalidUserInput();
1210                    return null;
1211                }
1212            },
1213        },
1214        .string => switch (option_ptr.value) {
1215            .flag, .list, .map, .lazy_path, .lazy_path_list => {
1216                log.err("Expected -D{s} to be a string, but received a {s}.", .{
1217                    name, @tagName(option_ptr.value),
1218                });
1219                b.markInvalidUserInput();
1220                return null;
1221            },
1222            .scalar => |s| return s,
1223        },
1224        .build_id => switch (option_ptr.value) {
1225            .flag, .map, .list, .lazy_path, .lazy_path_list => {
1226                log.err("Expected -D{s} to be an enum, but received a {s}.", .{
1227                    name, @tagName(option_ptr.value),
1228                });
1229                b.markInvalidUserInput();
1230                return null;
1231            },
1232            .scalar => |s| {
1233                if (std.zig.BuildId.parse(s)) |build_id| {
1234                    return build_id;
1235                } else |err| {
1236                    log.err("unable to parse option '-D{s}': {s}", .{ name, @errorName(err) });
1237                    b.markInvalidUserInput();
1238                    return null;
1239                }
1240            },
1241        },
1242        .list => switch (option_ptr.value) {
1243            .flag, .map, .lazy_path, .lazy_path_list => {
1244                log.err("Expected -D{s} to be a list, but received a {s}.", .{
1245                    name, @tagName(option_ptr.value),
1246                });
1247                b.markInvalidUserInput();
1248                return null;
1249            },
1250            .scalar => |s| {
1251                return b.allocator.dupe([]const u8, &[_][]const u8{s}) catch @panic("OOM");
1252            },
1253            .list => |lst| return lst.items,
1254        },
1255        .enum_list => switch (option_ptr.value) {
1256            .flag, .map, .lazy_path, .lazy_path_list => {
1257                log.err("Expected -D{s} to be a list, but received a {s}.", .{
1258                    name, @tagName(option_ptr.value),
1259                });
1260                b.markInvalidUserInput();
1261                return null;
1262            },
1263            .scalar => |s| {
1264                const Child = @typeInfo(T).pointer.child;
1265                const value = std.meta.stringToEnum(Child, s) orelse {
1266                    log.err("Expected -D{s} to be of type {s}.", .{ name, @typeName(Child) });
1267                    b.markInvalidUserInput();
1268                    return null;
1269                };
1270                return b.allocator.dupe(Child, &[_]Child{value}) catch @panic("OOM");
1271            },
1272            .list => |lst| {
1273                const Child = @typeInfo(T).pointer.child;
1274                const new_list = b.allocator.alloc(Child, lst.items.len) catch @panic("OOM");
1275                for (new_list, lst.items) |*new_item, str| {
1276                    new_item.* = std.meta.stringToEnum(Child, str) orelse {
1277                        log.err("Expected -D{s} to be of type {s}.", .{ name, @typeName(Child) });
1278                        b.markInvalidUserInput();
1279                        b.allocator.free(new_list);
1280                        return null;
1281                    };
1282                }
1283                return new_list;
1284            },
1285        },
1286        .lazy_path => switch (option_ptr.value) {
1287            .scalar => |s| return .{ .cwd_relative = s },
1288            .lazy_path => |lp| return lp,
1289            .flag, .map, .list, .lazy_path_list => {
1290                log.err("Expected -D{s} to be a path, but received a {s}.", .{
1291                    name, @tagName(option_ptr.value),
1292                });
1293                b.markInvalidUserInput();
1294                return null;
1295            },
1296        },
1297        .lazy_path_list => switch (option_ptr.value) {
1298            .scalar => |s| return b.allocator.dupe(LazyPath, &[_]LazyPath{.{ .cwd_relative = s }}) catch @panic("OOM"),
1299            .lazy_path => |lp| return b.allocator.dupe(LazyPath, &[_]LazyPath{lp}) catch @panic("OOM"),
1300            .list => |lst| {
1301                const new_list = b.allocator.alloc(LazyPath, lst.items.len) catch @panic("OOM");
1302                for (new_list, lst.items) |*new_item, str| {
1303                    new_item.* = .{ .cwd_relative = str };
1304                }
1305                return new_list;
1306            },
1307            .lazy_path_list => |lp_list| return lp_list.items,
1308            .flag, .map => {
1309                log.err("Expected -D{s} to be a path, but received a {s}.", .{
1310                    name, @tagName(option_ptr.value),
1311                });
1312                b.markInvalidUserInput();
1313                return null;
1314            },
1315        },
1316    }
1317}
1318
1319pub fn step(b: *Build, name: []const u8, description: []const u8) *Step {
1320    const step_info = b.allocator.create(TopLevelStep) catch @panic("OOM");
1321    step_info.* = .{
1322        .step = .init(.{
1323            .id = TopLevelStep.base_id,
1324            .name = name,
1325            .owner = b,
1326        }),
1327        .description = b.dupe(description),
1328    };
1329    const gop = b.top_level_steps.getOrPut(b.allocator, name) catch @panic("OOM");
1330    if (gop.found_existing) std.debug.panic("A top-level step with name \"{s}\" already exists", .{name});
1331
1332    gop.key_ptr.* = step_info.step.name;
1333    gop.value_ptr.* = step_info;
1334
1335    return &step_info.step;
1336}
1337
1338pub const StandardOptimizeOptionOptions = struct {
1339    preferred_optimize_mode: ?std.builtin.OptimizeMode = null,
1340};
1341
1342pub fn standardOptimizeOption(b: *Build, options: StandardOptimizeOptionOptions) std.builtin.OptimizeMode {
1343    if (options.preferred_optimize_mode) |mode| {
1344        if (b.option(bool, "release", "optimize for end users") orelse (b.release_mode != .off)) {
1345            return mode;
1346        } else {
1347            return .Debug;
1348        }
1349    }
1350
1351    if (b.option(
1352        std.builtin.OptimizeMode,
1353        "optimize",
1354        "Prioritize performance, safety, or binary size",
1355    )) |mode| {
1356        return mode;
1357    }
1358
1359    return switch (b.release_mode) {
1360        .off => .Debug,
1361        .any => {
1362            std.debug.print("the project does not declare a preferred optimization mode. choose: --release=fast, --release=safe, or --release=small\n", .{});
1363            process.exit(1);
1364        },
1365        .fast => .ReleaseFast,
1366        .safe => .ReleaseSafe,
1367        .small => .ReleaseSmall,
1368    };
1369}
1370
1371pub const StandardTargetOptionsArgs = struct {
1372    whitelist: ?[]const Target.Query = null,
1373    default_target: Target.Query = .{},
1374};
1375
1376/// Exposes standard `zig build` options for choosing a target and additionally
1377/// resolves the target query.
1378pub fn standardTargetOptions(b: *Build, args: StandardTargetOptionsArgs) ResolvedTarget {
1379    const query = b.standardTargetOptionsQueryOnly(args);
1380    return b.resolveTargetQuery(query);
1381}
1382
1383/// Obtain a target query from a string, reporting diagnostics to stderr if the
1384/// parsing failed.
1385/// Asserts that the `diagnostics` field of `options` is `null`. This use case
1386/// is handled instead by calling `std.Target.Query.parse` directly.
1387pub fn parseTargetQuery(options: std.Target.Query.ParseOptions) error{ParseFailed}!std.Target.Query {
1388    assert(options.diagnostics == null);
1389    var diags: Target.Query.ParseOptions.Diagnostics = .{};
1390    var opts_copy = options;
1391    opts_copy.diagnostics = &diags;
1392    return std.Target.Query.parse(opts_copy) catch |err| switch (err) {
1393        error.UnknownCpuModel => {
1394            std.debug.print("unknown CPU: '{s}'\navailable CPUs for architecture '{s}':\n", .{
1395                diags.cpu_name.?, @tagName(diags.arch.?),
1396            });
1397            for (diags.arch.?.allCpuModels()) |cpu| {
1398                std.debug.print(" {s}\n", .{cpu.name});
1399            }
1400            return error.ParseFailed;
1401        },
1402        error.UnknownCpuFeature => {
1403            std.debug.print(
1404                \\unknown CPU feature: '{s}'
1405                \\available CPU features for architecture '{s}':
1406                \\
1407            , .{
1408                diags.unknown_feature_name.?,
1409                @tagName(diags.arch.?),
1410            });
1411            for (diags.arch.?.allFeaturesList()) |feature| {
1412                std.debug.print(" {s}: {s}\n", .{ feature.name, feature.description });
1413            }
1414            return error.ParseFailed;
1415        },
1416        error.UnknownOperatingSystem => {
1417            std.debug.print(
1418                \\unknown OS: '{s}'
1419                \\available operating systems:
1420                \\
1421            , .{diags.os_name.?});
1422            inline for (std.meta.fields(Target.Os.Tag)) |field| {
1423                std.debug.print(" {s}\n", .{field.name});
1424            }
1425            return error.ParseFailed;
1426        },
1427        else => |e| {
1428            std.debug.print("unable to parse target '{s}': {s}\n", .{
1429                options.arch_os_abi, @errorName(e),
1430            });
1431            return error.ParseFailed;
1432        },
1433    };
1434}
1435
1436/// Exposes standard `zig build` options for choosing a target.
1437pub fn standardTargetOptionsQueryOnly(b: *Build, args: StandardTargetOptionsArgs) Target.Query {
1438    const maybe_triple = b.option(
1439        []const u8,
1440        "target",
1441        "The CPU architecture, OS, and ABI to build for",
1442    );
1443    const mcpu = b.option(
1444        []const u8,
1445        "cpu",
1446        "Target CPU features to add or subtract",
1447    );
1448    const ofmt = b.option(
1449        []const u8,
1450        "ofmt",
1451        "Target object format",
1452    );
1453    const dynamic_linker = b.option(
1454        []const u8,
1455        "dynamic-linker",
1456        "Path to interpreter on the target system",
1457    );
1458
1459    if (maybe_triple == null and mcpu == null and ofmt == null and dynamic_linker == null)
1460        return args.default_target;
1461
1462    const triple = maybe_triple orelse "native";
1463
1464    const selected_target = parseTargetQuery(.{
1465        .arch_os_abi = triple,
1466        .cpu_features = mcpu,
1467        .object_format = ofmt,
1468        .dynamic_linker = dynamic_linker,
1469    }) catch |err| switch (err) {
1470        error.ParseFailed => {
1471            b.markInvalidUserInput();
1472            return args.default_target;
1473        },
1474    };
1475
1476    const whitelist = args.whitelist orelse return selected_target;
1477
1478    // Make sure it's a match of one of the list.
1479    for (whitelist) |q| {
1480        if (q.eql(selected_target))
1481            return selected_target;
1482    }
1483
1484    for (whitelist) |q| {
1485        log.info("allowed target: -Dtarget={s} -Dcpu={s}", .{
1486            q.zigTriple(b.allocator) catch @panic("OOM"),
1487            q.serializeCpuAlloc(b.allocator) catch @panic("OOM"),
1488        });
1489    }
1490    log.err("chosen target '{s}' does not match one of the allowed targets", .{
1491        selected_target.zigTriple(b.allocator) catch @panic("OOM"),
1492    });
1493    b.markInvalidUserInput();
1494    return args.default_target;
1495}
1496
1497pub fn addUserInputOption(b: *Build, name_raw: []const u8, value_raw: []const u8) error{OutOfMemory}!bool {
1498    const name = b.dupe(name_raw);
1499    const value = b.dupe(value_raw);
1500    const gop = try b.user_input_options.getOrPut(name);
1501    if (!gop.found_existing) {
1502        gop.value_ptr.* = UserInputOption{
1503            .name = name,
1504            .value = .{ .scalar = value },
1505            .used = false,
1506        };
1507        return false;
1508    }
1509
1510    // option already exists
1511    switch (gop.value_ptr.value) {
1512        .scalar => |s| {
1513            // turn it into a list
1514            var list = std.array_list.Managed([]const u8).init(b.allocator);
1515            try list.append(s);
1516            try list.append(value);
1517            try b.user_input_options.put(name, .{
1518                .name = name,
1519                .value = .{ .list = list },
1520                .used = false,
1521            });
1522        },
1523        .list => |*list| {
1524            // append to the list
1525            try list.append(value);
1526            try b.user_input_options.put(name, .{
1527                .name = name,
1528                .value = .{ .list = list.* },
1529                .used = false,
1530            });
1531        },
1532        .flag => {
1533            log.warn("option '-D{s}={s}' conflicts with flag '-D{s}'.", .{ name, value, name });
1534            return true;
1535        },
1536        .map => |*map| {
1537            _ = map;
1538            log.warn("TODO maps as command line arguments is not implemented yet.", .{});
1539            return true;
1540        },
1541        .lazy_path, .lazy_path_list => {
1542            log.warn("the lazy path value type isn't added from the CLI, but somehow '{s}' is a .{f}", .{ name, std.zig.fmtId(@tagName(gop.value_ptr.value)) });
1543            return true;
1544        },
1545    }
1546    return false;
1547}
1548
1549pub fn addUserInputFlag(b: *Build, name_raw: []const u8) error{OutOfMemory}!bool {
1550    const name = b.dupe(name_raw);
1551    const gop = try b.user_input_options.getOrPut(name);
1552    if (!gop.found_existing) {
1553        gop.value_ptr.* = .{
1554            .name = name,
1555            .value = .{ .flag = {} },
1556            .used = false,
1557        };
1558        return false;
1559    }
1560
1561    // option already exists
1562    switch (gop.value_ptr.value) {
1563        .scalar => |s| {
1564            log.err("Flag '-D{s}' conflicts with option '-D{s}={s}'.", .{ name, name, s });
1565            return true;
1566        },
1567        .list, .map, .lazy_path_list => {
1568            log.err("Flag '-D{s}' conflicts with multiple options of the same name.", .{name});
1569            return true;
1570        },
1571        .lazy_path => |lp| {
1572            log.err("Flag '-D{s}' conflicts with option '-D{s}={s}'.", .{ name, name, lp.getDisplayName() });
1573            return true;
1574        },
1575
1576        .flag => {},
1577    }
1578    return false;
1579}
1580
1581fn typeToEnum(comptime T: type) TypeId {
1582    return switch (T) {
1583        std.zig.BuildId => .build_id,
1584        LazyPath => .lazy_path,
1585        else => return switch (@typeInfo(T)) {
1586            .int => .int,
1587            .float => .float,
1588            .bool => .bool,
1589            .@"enum" => .@"enum",
1590            .pointer => |pointer| switch (pointer.child) {
1591                u8 => .string,
1592                []const u8 => .list,
1593                LazyPath => .lazy_path_list,
1594                else => switch (@typeInfo(pointer.child)) {
1595                    .@"enum" => .enum_list,
1596                    else => @compileError("Unsupported type: " ++ @typeName(T)),
1597                },
1598            },
1599            else => @compileError("Unsupported type: " ++ @typeName(T)),
1600        },
1601    };
1602}
1603
1604fn markInvalidUserInput(b: *Build) void {
1605    b.invalid_user_input = true;
1606}
1607
1608pub fn validateUserInputDidItFail(b: *Build) bool {
1609    // Make sure all args are used.
1610    var it = b.user_input_options.iterator();
1611    while (it.next()) |entry| {
1612        if (!entry.value_ptr.used) {
1613            log.err("invalid option: -D{s}", .{entry.key_ptr.*});
1614            b.markInvalidUserInput();
1615        }
1616    }
1617
1618    return b.invalid_user_input;
1619}
1620
1621/// This creates the install step and adds it to the dependencies of the
1622/// top-level install step, using all the default options.
1623/// See `addInstallArtifact` for a more flexible function.
1624pub fn installArtifact(b: *Build, artifact: *Step.Compile) void {
1625    b.getInstallStep().dependOn(&b.addInstallArtifact(artifact, .{}).step);
1626}
1627
1628/// This merely creates the step; it does not add it to the dependencies of the
1629/// top-level install step.
1630pub fn addInstallArtifact(
1631    b: *Build,
1632    artifact: *Step.Compile,
1633    options: Step.InstallArtifact.Options,
1634) *Step.InstallArtifact {
1635    return Step.InstallArtifact.create(b, artifact, options);
1636}
1637
1638///`dest_rel_path` is relative to prefix path
1639pub fn installFile(b: *Build, src_path: []const u8, dest_rel_path: []const u8) void {
1640    b.getInstallStep().dependOn(&b.addInstallFileWithDir(b.path(src_path), .prefix, dest_rel_path).step);
1641}
1642
1643pub fn installDirectory(b: *Build, options: Step.InstallDir.Options) void {
1644    b.getInstallStep().dependOn(&b.addInstallDirectory(options).step);
1645}
1646
1647///`dest_rel_path` is relative to bin path
1648pub fn installBinFile(b: *Build, src_path: []const u8, dest_rel_path: []const u8) void {
1649    b.getInstallStep().dependOn(&b.addInstallFileWithDir(b.path(src_path), .bin, dest_rel_path).step);
1650}
1651
1652///`dest_rel_path` is relative to lib path
1653pub fn installLibFile(b: *Build, src_path: []const u8, dest_rel_path: []const u8) void {
1654    b.getInstallStep().dependOn(&b.addInstallFileWithDir(b.path(src_path), .lib, dest_rel_path).step);
1655}
1656
1657pub fn addObjCopy(b: *Build, source: LazyPath, options: Step.ObjCopy.Options) *Step.ObjCopy {
1658    return Step.ObjCopy.create(b, source, options);
1659}
1660
1661/// `dest_rel_path` is relative to install prefix path
1662pub fn addInstallFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
1663    return b.addInstallFileWithDir(source, .prefix, dest_rel_path);
1664}
1665
1666/// `dest_rel_path` is relative to bin path
1667pub fn addInstallBinFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
1668    return b.addInstallFileWithDir(source, .bin, dest_rel_path);
1669}
1670
1671/// `dest_rel_path` is relative to lib path
1672pub fn addInstallLibFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
1673    return b.addInstallFileWithDir(source, .lib, dest_rel_path);
1674}
1675
1676/// `dest_rel_path` is relative to header path
1677pub fn addInstallHeaderFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
1678    return b.addInstallFileWithDir(source, .header, dest_rel_path);
1679}
1680
1681pub fn addInstallFileWithDir(
1682    b: *Build,
1683    source: LazyPath,
1684    install_dir: InstallDir,
1685    dest_rel_path: []const u8,
1686) *Step.InstallFile {
1687    return Step.InstallFile.create(b, source, install_dir, dest_rel_path);
1688}
1689
1690pub fn addInstallDirectory(b: *Build, options: Step.InstallDir.Options) *Step.InstallDir {
1691    return Step.InstallDir.create(b, options);
1692}
1693
1694pub fn addCheckFile(
1695    b: *Build,
1696    file_source: LazyPath,
1697    options: Step.CheckFile.Options,
1698) *Step.CheckFile {
1699    return Step.CheckFile.create(b, file_source, options);
1700}
1701
1702pub fn truncateFile(b: *Build, dest_path: []const u8) (fs.Dir.MakeError || fs.Dir.StatFileError)!void {
1703    if (b.verbose) {
1704        log.info("truncate {s}", .{dest_path});
1705    }
1706    const cwd = fs.cwd();
1707    var src_file = cwd.createFile(dest_path, .{}) catch |err| switch (err) {
1708        error.FileNotFound => blk: {
1709            if (fs.path.dirname(dest_path)) |dirname| {
1710                try cwd.makePath(dirname);
1711            }
1712            break :blk try cwd.createFile(dest_path, .{});
1713        },
1714        else => |e| return e,
1715    };
1716    src_file.close();
1717}
1718
1719/// References a file or directory relative to the source root.
1720pub fn path(b: *Build, sub_path: []const u8) LazyPath {
1721    if (fs.path.isAbsolute(sub_path)) {
1722        std.debug.panic("sub_path is expected to be relative to the build root, but was this absolute path: '{s}'. It is best avoid absolute paths, but if you must, it is supported by LazyPath.cwd_relative", .{
1723            sub_path,
1724        });
1725    }
1726    return .{ .src_path = .{
1727        .owner = b,
1728        .sub_path = sub_path,
1729    } };
1730}
1731
1732/// This is low-level implementation details of the build system, not meant to
1733/// be called by users' build scripts. Even in the build system itself it is a
1734/// code smell to call this function.
1735pub fn pathFromRoot(b: *Build, sub_path: []const u8) []u8 {
1736    return b.pathResolve(&.{ b.build_root.path orelse ".", sub_path });
1737}
1738
1739fn pathFromCwd(b: *Build, sub_path: []const u8) []u8 {
1740    const cwd = process.getCwdAlloc(b.allocator) catch @panic("OOM");
1741    return b.pathResolve(&.{ cwd, sub_path });
1742}
1743
1744pub fn pathJoin(b: *Build, paths: []const []const u8) []u8 {
1745    return fs.path.join(b.allocator, paths) catch @panic("OOM");
1746}
1747
1748pub fn pathResolve(b: *Build, paths: []const []const u8) []u8 {
1749    return fs.path.resolve(b.allocator, paths) catch @panic("OOM");
1750}
1751
1752pub fn fmt(b: *Build, comptime format: []const u8, args: anytype) []u8 {
1753    return std.fmt.allocPrint(b.allocator, format, args) catch @panic("OOM");
1754}
1755
1756fn supportedWindowsProgramExtension(ext: []const u8) bool {
1757    inline for (@typeInfo(std.process.Child.WindowsExtension).@"enum".fields) |field| {
1758        if (std.ascii.eqlIgnoreCase(ext, "." ++ field.name)) return true;
1759    }
1760    return false;
1761}
1762
1763fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 {
1764    if (fs.realpathAlloc(b.allocator, full_path)) |p| {
1765        return p;
1766    } else |err| switch (err) {
1767        error.OutOfMemory => @panic("OOM"),
1768        else => {},
1769    }
1770
1771    if (builtin.os.tag == .windows) {
1772        if (b.graph.env_map.get("PATHEXT")) |PATHEXT| {
1773            var it = mem.tokenizeScalar(u8, PATHEXT, fs.path.delimiter);
1774
1775            while (it.next()) |ext| {
1776                if (!supportedWindowsProgramExtension(ext)) continue;
1777
1778                return fs.realpathAlloc(b.allocator, b.fmt("{s}{s}", .{ full_path, ext })) catch |err| switch (err) {
1779                    error.OutOfMemory => @panic("OOM"),
1780                    else => continue,
1781                };
1782            }
1783        }
1784    }
1785
1786    return null;
1787}
1788
1789pub fn findProgram(b: *Build, names: []const []const u8, paths: []const []const u8) error{FileNotFound}![]const u8 {
1790    // TODO report error for ambiguous situations
1791    for (b.search_prefixes.items) |search_prefix| {
1792        for (names) |name| {
1793            if (fs.path.isAbsolute(name)) {
1794                return name;
1795            }
1796            return tryFindProgram(b, b.pathJoin(&.{ search_prefix, "bin", name })) orelse continue;
1797        }
1798    }
1799    if (b.graph.env_map.get("PATH")) |PATH| {
1800        for (names) |name| {
1801            if (fs.path.isAbsolute(name)) {
1802                return name;
1803            }
1804            var it = mem.tokenizeScalar(u8, PATH, fs.path.delimiter);
1805            while (it.next()) |p| {
1806                return tryFindProgram(b, b.pathJoin(&.{ p, name })) orelse continue;
1807            }
1808        }
1809    }
1810    for (names) |name| {
1811        if (fs.path.isAbsolute(name)) {
1812            return name;
1813        }
1814        for (paths) |p| {
1815            return tryFindProgram(b, b.pathJoin(&.{ p, name })) orelse continue;
1816        }
1817    }
1818    return error.FileNotFound;
1819}
1820
1821pub fn runAllowFail(
1822    b: *Build,
1823    argv: []const []const u8,
1824    out_code: *u8,
1825    stderr_behavior: std.process.Child.StdIo,
1826) RunError![]u8 {
1827    assert(argv.len != 0);
1828
1829    if (!process.can_spawn)
1830        return error.ExecNotSupported;
1831
1832    const io = b.graph.io;
1833
1834    const max_output_size = 400 * 1024;
1835    var child = std.process.Child.init(argv, b.allocator);
1836    child.stdin_behavior = .Ignore;
1837    child.stdout_behavior = .Pipe;
1838    child.stderr_behavior = stderr_behavior;
1839    child.env_map = &b.graph.env_map;
1840
1841    try Step.handleVerbose2(b, null, child.env_map, argv);
1842    try child.spawn();
1843
1844    var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
1845    const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch {
1846        return error.ReadFailure;
1847    };
1848    errdefer b.allocator.free(stdout);
1849
1850    const term = try child.wait();
1851    switch (term) {
1852        .Exited => |code| {
1853            if (code != 0) {
1854                out_code.* = @as(u8, @truncate(code));
1855                return error.ExitCodeFailure;
1856            }
1857            return stdout;
1858        },
1859        .Signal, .Stopped, .Unknown => |code| {
1860            out_code.* = @as(u8, @truncate(code));
1861            return error.ProcessTerminated;
1862        },
1863    }
1864}
1865
1866/// This is a helper function to be called from build.zig scripts, *not* from
1867/// inside step make() functions. If any errors occur, it fails the build with
1868/// a helpful message.
1869pub fn run(b: *Build, argv: []const []const u8) []u8 {
1870    if (!process.can_spawn) {
1871        std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{
1872            try Step.allocPrintCmd(b.allocator, null, argv),
1873        });
1874        process.exit(1);
1875    }
1876
1877    var code: u8 = undefined;
1878    return b.runAllowFail(argv, &code, .Inherit) catch |err| {
1879        const printed_cmd = Step.allocPrintCmd(b.allocator, null, argv) catch @panic("OOM");
1880        std.debug.print("unable to spawn the following command: {s}\n{s}\n", .{
1881            @errorName(err), printed_cmd,
1882        });
1883        process.exit(1);
1884    };
1885}
1886
1887pub fn addSearchPrefix(b: *Build, search_prefix: []const u8) void {
1888    b.search_prefixes.append(b.allocator, b.dupePath(search_prefix)) catch @panic("OOM");
1889}
1890
1891pub fn getInstallPath(b: *Build, dir: InstallDir, dest_rel_path: []const u8) []const u8 {
1892    assert(!fs.path.isAbsolute(dest_rel_path)); // Install paths must be relative to the prefix
1893    const base_dir = switch (dir) {
1894        .prefix => b.install_path,
1895        .bin => b.exe_dir,
1896        .lib => b.lib_dir,
1897        .header => b.h_dir,
1898        .custom => |p| b.pathJoin(&.{ b.install_path, p }),
1899    };
1900    return b.pathResolve(&.{ base_dir, dest_rel_path });
1901}
1902
1903pub const Dependency = struct {
1904    builder: *Build,
1905
1906    pub fn artifact(d: *Dependency, name: []const u8) *Step.Compile {
1907        var found: ?*Step.Compile = null;
1908        for (d.builder.install_tls.step.dependencies.items) |dep_step| {
1909            const inst = dep_step.cast(Step.InstallArtifact) orelse continue;
1910            if (mem.eql(u8, inst.artifact.name, name)) {
1911                if (found != null) panic("artifact name '{s}' is ambiguous", .{name});
1912                found = inst.artifact;
1913            }
1914        }
1915        return found orelse {
1916            for (d.builder.install_tls.step.dependencies.items) |dep_step| {
1917                const inst = dep_step.cast(Step.InstallArtifact) orelse continue;
1918                log.info("available artifact: '{s}'", .{inst.artifact.name});
1919            }
1920            panic("unable to find artifact '{s}'", .{name});
1921        };
1922    }
1923
1924    pub fn module(d: *Dependency, name: []const u8) *Module {
1925        return d.builder.modules.get(name) orelse {
1926            panic("unable to find module '{s}'", .{name});
1927        };
1928    }
1929
1930    pub fn namedWriteFiles(d: *Dependency, name: []const u8) *Step.WriteFile {
1931        return d.builder.named_writefiles.get(name) orelse {
1932            panic("unable to find named writefiles '{s}'", .{name});
1933        };
1934    }
1935
1936    pub fn namedLazyPath(d: *Dependency, name: []const u8) LazyPath {
1937        return d.builder.named_lazy_paths.get(name) orelse {
1938            panic("unable to find named lazypath '{s}'", .{name});
1939        };
1940    }
1941
1942    pub fn path(d: *Dependency, sub_path: []const u8) LazyPath {
1943        return .{
1944            .dependency = .{
1945                .dependency = d,
1946                .sub_path = sub_path,
1947            },
1948        };
1949    }
1950};
1951
1952fn findPkgHashOrFatal(b: *Build, name: []const u8) []const u8 {
1953    for (b.available_deps) |dep| {
1954        if (mem.eql(u8, dep[0], name)) return dep[1];
1955    }
1956
1957    const full_path = b.pathFromRoot("build.zig.zon");
1958    std.debug.panic("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file", .{ name, full_path });
1959}
1960
1961inline fn findImportPkgHashOrFatal(b: *Build, comptime asking_build_zig: type, comptime dep_name: []const u8) []const u8 {
1962    const build_runner = @import("root");
1963    const deps = build_runner.dependencies;
1964
1965    const b_pkg_hash, const b_pkg_deps = comptime for (@typeInfo(deps.packages).@"struct".decls) |decl| {
1966        const pkg_hash = decl.name;
1967        const pkg = @field(deps.packages, pkg_hash);
1968        if (@hasDecl(pkg, "build_zig") and pkg.build_zig == asking_build_zig) break .{ pkg_hash, pkg.deps };
1969    } else .{ "", deps.root_deps };
1970    if (!std.mem.eql(u8, b_pkg_hash, b.pkg_hash)) {
1971        std.debug.panic("'{}' is not the struct that corresponds to '{s}'", .{ asking_build_zig, b.pathFromRoot("build.zig") });
1972    }
1973    comptime for (b_pkg_deps) |dep| {
1974        if (std.mem.eql(u8, dep[0], dep_name)) return dep[1];
1975    };
1976
1977    const full_path = b.pathFromRoot("build.zig.zon");
1978    std.debug.panic("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file", .{ dep_name, full_path });
1979}
1980
1981fn markNeededLazyDep(b: *Build, pkg_hash: []const u8) void {
1982    b.graph.needed_lazy_dependencies.put(b.graph.arena, pkg_hash, {}) catch @panic("OOM");
1983}
1984
1985/// When this function is called, it means that the current build does, in
1986/// fact, require this dependency. If the dependency is already fetched, it
1987/// proceeds in the same manner as `dependency`. However if the dependency was
1988/// not fetched, then when the build script is finished running, the build will
1989/// not proceed to the make phase. Instead, the parent process will
1990/// additionally fetch all the lazy dependencies that were actually required by
1991/// running the build script, rebuild the build script, and then run it again.
1992/// In other words, if this function returns `null` it means that the only
1993/// purpose of completing the configure phase is to find out all the other lazy
1994/// dependencies that are also required.
1995/// It is allowed to use this function for non-lazy dependencies, in which case
1996/// it will never return `null`. This allows toggling laziness via
1997/// build.zig.zon without changing build.zig logic.
1998pub fn lazyDependency(b: *Build, name: []const u8, args: anytype) ?*Dependency {
1999    const build_runner = @import("root");
2000    const deps = build_runner.dependencies;
2001    const pkg_hash = findPkgHashOrFatal(b, name);
2002
2003    inline for (@typeInfo(deps.packages).@"struct".decls) |decl| {
2004        if (mem.eql(u8, decl.name, pkg_hash)) {
2005            const pkg = @field(deps.packages, decl.name);
2006            const available = !@hasDecl(pkg, "available") or pkg.available;
2007            if (!available) {
2008                markNeededLazyDep(b, pkg_hash);
2009                return null;
2010            }
2011            return dependencyInner(b, name, pkg.build_root, if (@hasDecl(pkg, "build_zig")) pkg.build_zig else null, pkg_hash, pkg.deps, args);
2012        }
2013    }
2014
2015    unreachable; // Bad @dependencies source
2016}
2017
2018pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {
2019    const build_runner = @import("root");
2020    const deps = build_runner.dependencies;
2021    const pkg_hash = findPkgHashOrFatal(b, name);
2022
2023    inline for (@typeInfo(deps.packages).@"struct".decls) |decl| {
2024        if (mem.eql(u8, decl.name, pkg_hash)) {
2025            const pkg = @field(deps.packages, decl.name);
2026            if (@hasDecl(pkg, "available")) {
2027                std.debug.panic("dependency '{s}{s}' is marked as lazy in build.zig.zon which means it must use the lazyDependency function instead", .{ b.dep_prefix, name });
2028            }
2029            return dependencyInner(b, name, pkg.build_root, if (@hasDecl(pkg, "build_zig")) pkg.build_zig else null, pkg_hash, pkg.deps, args);
2030        }
2031    }
2032
2033    unreachable; // Bad @dependencies source
2034}
2035
2036/// In a build.zig file, this function is to `@import` what `lazyDependency` is to `dependency`.
2037/// If the dependency is lazy and has not yet been fetched, it instructs the parent process to fetch
2038/// that dependency after the build script has finished running, then returns `null`.
2039/// If the dependency is lazy but has already been fetched, or if it is eager, it returns
2040/// the build.zig struct of that dependency, just like a regular `@import`.
2041pub inline fn lazyImport(
2042    b: *Build,
2043    /// The build.zig struct of the package importing the dependency.
2044    /// When calling this function from the `build` function of a build.zig file's, you normally
2045    /// pass `@This()`.
2046    comptime asking_build_zig: type,
2047    comptime dep_name: []const u8,
2048) ?type {
2049    const build_runner = @import("root");
2050    const deps = build_runner.dependencies;
2051    const pkg_hash = findImportPkgHashOrFatal(b, asking_build_zig, dep_name);
2052
2053    inline for (@typeInfo(deps.packages).@"struct".decls) |decl| {
2054        if (comptime mem.eql(u8, decl.name, pkg_hash)) {
2055            const pkg = @field(deps.packages, decl.name);
2056            const available = !@hasDecl(pkg, "available") or pkg.available;
2057            if (!available) {
2058                markNeededLazyDep(b, pkg_hash);
2059                return null;
2060            }
2061            return if (@hasDecl(pkg, "build_zig"))
2062                pkg.build_zig
2063            else
2064                @compileError("dependency '" ++ dep_name ++ "' does not have a build.zig");
2065        }
2066    }
2067
2068    comptime unreachable; // Bad @dependencies source
2069}
2070
2071pub fn dependencyFromBuildZig(
2072    b: *Build,
2073    /// The build.zig struct of the dependency, normally obtained by `@import` of the dependency.
2074    /// If called from the build.zig file itself, use `@This` to obtain a reference to the struct.
2075    comptime build_zig: type,
2076    args: anytype,
2077) *Dependency {
2078    const build_runner = @import("root");
2079    const deps = build_runner.dependencies;
2080
2081    find_dep: {
2082        const pkg, const pkg_hash = inline for (@typeInfo(deps.packages).@"struct".decls) |decl| {
2083            const pkg_hash = decl.name;
2084            const pkg = @field(deps.packages, pkg_hash);
2085            if (@hasDecl(pkg, "build_zig") and pkg.build_zig == build_zig) break .{ pkg, pkg_hash };
2086        } else break :find_dep;
2087        const dep_name = for (b.available_deps) |dep| {
2088            if (mem.eql(u8, dep[1], pkg_hash)) break dep[1];
2089        } else break :find_dep;
2090        return dependencyInner(b, dep_name, pkg.build_root, pkg.build_zig, pkg_hash, pkg.deps, args);
2091    }
2092
2093    const full_path = b.pathFromRoot("build.zig.zon");
2094    debug.panic("'{}' is not a build.zig struct of a dependency in '{s}'", .{ build_zig, full_path });
2095}
2096
2097fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool {
2098    if (std.meta.activeTag(lhs) != rhs) return false;
2099    switch (lhs) {
2100        .flag => {},
2101        .scalar => |lhs_scalar| {
2102            const rhs_scalar = rhs.scalar;
2103
2104            if (!std.mem.eql(u8, lhs_scalar, rhs_scalar))
2105                return false;
2106        },
2107        .list => |lhs_list| {
2108            const rhs_list = rhs.list;
2109
2110            if (lhs_list.items.len != rhs_list.items.len)
2111                return false;
2112
2113            for (lhs_list.items, rhs_list.items) |lhs_list_entry, rhs_list_entry| {
2114                if (!std.mem.eql(u8, lhs_list_entry, rhs_list_entry))
2115                    return false;
2116            }
2117        },
2118        .map => |lhs_map| {
2119            const rhs_map = rhs.map;
2120
2121            if (lhs_map.count() != rhs_map.count())
2122                return false;
2123
2124            var lhs_it = lhs_map.iterator();
2125            while (lhs_it.next()) |lhs_entry| {
2126                const rhs_value = rhs_map.get(lhs_entry.key_ptr.*) orelse return false;
2127                if (!userValuesAreSame(lhs_entry.value_ptr.*.*, rhs_value.*))
2128                    return false;
2129            }
2130        },
2131        .lazy_path => |lhs_lp| {
2132            const rhs_lp = rhs.lazy_path;
2133            return userLazyPathsAreTheSame(lhs_lp, rhs_lp);
2134        },
2135        .lazy_path_list => |lhs_lp_list| {
2136            const rhs_lp_list = rhs.lazy_path_list;
2137            if (lhs_lp_list.items.len != rhs_lp_list.items.len) return false;
2138            for (lhs_lp_list.items, rhs_lp_list.items) |lhs_lp, rhs_lp| {
2139                if (!userLazyPathsAreTheSame(lhs_lp, rhs_lp)) return false;
2140            }
2141            return true;
2142        },
2143    }
2144
2145    return true;
2146}
2147
2148fn userLazyPathsAreTheSame(lhs_lp: LazyPath, rhs_lp: LazyPath) bool {
2149    if (std.meta.activeTag(lhs_lp) != rhs_lp) return false;
2150    switch (lhs_lp) {
2151        .src_path => |lhs_sp| {
2152            const rhs_sp = rhs_lp.src_path;
2153
2154            if (lhs_sp.owner != rhs_sp.owner) return false;
2155            if (std.mem.eql(u8, lhs_sp.sub_path, rhs_sp.sub_path)) return false;
2156        },
2157        .generated => |lhs_gen| {
2158            const rhs_gen = rhs_lp.generated;
2159
2160            if (lhs_gen.file != rhs_gen.file) return false;
2161            if (lhs_gen.up != rhs_gen.up) return false;
2162            if (std.mem.eql(u8, lhs_gen.sub_path, rhs_gen.sub_path)) return false;
2163        },
2164        .cwd_relative => |lhs_rel_path| {
2165            const rhs_rel_path = rhs_lp.cwd_relative;
2166
2167            if (!std.mem.eql(u8, lhs_rel_path, rhs_rel_path)) return false;
2168        },
2169        .dependency => |lhs_dep| {
2170            const rhs_dep = rhs_lp.dependency;
2171
2172            if (lhs_dep.dependency != rhs_dep.dependency) return false;
2173            if (!std.mem.eql(u8, lhs_dep.sub_path, rhs_dep.sub_path)) return false;
2174        },
2175    }
2176    return true;
2177}
2178
2179fn dependencyInner(
2180    b: *Build,
2181    name: []const u8,
2182    build_root_string: []const u8,
2183    comptime build_zig: ?type,
2184    pkg_hash: []const u8,
2185    pkg_deps: AvailableDeps,
2186    args: anytype,
2187) *Dependency {
2188    const user_input_options = userInputOptionsFromArgs(b.allocator, args);
2189    if (b.graph.dependency_cache.getContext(.{
2190        .build_root_string = build_root_string,
2191        .user_input_options = user_input_options,
2192    }, .{ .allocator = b.graph.arena })) |dep|
2193        return dep;
2194
2195    const build_root: std.Build.Cache.Directory = .{
2196        .path = build_root_string,
2197        .handle = fs.cwd().openDir(build_root_string, .{}) catch |err| {
2198            std.debug.print("unable to open '{s}': {s}\n", .{
2199                build_root_string, @errorName(err),
2200            });
2201            process.exit(1);
2202        },
2203    };
2204
2205    const sub_builder = b.createChild(name, build_root, pkg_hash, pkg_deps, user_input_options) catch @panic("unhandled error");
2206    if (build_zig) |bz| {
2207        sub_builder.runBuild(bz) catch @panic("unhandled error");
2208
2209        if (sub_builder.validateUserInputDidItFail()) {
2210            std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() });
2211        }
2212    }
2213
2214    const dep = b.allocator.create(Dependency) catch @panic("OOM");
2215    dep.* = .{ .builder = sub_builder };
2216
2217    b.graph.dependency_cache.putContext(b.graph.arena, .{
2218        .build_root_string = build_root_string,
2219        .user_input_options = user_input_options,
2220    }, dep, .{ .allocator = b.graph.arena }) catch @panic("OOM");
2221    return dep;
2222}
2223
2224pub fn runBuild(b: *Build, build_zig: anytype) anyerror!void {
2225    switch (@typeInfo(@typeInfo(@TypeOf(build_zig.build)).@"fn".return_type.?)) {
2226        .void => build_zig.build(b),
2227        .error_union => try build_zig.build(b),
2228        else => @compileError("expected return type of build to be 'void' or '!void'"),
2229    }
2230}
2231
2232/// A file that is generated by a build step.
2233/// This struct is an interface that is meant to be used with `@fieldParentPtr` to implement the actual path logic.
2234pub const GeneratedFile = struct {
2235    /// The step that generates the file
2236    step: *Step,
2237
2238    /// The path to the generated file. Must be either absolute or relative to the build runner cwd.
2239    /// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards.
2240    path: ?[]const u8 = null,
2241
2242    /// Deprecated, see `getPath2`.
2243    pub fn getPath(gen: GeneratedFile) []const u8 {
2244        return gen.step.owner.pathFromCwd(gen.path orelse std.debug.panic(
2245            "getPath() was called on a GeneratedFile that wasn't built yet. Is there a missing Step dependency on step '{s}'?",
2246            .{gen.step.name},
2247        ));
2248    }
2249
2250    pub fn getPath2(gen: GeneratedFile, src_builder: *Build, asking_step: ?*Step) []const u8 {
2251        return gen.path orelse {
2252            const w, const ttyconf = debug.lockStderrWriter(&.{});
2253            dumpBadGetPathHelp(gen.step, w, ttyconf, src_builder, asking_step) catch {};
2254            debug.unlockStderrWriter();
2255            @panic("misconfigured build script");
2256        };
2257    }
2258};
2259
2260// dirnameAllowEmpty is a variant of fs.path.dirname
2261// that allows "" to refer to the root for relative paths.
2262//
2263// For context, dirname("foo") and dirname("") are both null.
2264// However, for relative paths, we want dirname("foo") to be ""
2265// so that we can join it with another path (e.g. build root, cache root, etc.)
2266//
2267// dirname("") should still be null, because we can't go up any further.
2268fn dirnameAllowEmpty(full_path: []const u8) ?[]const u8 {
2269    return fs.path.dirname(full_path) orelse {
2270        if (fs.path.isAbsolute(full_path) or full_path.len == 0) return null;
2271
2272        return "";
2273    };
2274}
2275
2276test dirnameAllowEmpty {
2277    try std.testing.expectEqualStrings(
2278        "foo",
2279        dirnameAllowEmpty("foo" ++ fs.path.sep_str ++ "bar") orelse @panic("unexpected null"),
2280    );
2281
2282    try std.testing.expectEqualStrings(
2283        "",
2284        dirnameAllowEmpty("foo") orelse @panic("unexpected null"),
2285    );
2286
2287    try std.testing.expect(dirnameAllowEmpty("") == null);
2288}
2289
2290/// A reference to an existing or future path.
2291pub const LazyPath = union(enum) {
2292    /// A source file path relative to build root.
2293    src_path: struct {
2294        owner: *std.Build,
2295        sub_path: []const u8,
2296    },
2297
2298    generated: struct {
2299        file: *const GeneratedFile,
2300
2301        /// The number of parent directories to go up.
2302        /// 0 means the generated file itself.
2303        /// 1 means the directory of the generated file.
2304        /// 2 means the parent of that directory, and so on.
2305        up: usize = 0,
2306
2307        /// Applied after `up`.
2308        sub_path: []const u8 = "",
2309    },
2310
2311    /// An absolute path or a path relative to the current working directory of
2312    /// the build runner process.
2313    /// This is uncommon but used for system environment paths such as `--zig-lib-dir` which
2314    /// ignore the file system path of build.zig and instead are relative to the directory from
2315    /// which `zig build` was invoked.
2316    /// Use of this tag indicates a dependency on the host system.
2317    cwd_relative: []const u8,
2318
2319    dependency: struct {
2320        dependency: *Dependency,
2321        sub_path: []const u8,
2322    },
2323
2324    /// Returns a lazy path referring to the directory containing this path.
2325    ///
2326    /// The dirname is not allowed to escape the logical root for underlying path.
2327    /// For example, if the path is relative to the build root,
2328    /// the dirname is not allowed to traverse outside of the build root.
2329    /// Similarly, if the path is a generated file inside zig-cache,
2330    /// the dirname is not allowed to traverse outside of zig-cache.
2331    pub fn dirname(lazy_path: LazyPath) LazyPath {
2332        return switch (lazy_path) {
2333            .src_path => |sp| .{ .src_path = .{
2334                .owner = sp.owner,
2335                .sub_path = dirnameAllowEmpty(sp.sub_path) orelse {
2336                    dumpBadDirnameHelp(null, null, "dirname() attempted to traverse outside the build root\n", .{}) catch {};
2337                    @panic("misconfigured build script");
2338                },
2339            } },
2340            .generated => |generated| .{ .generated = if (dirnameAllowEmpty(generated.sub_path)) |sub_dirname| .{
2341                .file = generated.file,
2342                .up = generated.up,
2343                .sub_path = sub_dirname,
2344            } else .{
2345                .file = generated.file,
2346                .up = generated.up + 1,
2347                .sub_path = "",
2348            } },
2349            .cwd_relative => |rel_path| .{
2350                .cwd_relative = dirnameAllowEmpty(rel_path) orelse {
2351                    // If we get null, it means one of two things:
2352                    // - rel_path was absolute, and is now root
2353                    // - rel_path was relative, and is now ""
2354                    // In either case, the build script tried to go too far
2355                    // and we should panic.
2356                    if (fs.path.isAbsolute(rel_path)) {
2357                        dumpBadDirnameHelp(null, null,
2358                            \\dirname() attempted to traverse outside the root.
2359                            \\No more directories left to go up.
2360                            \\
2361                        , .{}) catch {};
2362                        @panic("misconfigured build script");
2363                    } else {
2364                        dumpBadDirnameHelp(null, null,
2365                            \\dirname() attempted to traverse outside the current working directory.
2366                            \\
2367                        , .{}) catch {};
2368                        @panic("misconfigured build script");
2369                    }
2370                },
2371            },
2372            .dependency => |dep| .{ .dependency = .{
2373                .dependency = dep.dependency,
2374                .sub_path = dirnameAllowEmpty(dep.sub_path) orelse {
2375                    dumpBadDirnameHelp(null, null,
2376                        \\dirname() attempted to traverse outside the dependency root.
2377                        \\
2378                    , .{}) catch {};
2379                    @panic("misconfigured build script");
2380                },
2381            } },
2382        };
2383    }
2384
2385    pub fn path(lazy_path: LazyPath, b: *Build, sub_path: []const u8) LazyPath {
2386        return lazy_path.join(b.allocator, sub_path) catch @panic("OOM");
2387    }
2388
2389    pub fn join(lazy_path: LazyPath, arena: Allocator, sub_path: []const u8) Allocator.Error!LazyPath {
2390        return switch (lazy_path) {
2391            .src_path => |src| .{ .src_path = .{
2392                .owner = src.owner,
2393                .sub_path = try fs.path.resolve(arena, &.{ src.sub_path, sub_path }),
2394            } },
2395            .generated => |gen| .{ .generated = .{
2396                .file = gen.file,
2397                .up = gen.up,
2398                .sub_path = try fs.path.resolve(arena, &.{ gen.sub_path, sub_path }),
2399            } },
2400            .cwd_relative => |cwd_relative| .{
2401                .cwd_relative = try fs.path.resolve(arena, &.{ cwd_relative, sub_path }),
2402            },
2403            .dependency => |dep| .{ .dependency = .{
2404                .dependency = dep.dependency,
2405                .sub_path = try fs.path.resolve(arena, &.{ dep.sub_path, sub_path }),
2406            } },
2407        };
2408    }
2409
2410    /// Returns a string that can be shown to represent the file source.
2411    /// Either returns the path, `"generated"`, or `"dependency"`.
2412    pub fn getDisplayName(lazy_path: LazyPath) []const u8 {
2413        return switch (lazy_path) {
2414            .src_path => |sp| sp.sub_path,
2415            .cwd_relative => |p| p,
2416            .generated => "generated",
2417            .dependency => "dependency",
2418        };
2419    }
2420
2421    /// Adds dependencies this file source implies to the given step.
2422    pub fn addStepDependencies(lazy_path: LazyPath, other_step: *Step) void {
2423        switch (lazy_path) {
2424            .src_path, .cwd_relative, .dependency => {},
2425            .generated => |gen| other_step.dependOn(gen.file.step),
2426        }
2427    }
2428
2429    /// Deprecated, see `getPath3`.
2430    pub fn getPath(lazy_path: LazyPath, src_builder: *Build) []const u8 {
2431        return getPath2(lazy_path, src_builder, null);
2432    }
2433
2434    /// Deprecated, see `getPath3`.
2435    pub fn getPath2(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 {
2436        const p = getPath3(lazy_path, src_builder, asking_step);
2437        return src_builder.pathResolve(&.{ p.root_dir.path orelse ".", p.sub_path });
2438    }
2439
2440    /// Intended to be used during the make phase only.
2441    ///
2442    /// `asking_step` is only used for debugging purposes; it's the step being
2443    /// run that is asking for the path.
2444    pub fn getPath3(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) Cache.Path {
2445        switch (lazy_path) {
2446            .src_path => |sp| return .{
2447                .root_dir = sp.owner.build_root,
2448                .sub_path = sp.sub_path,
2449            },
2450            .cwd_relative => |sub_path| return .{
2451                .root_dir = Cache.Directory.cwd(),
2452                .sub_path = sub_path,
2453            },
2454            .generated => |gen| {
2455                // TODO make gen.file.path not be absolute and use that as the
2456                // basis for not traversing up too many directories.
2457
2458                var file_path: Cache.Path = .{
2459                    .root_dir = Cache.Directory.cwd(),
2460                    .sub_path = gen.file.path orelse {
2461                        const w, const ttyconf = debug.lockStderrWriter(&.{});
2462                        dumpBadGetPathHelp(gen.file.step, w, ttyconf, src_builder, asking_step) catch {};
2463                        debug.unlockStderrWriter();
2464                        @panic("misconfigured build script");
2465                    },
2466                };
2467
2468                if (gen.up > 0) {
2469                    const cache_root_path = src_builder.cache_root.path orelse
2470                        (src_builder.cache_root.join(src_builder.allocator, &.{"."}) catch @panic("OOM"));
2471
2472                    for (0..gen.up) |_| {
2473                        if (mem.eql(u8, file_path.sub_path, cache_root_path)) {
2474                            // If we hit the cache root and there's still more to go,
2475                            // the script attempted to go too far.
2476                            dumpBadDirnameHelp(gen.file.step, asking_step,
2477                                \\dirname() attempted to traverse outside the cache root.
2478                                \\This is not allowed.
2479                                \\
2480                            , .{}) catch {};
2481                            @panic("misconfigured build script");
2482                        }
2483
2484                        // path is absolute.
2485                        // dirname will return null only if we're at root.
2486                        // Typically, we'll stop well before that at the cache root.
2487                        file_path.sub_path = fs.path.dirname(file_path.sub_path) orelse {
2488                            dumpBadDirnameHelp(gen.file.step, asking_step,
2489                                \\dirname() reached root.
2490                                \\No more directories left to go up.
2491                                \\
2492                            , .{}) catch {};
2493                            @panic("misconfigured build script");
2494                        };
2495                    }
2496                }
2497
2498                return file_path.join(src_builder.allocator, gen.sub_path) catch @panic("OOM");
2499            },
2500            .dependency => |dep| return .{
2501                .root_dir = dep.dependency.builder.build_root,
2502                .sub_path = dep.sub_path,
2503            },
2504        }
2505    }
2506
2507    pub fn basename(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 {
2508        return fs.path.basename(switch (lazy_path) {
2509            .src_path => |sp| sp.sub_path,
2510            .cwd_relative => |sub_path| sub_path,
2511            .generated => |gen| if (gen.sub_path.len > 0)
2512                gen.sub_path
2513            else
2514                gen.file.getPath2(src_builder, asking_step),
2515            .dependency => |dep| dep.sub_path,
2516        });
2517    }
2518
2519    /// Copies the internal strings.
2520    ///
2521    /// The `b` parameter is only used for its allocator. All *Build instances
2522    /// share the same allocator.
2523    pub fn dupe(lazy_path: LazyPath, b: *Build) LazyPath {
2524        return lazy_path.dupeInner(b.allocator);
2525    }
2526
2527    fn dupeInner(lazy_path: LazyPath, allocator: std.mem.Allocator) LazyPath {
2528        return switch (lazy_path) {
2529            .src_path => |sp| .{ .src_path = .{
2530                .owner = sp.owner,
2531                .sub_path = sp.owner.dupePath(sp.sub_path),
2532            } },
2533            .cwd_relative => |p| .{ .cwd_relative = dupePathInner(allocator, p) },
2534            .generated => |gen| .{ .generated = .{
2535                .file = gen.file,
2536                .up = gen.up,
2537                .sub_path = dupePathInner(allocator, gen.sub_path),
2538            } },
2539            .dependency => |dep| .{ .dependency = .{
2540                .dependency = dep.dependency,
2541                .sub_path = dupePathInner(allocator, dep.sub_path),
2542            } },
2543        };
2544    }
2545};
2546
2547fn dumpBadDirnameHelp(
2548    fail_step: ?*Step,
2549    asking_step: ?*Step,
2550    comptime msg: []const u8,
2551    args: anytype,
2552) anyerror!void {
2553    const w, const tty_config = debug.lockStderrWriter(&.{});
2554    defer debug.unlockStderrWriter();
2555
2556    try w.print(msg, args);
2557
2558    if (fail_step) |s| {
2559        tty_config.setColor(w, .red) catch {};
2560        try w.writeAll("    The step was created by this stack trace:\n");
2561        tty_config.setColor(w, .reset) catch {};
2562
2563        s.dump(w, tty_config);
2564    }
2565
2566    if (asking_step) |as| {
2567        tty_config.setColor(w, .red) catch {};
2568        try w.print("    The step '{s}' that is missing a dependency on the above step was created by this stack trace:\n", .{as.name});
2569        tty_config.setColor(w, .reset) catch {};
2570
2571        as.dump(w, tty_config);
2572    }
2573
2574    tty_config.setColor(w, .red) catch {};
2575    try w.writeAll("    Hope that helps. Proceeding to panic.\n");
2576    tty_config.setColor(w, .reset) catch {};
2577}
2578
2579/// In this function the stderr mutex has already been locked.
2580pub fn dumpBadGetPathHelp(
2581    s: *Step,
2582    w: *std.Io.Writer,
2583    tty_config: std.Io.tty.Config,
2584    src_builder: *Build,
2585    asking_step: ?*Step,
2586) anyerror!void {
2587    try w.print(
2588        \\getPath() was called on a GeneratedFile that wasn't built yet.
2589        \\  source package path: {s}
2590        \\  Is there a missing Step dependency on step '{s}'?
2591        \\
2592    , .{
2593        src_builder.build_root.path orelse ".",
2594        s.name,
2595    });
2596
2597    tty_config.setColor(w, .red) catch {};
2598    try w.writeAll("    The step was created by this stack trace:\n");
2599    tty_config.setColor(w, .reset) catch {};
2600
2601    s.dump(w, tty_config);
2602    if (asking_step) |as| {
2603        tty_config.setColor(w, .red) catch {};
2604        try w.print("    The step '{s}' that is missing a dependency on the above step was created by this stack trace:\n", .{as.name});
2605        tty_config.setColor(w, .reset) catch {};
2606
2607        as.dump(w, tty_config);
2608    }
2609    tty_config.setColor(w, .red) catch {};
2610    try w.writeAll("    Hope that helps. Proceeding to panic.\n");
2611    tty_config.setColor(w, .reset) catch {};
2612}
2613
2614pub const InstallDir = union(enum) {
2615    prefix: void,
2616    lib: void,
2617    bin: void,
2618    header: void,
2619    /// A path relative to the prefix
2620    custom: []const u8,
2621
2622    /// Duplicates the install directory including the path if set to custom.
2623    pub fn dupe(dir: InstallDir, builder: *Build) InstallDir {
2624        if (dir == .custom) {
2625            return .{ .custom = builder.dupe(dir.custom) };
2626        } else {
2627            return dir;
2628        }
2629    }
2630};
2631
2632/// This function is intended to be called in the `configure` phase only.
2633/// It returns an absolute directory path, which is potentially going to be a
2634/// source of API breakage in the future, so keep that in mind when using this
2635/// function.
2636pub fn makeTempPath(b: *Build) []const u8 {
2637    const rand_int = std.crypto.random.int(u64);
2638    const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
2639    const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM");
2640    b.cache_root.handle.makePath(tmp_dir_sub_path) catch |err| {
2641        std.debug.print("unable to make tmp path '{s}': {s}\n", .{
2642            result_path, @errorName(err),
2643        });
2644    };
2645    return result_path;
2646}
2647
2648/// A pair of target query and fully resolved target.
2649/// This type is generally required by build system API that need to be given a
2650/// target. The query is kept because the Zig toolchain needs to know which parts
2651/// of the target are "native". This can apply to the CPU, the OS, or even the ABI.
2652pub const ResolvedTarget = struct {
2653    query: Target.Query,
2654    result: Target,
2655};
2656
2657/// Converts a target query into a fully resolved target that can be passed to
2658/// various parts of the API.
2659pub fn resolveTargetQuery(b: *Build, query: Target.Query) ResolvedTarget {
2660    if (query.isNative()) {
2661        // Hot path. This is faster than querying the native CPU and OS again.
2662        return b.graph.host;
2663    }
2664    const io = b.graph.io;
2665    return .{
2666        .query = query,
2667        .result = std.zig.system.resolveTargetQuery(io, query) catch
2668            @panic("unable to resolve target query"),
2669    };
2670}
2671
2672pub fn wantSharedLibSymLinks(target: Target) bool {
2673    return target.os.tag != .windows;
2674}
2675
2676pub const SystemIntegrationOptionConfig = struct {
2677    /// If left as null, then the default will depend on system_package_mode.
2678    default: ?bool = null,
2679};
2680
2681pub fn systemIntegrationOption(
2682    b: *Build,
2683    name: []const u8,
2684    config: SystemIntegrationOptionConfig,
2685) bool {
2686    const gop = b.graph.system_library_options.getOrPut(b.allocator, name) catch @panic("OOM");
2687    if (gop.found_existing) switch (gop.value_ptr.*) {
2688        .user_disabled => {
2689            gop.value_ptr.* = .declared_disabled;
2690            return false;
2691        },
2692        .user_enabled => {
2693            gop.value_ptr.* = .declared_enabled;
2694            return true;
2695        },
2696        .declared_disabled => return false,
2697        .declared_enabled => return true,
2698    } else {
2699        gop.key_ptr.* = b.dupe(name);
2700        if (config.default orelse b.graph.system_package_mode) {
2701            gop.value_ptr.* = .declared_enabled;
2702            return true;
2703        } else {
2704            gop.value_ptr.* = .declared_disabled;
2705            return false;
2706        }
2707    }
2708}
2709
2710test {
2711    _ = Cache;
2712    _ = Step;
2713}