const Build = @This(); const builtin = @import("builtin"); const std = @import("std.zig"); const Io = std.Io; const fs = std.fs; const mem = std.mem; const panic = std.debug.panic; const assert = std.debug.assert; const log = std.log; const Allocator = std.mem.Allocator; const Target = std.Target; const process = std.process; const File = std.Io.File; const Sha256 = std.crypto.hash.sha2.Sha256; const ArrayList = std.ArrayList; const fatal = std.process.fatal; pub const Cache = @import("Build/Cache.zig"); pub const Step = @import("Build/Step.zig"); pub const Module = @import("Build/Module.zig"); pub const abi = @import("Build/abi.zig"); /// The serialized output of configure phase ingested by make phase. pub const Configuration = @import("Build/Configuration.zig"); /// Logic that transforms `Build` into `Configuration`. pub const Serialize = @import("Build/Serialize.zig"); /// Shared state among all Build instances. graph: *Graph, install_tls: Step.TopLevel, uninstall_tls: Step.TopLevel, allocator: Allocator, default_step: *Step, top_level_steps: std.array_hash_map.String(*Step.TopLevel), /// Path to the directory containing build.zig. root: Cache.Path, debug_log_scopes: []const []const u8 = &.{}, /// Number of stack frames captured when a `StackTrace` is recorded for debug purposes, /// in particular at `Step` creation. /// Set to 0 to disable stack collection. debug_stack_frames_count: u8 = 8, user_input_options: PackageOptions.Map, available_options_map: std.array_hash_map.String(AvailableOption) = .empty, invalid_user_input: bool, dep_prefix: []const u8 = "", modules: std.array_hash_map.String(*Module), named_writefiles: std.array_hash_map.String(*Step.WriteFile), named_lazy_paths: std.array_hash_map.String(LazyPath), /// The hash of this instance's package. `""` means that this is the root package. pkg_hash: []const u8, /// A mapping from dependency names to package hashes. available_deps: AvailableDeps, pub const ConfigureDependency = struct { lazy_path: LazyPath, is_directory: bool, metadata_only: bool, }; pub const ReleaseMode = enum { off, any, fast, safe, small, }; /// Shared state among all Build instances. /// Settings that are here rather than in Build are not configurable per-package. pub const Graph = struct { io: Io, /// Process lifetime. arena: Allocator, system_integration_options: std.array_hash_map.String(SystemLibraryMode) = .empty, system_package_mode: bool = false, zig_exe: []const u8, environ_map: process.Environ.Map, /// Managed by: /// * `Build.declareDependency` /// * `Build.dependency` /// * `Build.dependencyLazy` unlazy_deps: std.array_hash_map.Auto(Configuration.String, void) = .empty, /// Information about the native target. Computed before build() is invoked. host: ResolvedTarget, dependency_cache: PackageInstanceMap = .empty, allow_so_scripts: ?bool = null, time_report: bool = false, verbose: bool = false, /// Similar to the `Io.Terminal.Mode` returned by `Io.lockStderr`, but also /// respects the '--color' flag. stderr_mode: ?Io.Terminal.Mode = null, release_mode: ReleaseMode = .off, /// Indexes correspond to `Configuration.GeneratedFileIndex`. generated_files: std.ArrayList(*Step), wip_configuration: Configuration.Wip, cache_poison: CachePoison = .pure, /// Observing this data causes cache poisoning. See `CachePoison`. search_prefixes: std.ArrayList([]const u8) = .empty, /// Populated by calling one of: /// * `dependOnFileContents` /// * `dependOnFileMetadata` /// * `dependOnDirectoryContents` /// * `dependOnDirectoryMetadata` configure_dependencies: ArrayList(ConfigureDependency) = .empty, /// If the cache is poisoned means that the **configure logic** had side /// effects, or otherwise did something that could not be tracked by the /// cache system. /// /// This is not to be confused with whether individual steps may have side /// effects when being evaluated; it has to do with the logic inside build.zig /// itself. For example, a `Run` step that prints "hello world" has side /// effects *at make time* and therefore does not warrant setting this flag, /// while checking for the existence of `scdoc` *at configure time* in order to /// choose the default value for a configuration option does. /// /// Keeping the cache pure will make `zig build` faster, bypassing the /// configurer process when identical configuration would be generated. /// /// When the cache is poisoned, the maker process will delete the build /// configuration file upon ingesting it since it cannot be reused. pub const CachePoison = enum { pure, poisoned, /// Indicates the user would like to see a stack trace if the cache /// would become poisoned. disallowed, /// Indicates the user would like to ignore the cache being poisoned /// and cache anyway, opting into cache hits on stale configuration. ignored, }; pub fn addGeneratedFile(graph: *Graph, owner: *Step) Configuration.GeneratedFileIndex { graph.generated_files.append(graph.arena, owner) catch @panic("OOM"); return @fromBackingInt(@intCast(graph.generated_files.items.len - 1)); } pub fn dupeString(graph: *const Graph, bytes: []const u8) []const u8 { return graph.arena.dupe(u8, bytes) catch @panic("OOM"); } pub fn dupePath(graph: *const Graph, bytes: []const u8) []const u8 { return dupePathInner(graph.arena, bytes); } fn dupePathInner(arena: Allocator, bytes: []const u8) []const u8 { if (builtin.os.tag != .windows) return arena.dupe(u8, bytes) catch @panic("OOM"); const the_copy = arena.dupe(u8, bytes) catch @panic("OOM"); mem.replaceScalar(u8, the_copy, '/', '\\'); return the_copy; } pub fn dupeStrings(graph: *const Graph, strings: []const []const u8) []const []const u8 { const array = graph.alloc([]const u8, strings.len); for (array, strings) |*dest, source| dest.* = dupeString(graph, source); return array; } /// An absolute path or a path relative to the current working directory of /// the build runner process. /// /// Use of this function indicates a dependency on the host system. pub fn cwdRelativePath(graph: *Graph, sub_path: []const u8) LazyPath { return @This().path(graph, .cwd, sub_path); } /// A path whose components and contents are known at some point during /// `Step` resolution, relative to the provided base directory. pub fn path(graph: *Graph, base: Configuration.LazyPath.Relative.Base, sub_path: []const u8) LazyPath { assert(base != .build_root); return .{ .relative = .{ .base = base, .sub_path = @This().dupePath(graph, sub_path), } }; } /// Allocates using the global process arena, failing the build on /// allocation failure. pub fn alloc(graph: *const Graph, comptime T: type, n: usize) []T { return graph.arena.allocAdvancedWithRetAddr(T, null, n, @returnAddress()) catch @panic("OOM"); } /// Allocates using the global process arena, failing the build on /// allocation failure. pub fn create(graph: *const Graph, comptime T: type) *T { return @ptrCast(graph.arena.allocBytesAligned(.of(T), @sizeOf(T), @returnAddress()) catch @panic("OOM")); } pub fn addBytesList(graph: *Graph, bytes_list: []const []const u8) []const Configuration.Bytes { const result = graph.alloc(Configuration.Bytes, bytes_list.len); for (result, bytes_list) |*d, s| d.* = addBytes(graph, s); return result; } pub fn addBytes(graph: *Graph, bytes: []const u8) Configuration.Bytes { const wc = &graph.wip_configuration; return wc.addBytes(bytes) catch @panic("OOM"); } pub fn addString(graph: *Graph, bytes: []const u8) Configuration.String { const wc = &graph.wip_configuration; return wc.addString(bytes) catch @panic("OOM"); } /// Indicates that the **configure logic** had side effects, or otherwise /// did something that could not be tracked by the cache system. /// /// See `CachePoison` documentation for more details. /// /// As an alternative to calling this function, consider these APIs instead: /// * `dependOnFileContents` pub fn poisonCache(graph: *Graph) void { switch (graph.cache_poison) { .pure => graph.cache_poison = .poisoned, .poisoned => return, .disallowed => @panic("cache poisoned"), .ignored => log.warn("ignoring cache poisoning", .{}), } } }; const AvailableDeps = []const struct { []const u8, []const u8 }; pub const SystemLibraryMode = enum { /// User asked for the library to be disabled. /// The build runner has not confirmed whether the setting is recognized yet. user_disabled, /// User asked for the library to be enabled. /// The build runner has not confirmed whether the setting is recognized yet. user_enabled, /// The build runner has confirmed that this setting is recognized. /// System integration with this library has been resolved to off. declared_disabled, /// The build runner has confirmed that this setting is recognized. /// System integration with this library has been resolved to on. declared_enabled, }; const PackageInstanceMap = std.array_hash_map.Custom(PackageInstanceKey, *Dependency, struct { pub fn hash(_: @This(), k: PackageInstanceKey) u32 { var hasher = std.hash.Wyhash.init(0); hasher.update(k.pkg_hash); for (k.options.keys(), k.options.values()) |option_key, option_value| { hasher.update(option_key); option_value.hash(&hasher); } return @truncate(hasher.final()); } pub fn eql(_: @This(), a: PackageInstanceKey, b: PackageInstanceKey, _: usize) bool { if (!mem.eql(u8, a.pkg_hash, b.pkg_hash)) return false; if (a.options.count() != b.options.count()) return false; for ( a.options.keys(), b.options.keys(), a.options.values(), b.options.values(), ) |a_key, b_key, a_val, b_val| { if (!mem.eql(u8, a_key, b_key)) return false; if (!a_val.eql(b_val)) return false; } return true; } }, true); const PackageInstanceKey = struct { pkg_hash: []const u8, options: *const PackageOptions.Map, }; /// Build system implementation details. pub const PackageOptions = struct { pub const Map = std.array_hash_map.String(UserProvided); pub const UserProvided = union(enum) { flag: void, scalar: []const u8, list: std.ArrayList([]const u8), map: std.array_hash_map.String(*const UserProvided), lazy_path: LazyPath, lazy_path_list: std.ArrayList(LazyPath), fn eql(a: UserProvided, b: UserProvided) bool { if (std.meta.activeTag(a) != b) return false; return switch (a) { .flag => true, .scalar => |a_scalar| return mem.eql(u8, a_scalar, b.scalar), .list => |a_list| { if (a_list.items.len != b.list.items.len) return false; for (a_list.items, b.list.items) |a_elem, b_elem| { if (!mem.eql(u8, a_elem, b_elem)) return false; } return true; }, .map => |a_map| { if (a_map.count() != b.map.count()) return false; for (a_map.keys(), a_map.values(), b.map.keys(), b.map.values()) |a_key, a_val, b_key, b_val| { if (!mem.eql(u8, a_key, b_key)) return false; if (!a_val.eql(b_val.*)) return false; } return true; }, .lazy_path => |a_lazy_path| return a_lazy_path.eql(b.lazy_path), .lazy_path_list => |a_lazy_path_list| { if (a_lazy_path_list.items.len != b.lazy_path_list.items.len) return false; for (a_lazy_path_list.items, b.lazy_path_list.items) |a_lp, b_lp| { if (!a_lp.eql(b_lp)) return false; } return true; }, }; } fn hash(a: UserProvided, hasher: *std.hash.Wyhash) void { hasher.update(&mem.toBytes(std.meta.activeTag(a))); switch (a) { .flag => {}, .scalar => |scalar| hasher.update(scalar), .list => |*list| for (list.items) |elem| hasher.update(elem), .map => |*map| for (map.keys(), map.values()) |key, val| { hasher.update(key); val.hash(hasher); }, .lazy_path => |lp| lp.hash(hasher), .lazy_path_list => |*list| for (list.items) |lp| lp.hash(hasher), } } }; fn fromArgs(arena: Allocator, map: *PackageOptions.Map, args: anytype) void { const args_info = @typeInfo(@TypeOf(args)).@"struct"; inline for (args_info.field_names, args_info.field_types) |field_name, field_type| { if (field_type == @TypeOf(null)) continue; addPackageOptionFromArg(arena, map, field_name, field_type, @field(args, field_name)); } } pub fn sort(map: *Map) void { map.sortUnstable(@as(struct { keys: []const []const u8, pub fn lessThan(this: @This(), a_index: usize, b_index: usize) bool { return mem.lessThan(u8, this.keys[a_index], this.keys[b_index]); } }, .{ .keys = map.keys() })); } }; const AvailableOption = struct { type_id: Configuration.AvailableOption.Type, description: []const u8, /// If the `type_id` is `enum` or `enum_list` this provides the list of enum options enum_options: ?[]const []const u8, }; /// Build system implementation detail. pub fn create( graph: *Graph, root: Cache.Path, available_deps: AvailableDeps, ) error{OutOfMemory}!*Build { const arena = graph.arena; const b = try arena.create(Build); b.* = .{ .graph = graph, .root = root, .invalid_user_input = false, .allocator = arena, .user_input_options = .empty, .top_level_steps = .{}, .default_step = undefined, .install_tls = .{ .step = .init(.{ .tag = .top_level, .name = "install", .owner = b, }), .description = "Copy build artifacts to prefix path", }, .uninstall_tls = .{ .step = .init(.{ .tag = .top_level, .name = "uninstall", .owner = b, }), .description = "Remove build artifacts from prefix path", }, .modules = .empty, .named_writefiles = .empty, .named_lazy_paths = .empty, .pkg_hash = "", .available_deps = available_deps, }; try b.top_level_steps.put(arena, b.install_tls.step.name, &b.install_tls); try b.top_level_steps.put(arena, b.uninstall_tls.step.name, &b.uninstall_tls); b.default_step = &b.install_tls.step; return b; } fn createChild( parent: *Build, dep_name: []const u8, root: Cache.Path, pkg_hash: []const u8, pkg_deps: AvailableDeps, user_input_options: PackageOptions.Map, ) error{OutOfMemory}!*Build { const arena = parent.graph.arena; const child = try arena.create(Build); child.* = .{ .graph = parent.graph, .root = root, .allocator = arena, .install_tls = .{ .step = .init(.{ .tag = .top_level, .name = "install", .owner = child, }), .description = "Copy build artifacts to prefix path", }, .uninstall_tls = .{ .step = .init(.{ .tag = .top_level, .name = "uninstall", .owner = child, }), .description = "Remove build artifacts from prefix path", }, .user_input_options = user_input_options, .invalid_user_input = false, .default_step = undefined, .top_level_steps = .{}, .debug_log_scopes = parent.debug_log_scopes, .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }), .modules = .empty, .named_writefiles = .empty, .named_lazy_paths = .empty, .pkg_hash = pkg_hash, .available_deps = pkg_deps, }; try child.top_level_steps.put(arena, child.install_tls.step.name, &child.install_tls); try child.top_level_steps.put(arena, child.uninstall_tls.step.name, &child.uninstall_tls); child.default_step = &child.install_tls.step; return child; } fn addPackageOptionFromArg( arena: Allocator, map: *PackageOptions.Map, field_name: [:0]const u8, comptime T: type, /// If null, the value won't be added, but `T` will still be type-checked. maybe_value: ?T, ) void { map.ensureUnusedCapacity(arena, 2) catch @panic("OOM"); switch (T) { Target.Query => return if (maybe_value) |v| { map.putAssumeCapacity(field_name, .{ .scalar = v.zigTriple(arena) catch @panic("OOM") }); map.putAssumeCapacity("cpu", .{ .scalar = v.serializeCpuAlloc(arena) catch @panic("OOM") }); }, ResolvedTarget => return if (maybe_value) |v| { map.putAssumeCapacity(field_name, .{ .scalar = v.query.zigTriple(arena) catch @panic("OOM") }); map.putAssumeCapacity("cpu", .{ .scalar = v.query.serializeCpuAlloc(arena) catch @panic("OOM") }); }, std.zig.BuildId => return if (maybe_value) |v| { map.putAssumeCapacity(field_name, .{ .scalar = std.fmt.allocPrint(arena, "{f}", .{v}) catch @panic("OOM"), }); }, LazyPath => return if (maybe_value) |v| { map.putAssumeCapacity(field_name, .{ .lazy_path = v.dupeInner(arena) }); }, []const LazyPath => return if (maybe_value) |v| { var list: std.ArrayList(LazyPath) = .empty; const elems = list.addManyAsSlice(arena, v.len) catch @panic("OOM"); for (v, elems) |lp, *elem| elem.* = lp.dupeInner(arena); map.putAssumeCapacity(field_name, .{ .lazy_path_list = list }); }, []const u8 => return if (maybe_value) |v| { map.putAssumeCapacity(field_name, .{ .scalar = arena.dupe(u8, v) catch @panic("OOM") }); }, []const []const u8 => return if (maybe_value) |v| { var list: std.ArrayList([]const u8) = .empty; const elems = list.addManyAsSlice(arena, v.len) catch @panic("OOM"); for (v, elems) |s, *elem| elem.* = arena.dupe(u8, s) catch @panic("OOM"); map.putAssumeCapacity(field_name, .{ .list = list }); }, else => switch (@typeInfo(T)) { .bool => return if (maybe_value) |v| { map.putAssumeCapacity(field_name, .{ .scalar = if (v) "true" else "false" }); }, .@"enum", .enum_literal => return if (maybe_value) |v| { map.putAssumeCapacity(field_name, .{ .scalar = @tagName(v) }); }, .comptime_int, .int => return if (maybe_value) |v| { map.putAssumeCapacity(field_name, .{ .scalar = std.fmt.allocPrint(arena, "{d}", .{v}) catch @panic("OOM"), }); }, .comptime_float, .float => return if (maybe_value) |v| { map.putAssumeCapacity(field_name, .{ .scalar = std.fmt.allocPrint(arena, "{x}", .{v}) catch @panic("OOM"), }); }, .pointer => |ptr_info| switch (ptr_info.size) { .one => switch (@typeInfo(ptr_info.child)) { .array => |array_info| return addPackageOptionFromArg( arena, map, field_name, @Pointer(.slice, .{ .@"const" = true }, array_info.child, null), maybe_value orelse null, ), else => {}, }, .slice => switch (@typeInfo(ptr_info.child)) { .@"enum" => return if (maybe_value) |v| { var list: std.ArrayList([]const u8) = .empty; const elems = list.addManyAsSlice(arena, v.len) catch @panic("OOM"); for (elems, v) |*elem, tag| elem.* = @tagName(tag); map.putAssumeCapacity(field_name, .{ .list = list }); }, else => return addPackageOptionFromArg( arena, map, field_name, @Pointer(ptr_info.size, .{ .@"const" = true }, ptr_info.child, null), maybe_value orelse null, ), }, else => {}, }, .null => unreachable, .optional => |info| switch (@typeInfo(info.child)) { .optional => {}, else => return addPackageOptionFromArg(arena, map, field_name, info.child, maybe_value orelse null), }, else => {}, }, } @compileError("option '" ++ field_name ++ "' has unsupported type: " ++ @typeName(T)); } /// Create a set of key-value pairs that can be converted into a Zig source /// file and then inserted into a Zig compilation's module table for importing. /// /// This provides a way to expose build.zig values to Zig source code with /// `@import`. Related: `Module.addOptions`. pub fn addOptions(b: *Build) *Step.Options { return Step.Options.create(b); } pub const ExecutableOptions = struct { name: []const u8, root_module: *Module, version: ?std.SemanticVersion = null, linkage: ?std.builtin.LinkMode = null, max_rss: u64 = 0, use_llvm: ?bool = null, use_lld: ?bool = null, zig_lib_dir: ?LazyPath = null, /// Deprecated. This functionality will be moved to an external package: /// https://codeberg.org/ziglang/rc /// /// Embed a `.manifest` file in the compilation if the object format supports it. /// https://learn.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference /// Manifest files must have the extension `.manifest`. /// Can be set regardless of target. The `.manifest` file will be ignored /// if the target object format does not support embedded manifests. win32_manifest: ?LazyPath = null, }; pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile { return .create(b, .{ .name = options.name, .root_module = options.root_module, .version = options.version, .kind = .exe, .linkage = options.linkage, .max_rss = options.max_rss, .use_llvm = options.use_llvm, .use_lld = options.use_lld, .zig_lib_dir = options.zig_lib_dir, .win32_manifest = options.win32_manifest, }); } pub const ObjectOptions = struct { name: []const u8, root_module: *Module, max_rss: u64 = 0, use_llvm: ?bool = null, use_lld: ?bool = null, zig_lib_dir: ?LazyPath = null, }; pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile { return .create(b, .{ .name = options.name, .root_module = options.root_module, .kind = .obj, .max_rss = options.max_rss, .use_llvm = options.use_llvm, .use_lld = options.use_lld, .zig_lib_dir = options.zig_lib_dir, }); } pub const LibraryOptions = struct { linkage: std.builtin.LinkMode = .static, name: []const u8, root_module: *Module, version: ?std.SemanticVersion = null, max_rss: u64 = 0, use_llvm: ?bool = null, use_lld: ?bool = null, zig_lib_dir: ?LazyPath = null, /// Deprecated. This functionality will be moved to an external package: /// https://codeberg.org/ziglang/rc /// /// Embed a `.manifest` file in the compilation if the object format supports it. /// https://learn.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference /// Manifest files must have the extension `.manifest`. /// Can be set regardless of target. The `.manifest` file will be ignored /// if the target object format does not support embedded manifests. win32_manifest: ?LazyPath = null, /// Win32 module definition file (.def). win32_module_definition: ?LazyPath = null, }; pub fn addLibrary(b: *Build, options: LibraryOptions) *Step.Compile { return .create(b, .{ .name = options.name, .root_module = options.root_module, .kind = .lib, .linkage = options.linkage, .version = options.version, .max_rss = options.max_rss, .use_llvm = options.use_llvm, .use_lld = options.use_lld, .zig_lib_dir = options.zig_lib_dir, .win32_manifest = options.win32_manifest, .win32_module_definition = options.win32_module_definition, }); } pub const TestOptions = struct { name: []const u8 = "test", root_module: *Module, max_rss: u64 = 0, filters: []const []const u8 = &.{}, test_runner: ?Step.Compile.TestRunner = null, use_llvm: ?bool = null, use_lld: ?bool = null, zig_lib_dir: ?LazyPath = null, /// Emits an object file instead of a test binary. /// The object must be linked separately. /// Usually used in conjunction with a custom `test_runner`. emit_object: bool = false, }; /// Creates an executable containing unit tests. /// /// Equivalent to running the command `zig test --test-no-exec ...`. /// /// **This step does not run the unit tests**. Typically, the result of this /// function will be passed to `addRunArtifact`, creating a `Step.Run`. These /// two steps are separated because they are independently configured and /// cached. pub fn addTest(b: *Build, options: TestOptions) *Step.Compile { return .create(b, .{ .name = options.name, .kind = if (options.emit_object) .test_obj else .@"test", .root_module = options.root_module, .max_rss = options.max_rss, .filters = b.graph.dupeStrings(options.filters), .test_runner = options.test_runner, .use_llvm = options.use_llvm, .use_lld = options.use_lld, .zig_lib_dir = options.zig_lib_dir, }); } pub const AssemblyOptions = struct { name: []const u8, source_file: LazyPath, /// To choose the same computer as the one building the package, pass the /// `host` field of the package's `Build` instance. target: ResolvedTarget, optimize: std.builtin.Optimize, max_rss: u64 = 0, zig_lib_dir: ?LazyPath = null, }; /// This function creates a module and adds it to the package's module set, making /// it available to other packages which depend on this one. /// `createModule` can be used instead to create a private module. pub fn addModule(b: *Build, name: []const u8, options: Module.CreateOptions) *Module { const graph = b.graph; const arena = graph.arena; const module = Module.create(b, options); const gop = b.modules.getOrPutValue( arena, graph.dupeString(name), module, ) catch @panic("OOM"); if (gop.found_existing) { panic("A module with the name {q} has already been added to the package. Consider creating a private module with std.Build.createModule", .{name}); } return module; } /// This function creates a private module, to be used by the current package, /// but not exposed to other packages depending on this one. /// `addModule` can be used instead to create a public module. pub fn createModule(b: *Build, options: Module.CreateOptions) *Module { return Module.create(b, options); } /// Creates a step that executes a process on the host system. /// /// `argv` is one or more command line arguments passed to the executed /// process. The first element is the name of the executable to run. More /// command line arguments can be added with methods of `Step.Run`, such as: /// * `Step.Run.addArgs` /// * `Step.Run.addArtifactArg` /// * `Step.Run.addFileArg` /// * `Step.Run.addOutputFileArg` /// /// This function introduces a system dependency, compromising reproducibility /// and making it more difficult to set up one's computer in order to build the /// project from source. /// /// See also: /// * `addRunArtifact` /// * `addRunFile` pub fn addSystemCommand(b: *Build, argv: []const []const u8) *Step.Run { assert(argv.len >= 1); const run_step = Step.Run.create(b, b.fmt("run {s}", .{argv[0]})); run_step.addArgs(argv); return run_step; } /// Creates a `Step.Run` with an executable built with `addExecutable`. /// Add command line arguments with methods of `Step.Run`. /// /// It doesn't have to target the host. In some cases cross-compiled binaries /// can even be executed. /// /// This is declarative; it constructs a build step that may or may not be run /// depending on the options provided by the user to the build command. /// /// See also: /// * `addSystemCommand` /// * `addRunFile` pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run { // Avoid the common case of the step name looking like "run test test". const step_name = if (exe.kind.isTest() and mem.eql(u8, exe.name, "test")) b.fmt("run {t}", .{exe.kind}) else b.fmt("run {t} {s}", .{ exe.kind, exe.name }); const run_step = Step.Run.create(b, step_name); run_step.producer = exe; run_step.addArtifactArg(exe); if (exe.kind == .@"test") { const test_server_mode: bool = s: { if (exe.test_runner) |r| break :s r.mode == .server; if (exe.use_llvm == false) { // The default test runner does not use the server protocol if the selected backend // is too immature to support it. Keep this logic in sync with `need_simple` in the // default test runner implementation. switch (exe.rootModuleTarget().cpu.arch) { // stage2_aarch64 .aarch64, .aarch64_be, // stage2_powerpc .powerpc, .powerpcle, .powerpc64, .powerpc64le, // stage2_riscv64 .riscv64, => break :s false, else => {}, } } break :s true; }; if (test_server_mode) { run_step.enableTestRunnerMode(); } else if (exe.test_runner == null) { // If a test runner does not use the `std.zig.Server` protocol, it can instead // communicate failure via its exit code. run_step.expectExitCode(0); } } return run_step; } /// Creates a step that executes the provided file. /// /// Add more command line arguments via methods of `Step.Run`. /// /// See also: /// * `addSystemCommand` /// * `addRunArtifact` pub fn addRunFile(b: *Build, executable: LazyPath) *Step.Run { const run_step = Step.Run.create(b, b.fmt("run {f}", .{executable})); run_step.addFileArg(executable); return run_step; } /// Using the `values` provided, produces a C header file, possibly based on a /// template input file (e.g. config.h.in). /// When an input template file is provided, this function will fail the build /// when an option not found in the input file is provided in `values`, and /// when an option found in the input file is missing from `values`. pub fn addConfigHeader( b: *Build, options: Step.ConfigHeader.Options, values: anytype, ) *Step.ConfigHeader { var options_copy = options; if (options_copy.first_ret_addr == null) options_copy.first_ret_addr = @returnAddress(); const config_header_step = Step.ConfigHeader.create(b, options_copy); config_header_step.addValues(values); return config_header_step; } /// Deprecated, call `Graph.dupeString` instead. pub fn dupe(b: *Build, bytes: []const u8) []const u8 { return b.graph.dupeString(bytes); } /// Deprecated, call `Graph.dupeStrings` instead. pub fn dupeStrings(b: *Build, strings: []const []const u8) []const []const u8 { return b.graph.dupeStrings(strings); } /// Deprecated, call `Graph.dupePath` instead. pub fn dupePath(b: *Build, bytes: []const u8) []const u8 { return b.graph.dupePath(bytes); } pub fn addWriteFile(b: *Build, file_path: []const u8, data: []const u8) *Step.WriteFile { const write_file_step = b.addWriteFiles(); _ = write_file_step.add(file_path, data); return write_file_step; } pub fn addNamedWriteFiles(b: *Build, name: []const u8) *Step.WriteFile { const graph = b.graph; const wf = Step.WriteFile.create(b); const gop = b.named_writefiles.getOrPutValue( graph.arena, graph.dupeString(name), wf, ) catch @panic("OOM"); if (gop.found_existing) { panic( "A WriteFile step with the name {q} has already been added to the package. Consider creating a private WriteFile step with std.Build.addWriteFiles", .{name}, ); } return wf; } pub fn addNamedLazyPath(b: *Build, name: []const u8, lp: LazyPath) void { const graph = b.graph; const gop = b.named_lazy_paths.getOrPutValue( graph.arena, graph.dupeString(name), lp.dupe(graph), ) catch @panic("OOM"); if (gop.found_existing) { panic("A LazyPath with the name {q} has already been added to the package.", .{name}); } } /// Creates a step for mutating files inside a temporary directory created lazily /// and automatically cleaned up upon successful build. /// /// The directory will be placed inside "tmp" rather than "o", and caching will /// be skipped. During the `make` phase, the step will always do all the file /// system operations, and on successful build completion, the dir will be /// deleted along with all other tmp directories. The directory is therefore /// eligible to be used for mutations by other steps. /// /// See also: /// * `addWriteFiles` /// * `addMutateFiles` pub fn addTempFiles(b: *Build) *Step.WriteFile { const wf = addWriteFiles(b); wf.mode = .tmp; return wf; } /// Creates a step for mutating temporary directories created with `addTempFiles`. /// /// Consider instead `addWriteFiles` which is for creating a cached directory /// of files to operate on. /// /// This should only be used with a `tmp_path` obtained via `addTempFiles` or /// `tmpPath`. pub fn addMutateFiles(b: *Build, tmp_path: LazyPath) *Step.WriteFile { const wf = addWriteFiles(b); wf.mode = .{ .mutate = tmp_path }; tmp_path.addStepDependencies(&wf.step); return wf; } pub fn addWriteFiles(b: *Build) *Step.WriteFile { return Step.WriteFile.create(b); } /// Creates a step for writing data to paths relative to the build root, /// mutating the project's source files. /// /// This build step was designed not to be used during the normal build /// process, but rather as a utility run by a developer with intention to /// update source files, which will then be committed to version control. /// /// Example use cases: /// * precompiling assets which are tracked by version control /// * snapshot testing pub fn addUpdateSourceFiles(b: *Build) *Step.UpdateSourceFiles { return Step.UpdateSourceFiles.create(b); } pub fn addFail(b: *Build, error_msg: []const u8) *Step.Fail { return Step.Fail.create(b, error_msg); } pub fn addFmt(b: *Build, options: Step.Fmt.Options) *Step.Fmt { return Step.Fmt.create(b, options); } pub fn addTranslateC(b: *Build, options: Step.TranslateC.Options) *Step.TranslateC { return Step.TranslateC.create(b, options); } pub fn getInstallStep(b: *Build) *Step { return &b.install_tls.step; } pub fn getUninstallStep(b: *Build) *Step { return &b.uninstall_tls.step; } /// Creates a configuration option to be passed to the build.zig script. /// When a user directly runs `zig build`, they can set these options with `-D` arguments. /// When a project depends on a Zig package as a dependency, it programmatically sets /// these options when calling the dependency's build.zig script as a function. /// `null` is returned when an option is left to default. pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T { const graph = b.graph; const arena = graph.arena; const name = graph.dupeString(name_raw); const description = graph.dupeString(description_raw); const type_id = comptime typeToEnum(T); const available_option: AvailableOption = .{ .type_id = type_id, .description = description, .enum_options = if (type_id == .@"enum" or type_id == .enum_list) blk: { const E = if (type_id == .enum_list) @typeInfo(T).pointer.child else T; break :blk @typeInfo(E).@"enum".field_names; } else null, }; if ((b.available_options_map.fetchPut(arena, name, available_option) catch @panic("OOM")) != null) { panic("option {q} declared twice", .{name}); } const user_provided = b.user_input_options.get(name) orelse return null; switch (type_id) { .bool => switch (user_provided) { .flag => return true, .scalar => |s| { if (mem.eql(u8, s, "true")) { return true; } else if (mem.eql(u8, s, "false")) { return false; } else { log.err("expected -D{s} to be a boolean; received: {s}", .{ name, s }); b.markInvalidUserInput(); return null; } }, .list, .map, .lazy_path, .lazy_path_list => { log.err("expected -D{s} to be a boolean; received: {t}", .{ name, user_provided }); b.markInvalidUserInput(); return null; }, }, .int => switch (user_provided) { .flag, .list, .map, .lazy_path, .lazy_path_list => { log.err("expected -D{s} to be an integer; received: {t}", .{ name, user_provided }); b.markInvalidUserInput(); return null; }, .scalar => |s| { const n = std.fmt.parseInt(T, s, 10) catch |err| switch (err) { error.Overflow => { log.err("-D{s} value {s} cannot fit into type {s}", .{ name, s, @typeName(T) }); b.markInvalidUserInput(); return null; }, else => { log.err("expected -D{s} to be an integer of type {s}", .{ name, @typeName(T) }); b.markInvalidUserInput(); return null; }, }; return n; }, }, .float => switch (user_provided) { .flag, .map, .list, .lazy_path, .lazy_path_list => { log.err("expected -D{s} to be a float; received: {t}", .{ name, user_provided }); b.markInvalidUserInput(); return null; }, .scalar => |s| { const n = std.fmt.parseFloat(T, s) catch { log.err("expected -D{s} to be a float of type {s}", .{ name, @typeName(T) }); b.markInvalidUserInput(); return null; }; return n; }, }, .@"enum" => switch (user_provided) { .flag, .map, .list, .lazy_path, .lazy_path_list => { log.err("expected -D{s} to be an enum; received: {t}.", .{ name, user_provided }); b.markInvalidUserInput(); return null; }, .scalar => |s| { if (T == std.lang.Optimize) { if (std.lang.Optimize.fromString(s)) |tag| { return tag; } } else if (std.meta.stringToEnum(T, s)) |tag| { return tag; } log.err("expected -D{s} to be of type {q}", .{ name, @typeName(T) }); b.markInvalidUserInput(); return null; }, }, .string => switch (user_provided) { .flag, .list, .map, .lazy_path, .lazy_path_list => { log.err("expected -D{s} to be a string; received: {t}", .{ name, user_provided }); b.markInvalidUserInput(); return null; }, .scalar => |s| return s, }, .build_id => switch (user_provided) { .flag, .map, .list, .lazy_path, .lazy_path_list => { log.err("expected -D{s} to be an enum; received: {t}.", .{ name, user_provided }); b.markInvalidUserInput(); return null; }, .scalar => |s| { if (std.zig.BuildId.parse(s)) |build_id| { return build_id; } else |err| { log.err("failed to parse option -D{s}: {t}", .{ name, err }); b.markInvalidUserInput(); return null; } }, }, .list => switch (user_provided) { .flag, .map, .lazy_path, .lazy_path_list => { log.err("expected -D{s} to be a list; received: {t}", .{ name, user_provided }); b.markInvalidUserInput(); return null; }, .scalar => |s| { return arena.dupe([]const u8, &[_][]const u8{s}) catch @panic("OOM"); }, .list => |lst| return lst.items, }, .enum_list => switch (user_provided) { .flag, .map, .lazy_path, .lazy_path_list => { log.err("expected -D{s} to be a list; received: {t}", .{ name, user_provided }); b.markInvalidUserInput(); return null; }, .scalar => |s| { const Child = @typeInfo(T).pointer.child; if (Child == std.lang.Optimize) { if (std.lang.Optimize.fromString(s)) |tag| { return arena.dupe(Child, &.{tag}) catch @panic("OOM"); } } else { if (std.meta.stringToEnum(Child, s)) |tag| { return arena.dupe(Child, &.{tag}) catch @panic("OOM"); } } log.err("expected -D{s} to be of type {q}", .{ name, @typeName(Child) }); b.markInvalidUserInput(); return null; }, .list => |lst| { const Child = @typeInfo(T).pointer.child; const new_list = graph.alloc(Child, lst.items.len); for (new_list, lst.items) |*new_item, str| { if (Child == std.lang.Optimize) { if (std.lang.Optimize.fromString(str)) |tag| { new_item.* = tag; continue; } } if (std.meta.stringToEnum(Child, str)) |tag| { new_item.* = tag; continue; } log.err("expected -D{s} to be of type {q}", .{ name, @typeName(Child) }); b.markInvalidUserInput(); return null; } return new_list; }, }, .lazy_path => switch (user_provided) { .scalar => |s| return .{ .cwd_relative = s }, .lazy_path => |lp| return lp, .flag, .map, .list, .lazy_path_list => { log.err("expected -D{s} to be a path; received: {t}", .{ name, user_provided }); b.markInvalidUserInput(); return null; }, }, .lazy_path_list => switch (user_provided) { .scalar => |s| return arena.dupe(LazyPath, &[_]LazyPath{.{ .cwd_relative = s }}) catch @panic("OOM"), .lazy_path => |lp| return arena.dupe(LazyPath, &[_]LazyPath{lp}) catch @panic("OOM"), .list => |lst| { const new_list = graph.alloc(LazyPath, lst.items.len); for (new_list, lst.items) |*new_item, str| { new_item.* = .{ .cwd_relative = str }; } return new_list; }, .lazy_path_list => |lp_list| return lp_list.items, .flag, .map => { log.err("expected -D{s} to be a path; received: {t}", .{ name, user_provided }); b.markInvalidUserInput(); return null; }, }, } } /// Creates a top-level build step, exposed to the CLI user and advertised in /// the "--help" menu. pub fn step(b: *Build, name: []const u8, description: []const u8) *Step { const graph = b.graph; const arena = graph.arena; const step_info = arena.create(Step.TopLevel) catch @panic("OOM"); step_info.* = .{ .step = .init(.{ .tag = .top_level, .name = name, .owner = b, }), .description = graph.dupeString(description), }; const gop = b.top_level_steps.getOrPut(arena, name) catch @panic("OOM"); if (gop.found_existing) panic("A top-level step with name \"{s}\" already exists", .{name}); gop.key_ptr.* = step_info.step.name; gop.value_ptr.* = step_info; return &step_info.step; } pub const StandardOptimizeOptionOptions = struct { preferred_optimize_mode: ?std.builtin.Optimize = null, }; pub fn standardOptimizeOption(b: *Build, options: StandardOptimizeOptionOptions) std.builtin.Optimize { const graph = b.graph; if (options.preferred_optimize_mode) |mode| { if (b.option(bool, "release", "optimize for end users") orelse (graph.release_mode != .off)) { return mode; } else { return .debug; } } if (b.option( std.builtin.Optimize, "optimize", "Prioritize performance, safety, or binary size", )) |mode| { return mode; } return switch (graph.release_mode) { .off => .debug, .any => { std.debug.print("the project does not declare a preferred optimization mode. choose: --release=fast, --release=safe, or --release=small\n", .{}); process.exit(1); }, .fast => .fast, .safe => .safe, .small => .small, }; } pub const StandardTargetOptionsArgs = struct { whitelist: ?[]const Target.Query = null, default_target: Target.Query = .{}, }; /// Exposes standard `zig build` options for choosing a target and additionally /// resolves the target query. pub fn standardTargetOptions(b: *Build, args: StandardTargetOptionsArgs) ResolvedTarget { const query = b.standardTargetOptionsQueryOnly(args); return b.resolveTargetQuery(query); } /// Obtain a target query from a string, reporting diagnostics to stderr if the /// parsing failed. /// Asserts that the `diagnostics` field of `options` is `null`. This use case /// is handled instead by calling `std.Target.Query.parse` directly. pub fn parseTargetQuery(options: std.Target.Query.ParseOptions) error{ParseFailed}!std.Target.Query { assert(options.diagnostics == null); var diags: Target.Query.ParseOptions.Diagnostics = .{}; var opts_copy = options; opts_copy.diagnostics = &diags; return std.Target.Query.parse(opts_copy) catch |err| switch (err) { error.UnknownCpuModel => { std.debug.print("unknown CPU: {q}\navailable CPUs for architecture {t}:\n", .{ diags.cpu_name.?, diags.arch.?, }); for (diags.arch.?.allCpuModels()) |cpu| { std.debug.print(" {s}\n", .{cpu.name}); } return error.ParseFailed; }, error.UnknownCpuFeature => { std.debug.print( \\unknown CPU feature: {q} \\available CPU features for architecture '{t}': \\ , .{ diags.unknown_feature_name.?, diags.arch.?, }); for (diags.arch.?.allFeaturesList()) |feature| { std.debug.print(" {s}: {s}\n", .{ feature.name, feature.description }); } return error.ParseFailed; }, error.UnknownOperatingSystem => { std.debug.print( \\unknown OS: {q} \\available operating systems: \\ , .{diags.os_name.?}); inline for (@typeInfo(Target.Os.Tag).@"enum".field_names) |field_name| { std.debug.print(" {s}\n", .{field_name}); } return error.ParseFailed; }, else => |e| { std.debug.print("unable to parse target {q}: {t}\n", .{ options.arch_os_abi, e }); return error.ParseFailed; }, }; } /// Exposes standard `zig build` options for choosing a target. pub fn standardTargetOptionsQueryOnly(b: *Build, args: StandardTargetOptionsArgs) Target.Query { const graph = b.graph; const arena = graph.arena; const maybe_triple = b.option( []const u8, "target", "The CPU architecture, OS, and ABI to build for", ); const mcpu = b.option( []const u8, "cpu", "Target CPU features to add or subtract", ); const ofmt = b.option( []const u8, "ofmt", "Target object format", ); const dynamic_linker = b.option( []const u8, "dynamic-linker", "Path to interpreter on the target system", ); if (maybe_triple == null and mcpu == null and ofmt == null and dynamic_linker == null) return args.default_target; const triple = maybe_triple orelse "native"; const selected_target = parseTargetQuery(.{ .arch_os_abi = triple, .cpu_features = mcpu, .object_format = ofmt, .dynamic_linker = dynamic_linker, }) catch |err| switch (err) { error.ParseFailed => { b.markInvalidUserInput(); return args.default_target; }, }; const whitelist = args.whitelist orelse return selected_target; // Make sure it's a match of one of the list. for (whitelist) |q| { if (q.eql(selected_target)) return selected_target; } for (whitelist) |q| { log.info("allowed target: -Dtarget={s} -Dcpu={s}", .{ q.zigTriple(arena) catch @panic("OOM"), q.serializeCpuAlloc(arena) catch @panic("OOM"), }); } log.err("chosen target {q} does not match one of the allowed targets", .{ selected_target.zigTriple(arena) catch @panic("OOM"), }); b.markInvalidUserInput(); return args.default_target; } /// Build system implementation detail. pub fn addUserInputOption(b: *Build, name: []const u8, value_raw: []const u8) error{OutOfMemory}!bool { const graph = b.graph; const arena = graph.arena; const value = graph.dupeString(value_raw); const gop = try b.user_input_options.getOrPut(arena, name); if (!gop.found_existing) { gop.key_ptr.* = graph.dupeString(name); gop.value_ptr.* = .{ .scalar = value }; return false; } // Option already exists. switch (gop.value_ptr.*) { .scalar => |s| { // Turn it into a list. var list: std.ArrayList([]const u8) = .empty; (try list.addManyAsArray(arena, 2)).* = .{ s, value }; gop.value_ptr.* = .{ .list = list }; }, .list => |*list| try list.append(arena, value), .flag => { log.err("option -D{s}={s} conflicts with flag -D{s}", .{ name, value, name }); return true; }, .map => |*map| { _ = map; unreachable; // TODO implement maps as command line arguments }, .lazy_path => unreachable, .lazy_path_list => unreachable, } return false; } /// Build system implementation detail. pub fn addUserInputFlag(b: *Build, name: []const u8) error{OutOfMemory}!bool { const graph = b.graph; const arena = graph.arena; const gop = try b.user_input_options.getOrPut(arena, name); if (!gop.found_existing) { gop.key_ptr.* = graph.dupeString(name); gop.value_ptr.* = .{ .flag = {} }; return false; } // Option already exists. switch (gop.value_ptr.*) { .scalar => |s| { log.err("flag -D{s} conflicts with option -D{s}={s}", .{ name, name, s }); return true; }, .list, .map, .lazy_path_list => { log.err("flag -D{s} conflicts with multiple options of the same name", .{name}); return true; }, .lazy_path => |lp| { log.err("flag -D{s} conflicts with option -D{s}={f}", .{ name, name, lp }); return true; }, .flag => {}, } return false; } fn typeToEnum(comptime T: type) Configuration.AvailableOption.Type { return switch (T) { std.zig.BuildId => .build_id, LazyPath => .lazy_path, else => return switch (@typeInfo(T)) { .int => .int, .float => .float, .bool => .bool, .@"enum" => .@"enum", .pointer => |pointer| switch (pointer.child) { u8 => .string, []const u8 => .list, LazyPath => .lazy_path_list, else => switch (@typeInfo(pointer.child)) { .@"enum" => .enum_list, else => @compileError("Unsupported type: " ++ @typeName(T)), }, }, else => @compileError("Unsupported type: " ++ @typeName(T)), }, }; } fn markInvalidUserInput(b: *Build) void { b.invalid_user_input = true; } fn validateUserInputDidItFail(b: *Build) bool { for (b.user_input_options.keys()) |name| { if (!b.available_options_map.contains(name)) { for (b.available_options_map.keys(), b.available_options_map.values()) |available_name, *available| { log.info("available option: {q}: {s}", .{ available_name, available.description }); } log.err("invalid option: {q}", .{name}); b.markInvalidUserInput(); } } return b.invalid_user_input; } /// This creates the install step and adds it to the dependencies of the /// top-level install step, using all the default options. /// See `addInstallArtifact` for a more flexible function. pub fn installArtifact(b: *Build, artifact: *Step.Compile) void { b.getInstallStep().dependOn(&b.addInstallArtifact(artifact, .{}).step); } /// This merely creates the step; it does not add it to the dependencies of the /// top-level install step. pub fn addInstallArtifact( b: *Build, artifact: *Step.Compile, options: Step.InstallArtifact.Options, ) *Step.InstallArtifact { return Step.InstallArtifact.create(b, artifact, options); } ///`dest_rel_path` is relative to prefix path pub fn installFile(b: *Build, src_path: []const u8, dest_rel_path: []const u8) void { b.getInstallStep().dependOn(&b.addInstallFileWithDir(b.path(src_path), .prefix, dest_rel_path).step); } pub fn installDirectory(b: *Build, options: Step.InstallDir.Options) void { b.getInstallStep().dependOn(&b.addInstallDirectory(options).step); } ///`dest_rel_path` is relative to bin path pub fn installBinFile(b: *Build, src_path: []const u8, dest_rel_path: []const u8) void { b.getInstallStep().dependOn(&b.addInstallFileWithDir(b.path(src_path), .bin, dest_rel_path).step); } ///`dest_rel_path` is relative to lib path pub fn installLibFile(b: *Build, src_path: []const u8, dest_rel_path: []const u8) void { b.getInstallStep().dependOn(&b.addInstallFileWithDir(b.path(src_path), .lib, dest_rel_path).step); } pub fn addObjCopy(b: *Build, source: LazyPath, options: Step.ObjCopy.Options) *Step.ObjCopy { return Step.ObjCopy.create(b, source, options); } /// `dest_rel_path` is relative to install prefix path pub fn addInstallFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile { return b.addInstallFileWithDir(source, .prefix, dest_rel_path); } /// `dest_rel_path` is relative to bin path pub fn addInstallBinFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile { return b.addInstallFileWithDir(source, .bin, dest_rel_path); } /// `dest_rel_path` is relative to lib path pub fn addInstallLibFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile { return b.addInstallFileWithDir(source, .lib, dest_rel_path); } /// `dest_rel_path` is relative to header path pub fn addInstallHeaderFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile { return b.addInstallFileWithDir(source, .header, dest_rel_path); } pub fn addInstallFileWithDir( b: *Build, source: LazyPath, install_dir: InstallDir, dest_rel_path: []const u8, ) *Step.InstallFile { return Step.InstallFile.create(b, source, install_dir, dest_rel_path); } pub fn addInstallDirectory(b: *Build, options: Step.InstallDir.Options) *Step.InstallDir { return Step.InstallDir.create(b, options); } pub fn addCheckFile( b: *Build, file_source: LazyPath, options: Step.CheckFile.Options, ) *Step.CheckFile { return Step.CheckFile.create(b, file_source, options); } /// References a file or directory relative to the source root. pub fn path(b: *Build, sub_path: []const u8) LazyPath { if (fs.path.isAbsolute(sub_path)) { panic("sub_path is expected to be relative to the build root, but was this absolute path: {q}. Absolute paths can cause problems but can be created via Graph.cwdRelativePath", .{sub_path}); } return .{ .src_path = .{ .owner = b, .sub_path = sub_path, } }; } /// Creates a list of files and/or directories relative to the source root. pub fn pathList(b: *Build, sub_paths: []const []const u8) []const LazyPath { const graph = b.graph; const result = graph.alloc(LazyPath, sub_paths.len); for (result, sub_paths) |*d, s| d.* = path(b, s); return result; } pub fn pathJoin(b: *Build, paths: []const []const u8) []u8 { const graph = b.graph; const arena = graph.arena; return fs.path.join(arena, paths) catch @panic("OOM"); } pub fn pathResolve(b: *Build, paths: []const []const u8) []u8 { const graph = b.graph; const arena = graph.arena; return fs.path.resolve(arena, paths) catch @panic("OOM"); } pub fn fmt(b: *Build, comptime format: []const u8, args: anytype) []u8 { const graph = b.graph; const arena = graph.arena; return std.fmt.allocPrint(arena, format, args) catch @panic("OOM"); } /// Creates an anonymous `Step` that searches for an executable on the host that /// has more than one possible name. /// /// Returns the `LazyPath` of the found executable. The search only takes place /// if the `LazyPath` will be used by a depending `Step`. /// /// This API is useful in the following cases: /// * The binary is not named the same across all systems (for example "python" /// vs "python3"). /// * The binary may be produced by building from source rather than being /// globally installed and will therefore be possibly found in one of the /// search prefix paths. /// /// Names are searched in order, observing search prefixes first and then PATH /// environment variable. /// /// Windows file name extensions are searched automatically, respecting the /// PATHEXT environment variable, so they need not be included in this list. /// However, even on Windows, the names will be checked without appending /// extensions first, so that can be used as a priority system. /// /// See also: /// * `findProgram` pub fn findProgramLazy(b: *Build, options: Step.FindProgram.Options) LazyPath { return .{ .generated = .{ .index = Step.FindProgram.create(b, options).found_path } }; } pub const FindProgramOptions = Step.FindProgram.Options; /// Immediately (in the configure phase), searches for an executable on the host /// that has more than one possible name. /// /// Calling this function poisons the configuration cache, so it is only /// appropriate when the existence of the program or its output needs to be /// observed by configuration logic. For more information, see /// `Graph.CachePoison` documentation. /// /// Names are searched in order, observing search prefixes first and then PATH /// environment variable. /// /// Windows file name extensions are searched automatically, respecting the /// PATHEXT environment variable, so they need not be included in this list. /// However, even on Windows, the names will be checked without appending /// extensions first, so that can be used as a priority system. /// /// See also: /// * `findProgramLazy` pub fn findProgram(b: *Build, options: FindProgramOptions) ?[]const u8 { const graph = b.graph; // Because it observes search prefixes and contents of directories in PATH. graph.poisonCache(); for (options.names) |name| { if (Io.Dir.path.isAbsolute(name)) { if (tryFindProgram(b, name)) |found| return found; } for (graph.search_prefixes.items) |search_prefix| { const full_path = b.pathJoin(&.{ search_prefix, "bin", name }); if (tryFindProgram(b, full_path)) |found| return found; } } if (b.graph.environ_map.get("PATH")) |PATH| { for (options.names) |name| { var it = mem.tokenizeScalar(u8, PATH, Io.Dir.path.delimiter); while (it.next()) |p| { const full_path = b.pathJoin(&.{ p, name }); if (tryFindProgram(b, full_path)) |found| return found; } } } return null; } fn supportedWindowsProgramExtension(ext: []const u8) bool { inline for (@typeInfo(std.process.WindowsExtension).@"enum".field_names) |field_name| { if (std.ascii.eqlIgnoreCase(ext, "." ++ field_name)) return true; } return false; } fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 { const graph = b.graph; const io = graph.io; const arena = graph.arena; if (Io.Dir.cwd().access(io, full_path, .{ .execute = true })) |_| { return full_path; } else |err| switch (err) { error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| { if (graph.verbose) log.info("searched: {t} {s}", .{ e, full_path }); }, else => |e| return panic("failed accessing {s}: {t}", .{ full_path, e }), } if (builtin.os.tag == .windows) { if (b.graph.environ_map.get("PATHEXT")) |PATHEXT| { var it = mem.tokenizeScalar(u8, PATHEXT, fs.path.delimiter); const extended_path_buf = arena.alloc(u8, full_path.len + 1 + std.process.WindowsExtension.max_len) catch @panic("OOM"); @memcpy(extended_path_buf[0..full_path.len], full_path); while (it.next()) |ext| { if (!supportedWindowsProgramExtension(ext)) continue; @memcpy(extended_path_buf[full_path.len..][0..ext.len], ext); const extended_path = extended_path_buf[0 .. full_path.len + ext.len]; if (Io.Dir.cwd().access(io, extended_path, .{ .execute = true })) |_| { return extended_path; } else |err| switch (err) { error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| { if (graph.verbose) log.info("searched: {t} {s}", .{ e, extended_path }); }, else => |e| return panic("failed accessing {s}: {t}", .{ extended_path, e }), } } } } return null; } /// Deprecated; use `runFallible`. pub fn runAllowFail( b: *Build, argv: []const []const u8, exit_code: *u8, stderr_behavior: process.SpawnOptions.StdIo, ) anyerror![]u8 { if (!process.can_spawn) return error.ExecNotSupported; switch (runFallible(b, argv, .{ .stderr_behavior = stderr_behavior, })) { .success => |stdout| return stdout, .spawn_failed => |err| return err, .bad_exit_code => |code| { exit_code.* = code; return error.ExitCodeFailure; }, .crashed => { exit_code.* = 255; return error.ProcessTerminated; }, } } pub const RunOptions = struct { stderr_behavior: process.SpawnOptions.StdIo = .inherit, /// Fail the configuration if stdout is larger than this. stdout_limit: Io.Limit = .limited(1_000_000), /// Set to change the current working directory when spawning the child /// process. cwd: process.Child.Cwd = .inherit, /// Replaces the child environment when provided. The PATH value from here /// is not used to resolve `argv[0]`; that resolution always uses parent /// environment. environ_map: ?*const process.Environ.Map = null, expand_arg0: process.ArgExpansion = .no_expand, }; pub const RunResult = union(enum) { /// Thild process exited with code 0, writing this stdout. success: []u8, /// The child process could not be created. spawn_failed: process.SpawnError, /// The child process indicated failure. bad_exit_code: u8, /// The child process terminated abnormally. crashed, }; /// Executes the provided command immediately, allowing failure. /// /// If the program exits successfully, stdout is returned. Otherwise, returns /// an indication of failure. /// /// See also: /// * `run`. pub fn runFallible(b: *Build, argv: []const []const u8, options: RunOptions) RunResult { assert(argv.len != 0); const graph = b.graph; const io = graph.io; const arena = graph.arena; const print_opts: std.zig.AllocPrintCmdOptions = .{ .cwd = switch (options.cwd) { .inherit => null, .path => |p| p, .dir => null, // Unknown without changing function signature of runFallible. }, .child_env = options.environ_map, .parent_env = &graph.environ_map, }; if (graph.verbose) { const text = std.zig.allocPrintCmd(arena, argv, print_opts) catch @panic("OOM"); std.log.scoped(.verbose).info("{s}", .{text}); } var child = process.spawn(io, .{ .argv = argv, .stdin = .ignore, .stdout = .pipe, .stderr = options.stderr_behavior, .cwd = options.cwd, .environ_map = &graph.environ_map, .expand_arg0 = options.expand_arg0, }) catch |err| return .{ .spawn_failed = err }; var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); const stdout = stdout_reader.interface.allocRemaining(arena, options.stdout_limit) catch |err| switch (err) { error.ReadFailed => panic("failed to read from child: {t}", .{stdout_reader.err.?}), else => |e| panic("failed to read from child: {t}", .{e}), }; const term = child.wait(io) catch @panic("unexpected"); return switch (term) { .exited => |code| switch (code) { 0 => .{ .success = stdout }, else => .{ .bad_exit_code = code }, }, .signal, .stopped, .unknown => .crashed, }; } /// Executes the provided command immediately. /// /// If the program exits successfully, stdout is returned. Otherwise, fails the /// build with a helpful message. /// /// See also: /// * `runFallible`. pub fn run(b: *Build, argv: []const []const u8) []u8 { const graph = b.graph; const arena = graph.arena; switch (b.runFallible(argv, .{ .stderr_behavior = .inherit, })) { .success => |stdout| return stdout, .spawn_failed => |err| fatal("the following command failed with {t}:\n{s}", .{ err, std.zig.allocPrintCmd(arena, argv, .{}) catch @panic("OOM"), }), .bad_exit_code => |code| fatal("the following command exited with code {d}:\n{s}", .{ code, std.zig.allocPrintCmd(arena, argv, .{}) catch @panic("OOM"), }), .crashed => fatal("the following command crashed:\n{s}", .{ std.zig.allocPrintCmd(arena, argv, .{}) catch @panic("OOM"), }), } } /// Adds additional paths, equivalent to the `--search-prefix` arguments /// provided by the user. Paths added with this function have lower precedence /// than the ones specified by the user on the command line. /// /// It is generally best practice to avoid calling this function, instead /// relying on the user to provide these paths via the standard build system /// interface. However, when integrating with other build systems, the user may /// have already provided the information to the other build system, and thus /// it is desirable to use that same information without requiring the user to /// provide it again. pub fn addSearchPrefix(b: *Build, search_prefix: []const u8) void { if (b.isRoot()) { const graph = b.graph; const wc = &graph.wip_configuration; const string = wc.addString(search_prefix) catch @panic("OOM"); wc.search_prefixes.append(wc.gpa, string) catch @panic("OOM"); } } pub fn isRoot(b: *const Build) bool { return b.pkg_hash.len == 0; } pub const Dependency = struct { builder: *Build, pub fn artifact(d: *Dependency, name: []const u8) *Step.Compile { var found: ?*Step.Compile = null; for (d.builder.install_tls.step.dependencies.items) |dep_step| { const inst = dep_step.cast(Step.InstallArtifact) orelse continue; if (mem.eql(u8, inst.artifact.name, name)) { if (found != null) panic("artifact name {q} is ambiguous", .{name}); found = inst.artifact; } } return found orelse { if (d.builder.graph.unlazy_deps.count() != 0) { d.builder.serializeConfigurationExiting(); } for (d.builder.install_tls.step.dependencies.items) |dep_step| { const inst = dep_step.cast(Step.InstallArtifact) orelse continue; log.info("available artifact: {q}", .{inst.artifact.name}); } panic("unable to find artifact {q}", .{name}); }; } pub fn module(d: *Dependency, name: []const u8) *Module { return d.builder.modules.get(name) orelse { if (d.builder.graph.unlazy_deps.count() != 0) { d.builder.serializeConfigurationExiting(); } panic("unable to find module {q}", .{name}); }; } pub fn namedWriteFiles(d: *Dependency, name: []const u8) *Step.WriteFile { return d.builder.named_writefiles.get(name) orelse { if (d.builder.graph.unlazy_deps.count() != 0) { d.builder.serializeConfigurationExiting(); } panic("unable to find named writefiles {q}", .{name}); }; } pub fn namedLazyPath(d: *Dependency, name: []const u8) LazyPath { return d.builder.named_lazy_paths.get(name) orelse { if (d.builder.graph.unlazy_deps.count() != 0) { d.builder.serializeConfigurationExiting(); } panic("unable to find named lazypath {q}", .{name}); }; } pub fn path(d: *Dependency, sub_path: []const u8) LazyPath { return .{ .dependency = .{ .dependency = d, .sub_path = sub_path, }, }; } }; fn findPkgHashOrFatal(b: *Build, name: []const u8) []const u8 { for (b.available_deps) |dep| { if (mem.eql(u8, dep[0], name)) return dep[1]; } log.info("all dependencies used by build.zig must be declared in corresponding build.zig.zon", .{}); if (b.pkg_hash.len == 0) panic("no dependency named {s}", .{name}); panic("no dependency named {s} in {s} ({s})", .{ name, b.dep_prefix, b.pkg_hash }); } inline fn findImportPkgHashOrFatal(b: *Build, comptime asking_build_zig: type, comptime dep_name: []const u8) []const u8 { const build_runner = @import("root"); const deps = build_runner.dependencies; const arena = b.graph.arena; const b_pkg_hash, const b_pkg_deps = comptime for (@typeInfo(deps.packages).@"struct".decl_names) |pkg_hash| { const pkg = @field(deps.packages, pkg_hash); if (@hasDecl(pkg, "build_zig") and pkg.build_zig == asking_build_zig) break .{ pkg_hash, pkg.deps }; } else .{ "", deps.root_deps }; if (!mem.eql(u8, b_pkg_hash, b.pkg_hash)) { const build_zig_path = b.root.join(arena, "build.zig") catch @panic("OOM"); panic("{} is not the struct that corresponds to {f}", .{ asking_build_zig, build_zig_path, }); } comptime for (b_pkg_deps) |dep| { if (mem.eql(u8, dep[0], dep_name)) return dep[1]; }; const full_path = b.root.join(arena, "build.zig.zon") catch @panic("OOM"); panic("no dependency named {s} in {f}. All packages used in build.zig must be declared in this file", .{ dep_name, full_path, }); } fn markNeededLazyDep(b: *Build, pkg_hash: []const u8) void { const graph = b.graph; const interned_hash = graph.wip_configuration.addString(pkg_hash) catch @panic("OOM"); graph.unlazy_deps.put(graph.arena, interned_hash, {}) catch @panic("OOM"); } /// Deprecated in favor of `dependencyLazy`. pub fn lazyDependency(b: *Build, name: []const u8, args: anytype) ?*Dependency { return dependencyLazy(b, name, args) catch |err| switch (err) { error.LazyDependencyNeeded => null, }; } /// Indicates that the current configuration needs a potentially lazy dependency. /// /// Related: /// * `declareDependencyStep` /// * `dependency` /// * `dependencyLazy` /// * `dependencyStep` pub fn declareDependency(b: *Build, name: []const u8) void { const pkg_hash = findPkgHashOrFatal(b, name); const entry = package_map.get(pkg_hash).?; if (entry.available) return; markNeededLazyDep(b, pkg_hash); } /// Indicates that the current configuration needs a potentially lazy dependency for the provided `Step`. /// /// Related: /// * `declareDependency` /// * `dependency` /// * `dependencyLazy` /// * `dependencyStep` pub fn declareDependencyStep(b: *Build, s: *Step, name: []const u8) void { const graph = b.graph; const pkg_hash = findPkgHashOrFatal(b, name); const entry = package_map.get(pkg_hash).?; if (entry.available) return; const interned_hash = graph.wip_configuration.addString(pkg_hash) catch @panic("OOM"); s.unlazy_deps.put(graph.arena, interned_hash, {}) catch @panic("OOM"); } /// Indicates that the current configuration needs a potentially lazy dependency for the provided `Step`. /// /// If the dependency is already fetched, it is returned, otherwise `null` is returned. After configuration /// completes, if the dependency is needed after all, it will be fetched and configuration will re-run. /// /// For non-lazy dependencies, this never returns `null`. /// /// Related: /// * `dependency` /// * `dependencyLazy` /// * `declareDependency` /// * `declareDependencyStep` pub fn dependencyStep(b: *Build, s: *Step, name: []const u8, args: anytype) ?*Dependency { const pkg_hash = findPkgHashOrFatal(b, name); const graph = b.graph; const arena = graph.arena; const entry = package_map.get(pkg_hash).?; if (!entry.available) { const interned_hash = graph.wip_configuration.addString(pkg_hash) catch @panic("OOM"); s.unlazy_deps.put(arena, interned_hash, {}) catch @panic("OOM"); return null; } var map: PackageOptions.Map = .empty; PackageOptions.fromArgs(arena, &map, args); return dependencyResolved(b, name, entry, &map); } /// Declares that the current configuration does in fact require a potentially lazy dependency. /// /// If the dependency is already fetched, it is returned. However if the dependency is not yet fetched, then /// when the build script is finished running, the toolchain will not proceed to the make phase. Instead, the /// parent process will additionally fetch all the lazy dependencies that were actually required by running the /// build script, recompile the build script, and then run it again. In other words, if this function returns /// `error.LazyDependencyNeeded` it means that the only purpose of completing the configure phase is to find /// out all the other lazy dependencies that are also required. In this case, one must propagate the error all /// the way up and return it from the main build function. /// /// For non-lazy dependencies, this always succeeds. /// /// Related: /// * `dependency` /// * `dependencyStep` /// * `declareDependency` /// * `declareDependencyStep` pub fn dependencyLazy(b: *Build, name: []const u8, args: anytype) error{LazyDependencyNeeded}!*Dependency { const pkg_hash = findPkgHashOrFatal(b, name); const entry = package_map.get(pkg_hash) orelse unreachable; if (!entry.available) { markNeededLazyDep(b, pkg_hash); return error.LazyDependencyNeeded; } var map: PackageOptions.Map = .empty; PackageOptions.fromArgs(b.graph.arena, &map, args); return dependencyResolved(b, name, entry, &map); } pub const PackageEntry = struct { hash: []const u8, available: bool, build_root: []const u8, deps: AvailableDeps, run_build: ?*const fn (*Build) void, }; /// Build system implementation detail. pub const package_map: std.StaticStringMap(PackageEntry) = blk: { const deps = @import("root").dependencies; const decl_names = @typeInfo(deps.packages).@"struct".decl_names; var kvs: [decl_names.len]struct { []const u8, PackageEntry } = undefined; for (decl_names, 0..) |decl_name, i| { const pkg = @field(deps.packages, decl_name); const available = !@hasDecl(pkg, "available") or pkg.available; kvs[i] = .{ decl_name, .{ .hash = decl_name, .available = available, .build_root = if (available) pkg.build_root else "", .deps = if (available) pkg.deps else &.{}, .run_build = if (available and @hasDecl(pkg, "build_zig")) &struct { fn run(sb: *Build) void { sb.runPackageScript(pkg.build_zig); } }.run else null, } }; } const frozen = kvs; break :blk .initComptime(&frozen); }; /// Declares that the current configuration does in fact require a potentially /// lazy dependency. /// /// If the dependency is already fetched, it is returned. Otherwise, exits the /// configuration phase with intent to fetch the lazy dependency and rerun the /// configuration script. /// /// If it is known to the caller at this point that additional lazy /// dependencies are also required, it would save time to call `dependencyLazy` /// instead, handling `error.LazyDependencyNeeded` in a way that marks multiple /// potentially lazy dependencies as required before eventually returning /// that error from the top level build function. pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency { return dependencyLazy(b, name, args) catch |err| switch (err) { error.LazyDependencyNeeded => { assert(b.graph.unlazy_deps.count() != 0); serializeConfigurationExiting(b); }, }; } /// In a build.zig file, this function is to `@import` what `lazyDependency` is to `dependency`. /// If the dependency is lazy and has not yet been fetched, it instructs the parent process to fetch /// that dependency after the build script has finished running, then returns `null`. /// If the dependency is lazy but has already been fetched, or if it is eager, it returns /// the build.zig struct of that dependency, just like a regular `@import`. pub inline fn lazyImport( b: *Build, /// The build.zig struct of the package importing the dependency. /// When calling this function from the `build` function of a build.zig file's, you normally /// pass `@This()`. comptime asking_build_zig: type, comptime dep_name: []const u8, ) ?type { const build_runner = @import("root"); const deps = build_runner.dependencies; const pkg_hash = findImportPkgHashOrFatal(b, asking_build_zig, dep_name); inline for (@typeInfo(deps.packages).@"struct".decl_names) |decl_name| { if (comptime mem.eql(u8, decl_name, pkg_hash)) { const pkg = @field(deps.packages, decl_name); const available = !@hasDecl(pkg, "available") or pkg.available; if (!available) { markNeededLazyDep(b, pkg_hash); return null; } return if (@hasDecl(pkg, "build_zig")) pkg.build_zig else @compileError("dependency '" ++ dep_name ++ "' does not have a build.zig"); } } comptime unreachable; // Bad @dependencies source } inline fn pkgHashFromBuildZig(comptime build_zig: type) ?[]const u8 { comptime { const deps = @import("root").dependencies; return for (@typeInfo(deps.packages).@"struct".decl_names) |pkg_hash| { const pkg = @field(deps.packages, pkg_hash); if (@hasDecl(pkg, "build_zig") and pkg.build_zig == build_zig) break pkg_hash; } else null; } } /// Build system implementation detail. pub fn dependencyFromBuildZig( b: *Build, /// The build.zig struct of the dependency, normally obtained by `@import` of the dependency. /// If called from the build.zig file itself, use `@This` to obtain a reference to the struct. comptime build_zig: type, args: anytype, ) *Dependency { const arena = b.graph.arena; find_dep: { const pkg_hash = pkgHashFromBuildZig(build_zig) orelse break :find_dep; const dep_name = for (b.available_deps) |dep| { if (mem.eql(u8, dep[1], pkg_hash)) break dep[1]; } else break :find_dep; const entry = package_map.get(pkg_hash) orelse break :find_dep; var map: PackageOptions.Map = .empty; PackageOptions.fromArgs(arena, &map, args); return dependencyResolved(b, dep_name, entry, &map); } const full_path = b.root.join(arena, "build.zig.zon") catch @panic("OOM"); panic("{} is not a build.zig struct of a dependency in {f}", .{ build_zig, full_path }); } /// Takes ownership of `package_options`, which may be unsorted. fn dependencyResolved( b: *Build, name: []const u8, entry: PackageEntry, package_options: *PackageOptions.Map, ) *Dependency { const graph = b.graph; const io = graph.io; const arena = graph.arena; PackageOptions.sort(package_options); if (graph.dependency_cache.getContext(.{ .pkg_hash = entry.hash, .options = package_options, }, .{})) |dep| return dep; const dep_root: Cache.Path = .{ .root_dir = .{ .path = entry.build_root, .handle = Io.Dir.cwd().openDir(io, entry.build_root, .{}) catch |err| fatal("failed to open {q}: {t}", .{ entry.build_root, err }), }, }; const sub_builder = b.createChild(name, dep_root, entry.hash, entry.deps, package_options.*) catch @panic("OOM"); if (entry.run_build) |run_build| { run_build(sub_builder); if (sub_builder.validateUserInputDidItFail()) { std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() }); } } const dep = graph.create(Dependency); dep.* = .{ .builder = sub_builder }; graph.dependency_cache.putContext(arena, .{ .pkg_hash = entry.hash, .options = &sub_builder.user_input_options, }, dep, .{}) catch @panic("OOM"); return dep; } /// Build system implementation detail. pub inline fn runPackageScript(b: *Build, comptime build_zig: anytype) void { const result: anyerror!void = build_zig.build(b); result catch |err| switch (err) { error.LazyDependencyNeeded => assert(b.graph.unlazy_deps.count() != 0), else => { if (b.dep_prefix.len == 0) { log.err("package {q} configuration failed: {t}", .{ b.dep_prefix, err }); } else { log.err("configuration failed: {t}", .{err}); } if (@errorReturnTrace()) |trace| std.debug.dumpErrorReturnTrace(trace); const lazy_count = b.graph.unlazy_deps.count(); if (lazy_count == 0) process.exit(1); log.info("{d} lazy dependencies detected; fetching and retrying configuration", .{lazy_count}); }, }; } // dirnameAllowEmpty is a variant of fs.path.dirname // that allows "" to refer to the root for relative paths. // // For context, dirname("foo") and dirname("") are both null. // However, for relative paths, we want dirname("foo") to be "" // so that we can join it with another path (e.g. build root, cache root, etc.) // // dirname("") should still be null, because we can't go up any further. fn dirnameAllowEmpty(full_path: []const u8) ?[]const u8 { return fs.path.dirname(full_path) orelse { if (fs.path.isAbsolute(full_path) or full_path.len == 0) return null; return ""; }; } test dirnameAllowEmpty { try std.testing.expectEqualStrings( "foo", dirnameAllowEmpty("foo" ++ fs.path.sep_str ++ "bar") orelse @panic("unexpected null"), ); try std.testing.expectEqualStrings( "", dirnameAllowEmpty("foo") orelse @panic("unexpected null"), ); try std.testing.expect(dirnameAllowEmpty("") == null); } /// A reference to an existing or future path. pub const LazyPath = union(enum) { /// A source file path relative to build root. src_path: struct { owner: *std.Build, sub_path: []const u8, }, generated: struct { index: Configuration.GeneratedFileIndex, /// The number of parent directories to go up. /// 0 means the generated file itself. /// 1 means the directory of the generated file. /// 2 means the parent of that directory, and so on. up: usize = 0, /// Applied after `up`. sub_path: []const u8 = "", }, /// Deprecated; call `Graph.cwdRelativePath` instead. cwd_relative: []const u8, dependency: struct { dependency: *Dependency, sub_path: []const u8, }, relative: struct { base: Configuration.LazyPath.Relative.Base, sub_path: []const u8 = "", pub fn eql(a: @This(), b: @This()) bool { return a.base == b.base and mem.eql(u8, a.sub_path, b.sub_path); } }, /// Path to the Zig executable being used to execute "zig build". pub const zig_exe: LazyPath = .{ .relative = .{ .base = .zig_exe } }; /// Path to the "lib/" directory from the Zig installation being used to /// execute "zig build". pub const zig_lib: LazyPath = .{ .relative = .{ .base = .zig_lib } }; /// Path to the project's local cache directory (usually called ".zig-cache"). pub const cache_root: LazyPath = .{ .relative = .{ .base = .local_cache } }; /// Returns a lazy path referring to the directory containing this path. /// /// The dirname is not allowed to escape the logical root for underlying /// path. For example, if the path is relative to the build root, the /// dirname is not allowed to traverse outside of the build root. /// Similarly, if the path is a generated file inside zig-cache, the /// dirname is not allowed to traverse outside of zig-cache. pub fn dirname(lazy_path: LazyPath) LazyPath { return switch (lazy_path) { .src_path => |sp| .{ .src_path = .{ .owner = sp.owner, .sub_path = dirnameAllowEmpty(sp.sub_path) orelse { dumpBadDirnameHelp(null, null, "dirname() attempted to traverse outside the build root\n", .{}) catch {}; @panic("misconfigured build script"); }, } }, .generated => |generated| .{ .generated = if (dirnameAllowEmpty(generated.sub_path)) |sub_dirname| .{ .index = generated.index, .up = generated.up, .sub_path = sub_dirname, } else .{ .index = generated.index, .up = generated.up + 1, .sub_path = "", } }, .cwd_relative => |rel_path| .{ .cwd_relative = dirnameAllowEmpty(rel_path) orelse { // If we get null, it means one of two things: // - rel_path was absolute, and is now root // - rel_path was relative, and is now "" // In either case, the build script tried to go too far // and we should panic. if (fs.path.isAbsolute(rel_path)) { dumpBadDirnameHelp(null, null, \\dirname() attempted to traverse outside the root. \\No more directories left to go up. \\ , .{}) catch {}; @panic("misconfigured build script"); } else { dumpBadDirnameHelp(null, null, \\dirname() attempted to traverse outside the current working directory. \\ , .{}) catch {}; @panic("misconfigured build script"); } }, }, .relative => |r| .{ .relative = .{ .base = r.base, .sub_path = dirnameAllowEmpty(r.sub_path) orelse { dumpBadDirnameHelp(null, null, "dirname() attempted to traverse outside the base path\n", .{}) catch {}; @panic("misconfigured build script"); }, } }, .dependency => |dep| .{ .dependency = .{ .dependency = dep.dependency, .sub_path = dirnameAllowEmpty(dep.sub_path) orelse { dumpBadDirnameHelp(null, null, \\dirname() attempted to traverse outside the dependency root. \\ , .{}) catch {}; @panic("misconfigured build script"); }, } }, }; } pub fn path(lazy_path: LazyPath, b: *Build, sub_path: []const u8) LazyPath { const graph = b.graph; const arena = graph.arena; return lazy_path.join(arena, sub_path) catch @panic("OOM"); } pub fn join(lazy_path: LazyPath, arena: Allocator, sub_path: []const u8) Allocator.Error!LazyPath { return switch (lazy_path) { .src_path => |src| .{ .src_path = .{ .owner = src.owner, .sub_path = try fs.path.resolve(arena, &.{ src.sub_path, sub_path }), } }, .generated => |gen| .{ .generated = .{ .index = gen.index, .up = gen.up, .sub_path = try fs.path.resolve(arena, &.{ gen.sub_path, sub_path }), } }, .cwd_relative => |cwd_relative| .{ .cwd_relative = try fs.path.resolve(arena, &.{ cwd_relative, sub_path }), }, .relative => |r| .{ .relative = .{ .base = r.base, .sub_path = try fs.path.resolve(arena, &.{ r.sub_path, sub_path }), } }, .dependency => |dep| .{ .dependency = .{ .dependency = dep.dependency, .sub_path = try fs.path.resolve(arena, &.{ dep.sub_path, sub_path }), } }, }; } /// Deprecated, use `format` instead. pub fn getDisplayName(lazy_path: LazyPath) []const u8 { return switch (lazy_path) { .src_path => |sp| sp.sub_path, .cwd_relative => |p| p, .generated => "generated", .dependency => "dependency", .relative => |r| @tagName(r.base), }; } pub fn format(lp: LazyPath, w: *Io.Writer) Io.Writer.Error!void { switch (lp) { .src_path => |sp| try w.writeAll(sp.sub_path), .cwd_relative => |p| try w.writeAll(p), .generated => try w.writeAll("generated"), .dependency => try w.writeAll("dependency"), .relative => |r| try w.print("{t} {s}", .{ r.base, r.sub_path }), } } /// Adds dependencies this file source implies to the given step. pub fn addStepDependencies(lazy_path: LazyPath, other_step: *Step) void { switch (lazy_path) { .src_path, .cwd_relative, .relative, .dependency => {}, .generated => |gen| { const graph = other_step.owner.graph; const generated_owner_step = graph.generated_files.items[@backingInt(gen.index)]; other_step.dependOn(generated_owner_step); }, } } /// Copies the internal strings. /// /// The `graph` parameter is only used for the global arena allocator. pub fn dupe(lazy_path: LazyPath, graph: *const Graph) LazyPath { return dupeInner(lazy_path, graph.arena); } /// Copies the slice of paths and all internal strings. /// /// The `graph` parameter is only used for the global arena allocator. pub fn dupeList(lazy_paths: []const LazyPath, graph: *const Graph) []const LazyPath { const arena = graph.arena; const result = graph.alloc(LazyPath, lazy_paths.len); for (result, lazy_paths) |*d, s| d.* = dupeInner(s, arena); return result; } fn dupeInner(lazy_path: LazyPath, arena: Allocator) LazyPath { return switch (lazy_path) { .src_path => |sp| .{ .src_path = .{ .owner = sp.owner, .sub_path = sp.owner.graph.dupePath(sp.sub_path), } }, .cwd_relative => |p| .{ .cwd_relative = Graph.dupePathInner(arena, p) }, .relative => |r| .{ .relative = r }, .generated => |gen| .{ .generated = .{ .index = gen.index, .up = gen.up, .sub_path = Graph.dupePathInner(arena, gen.sub_path), } }, .dependency => |dep| .{ .dependency = .{ .dependency = dep.dependency, .sub_path = Graph.dupePathInner(arena, dep.sub_path), } }, }; } fn eql(a: LazyPath, b: LazyPath) bool { if (std.meta.activeTag(a) != b) return false; switch (a) { .src_path => |a_sp| { const b_sp = b.src_path; if (a_sp.owner != b_sp.owner) return false; if (mem.eql(u8, a_sp.sub_path, b_sp.sub_path)) return false; }, .generated => |*a_gen| { const b_gen = &b.generated; if (a_gen.index != b_gen.index) return false; if (a_gen.up != b_gen.up) return false; if (mem.eql(u8, a_gen.sub_path, b_gen.sub_path)) return false; }, .cwd_relative => |a_rel_path| { const b_rel_path = b.cwd_relative; if (!mem.eql(u8, a_rel_path, b_rel_path)) return false; }, .relative => |a_relative| return a_relative.eql(b.relative), .dependency => |a_dep| { const b_dep = b.dependency; if (a_dep.dependency != b_dep.dependency) return false; if (!mem.eql(u8, a_dep.sub_path, b_dep.sub_path)) return false; }, } return true; } fn hash(lp: LazyPath, hasher: *std.hash.Wyhash) void { switch (lp) { .src_path => |sp| { hasher.update(sp.owner.pkg_hash); hasher.update(sp.sub_path); }, .generated => |gen| { hasher.update(@ptrCast(&gen.index)); hasher.update(@ptrCast(&gen.up)); hasher.update(gen.sub_path); }, .cwd_relative => |rel_path| { hasher.update(rel_path); }, .relative => |r| { hasher.update(@ptrCast(&r.base)); hasher.update(@ptrCast(&r.sub_path)); }, .dependency => |dep| { hasher.update(dep.dependency.builder.pkg_hash); hasher.update(dep.sub_path); }, } } }; fn dumpBadDirnameHelp( fail_step: ?*Step, asking_step: ?*Step, comptime msg: []const u8, args: anytype, ) anyerror!void { const stderr = std.debug.lockStderr(&.{}).terminal(); defer std.debug.unlockStderr(); const w = stderr.writer; try w.print(msg, args); if (fail_step) |s| { stderr.setColor(.red) catch {}; try w.writeAll(" The step was created by this stack trace:\n"); stderr.setColor(.reset) catch {}; s.dump(stderr); } if (asking_step) |as| { stderr.setColor(.red) catch {}; try w.print(" The step {q} that is missing a dependency on the above step was created by this stack trace:\n", .{as.name}); stderr.setColor(.reset) catch {}; as.dump(stderr); } stderr.setColor(.red) catch {}; try w.writeAll(" Proceeding to panic.\n"); stderr.setColor(.reset) catch {}; } pub const InstallDir = union(enum) { prefix: void, lib: void, bin: void, header: void, /// A path relative to the prefix custom: []const u8, /// Duplicates the install directory including the path if set to custom. pub fn dupe(dir: InstallDir, graph: *const Graph) InstallDir { if (dir == .custom) { return .{ .custom = graph.dupeString(dir.custom) }; } else { return dir; } } }; /// Creates a path leading to a directory inside "tmp" subdirectory of local /// cache which is created on demand and cleaned up by the build runner upon /// success. pub fn tmpPath(b: *Build) LazyPath { const wf = b.addTempFiles(); return wf.getDirectory(); } /// A pair of target query and fully resolved target. /// This type is generally required by build system API that need to be given a /// target. The query is kept because the Zig toolchain needs to know which parts /// of the target are "native". This can apply to the CPU, the OS, or even the ABI. pub const ResolvedTarget = struct { query: Target.Query, result: Target, }; /// Converts a target query into a fully resolved target that can be passed to /// various parts of the API. pub fn resolveTargetQuery(b: *Build, query: Target.Query) ResolvedTarget { if (query.isNative()) { // Hot path. This is faster than querying the native CPU and OS again. return b.graph.host; } const io = b.graph.io; return .{ .query = query, .result = std.zig.system.resolveTargetQuery(io, query) catch @panic("unable to resolve target query"), }; } pub fn wantSharedLibSymLinks(target: Target) bool { return target.os.tag != .windows; } pub const SystemIntegrationOptionConfig = struct { /// If left as null, then the default will depend on system_package_mode. default: ?bool = null, }; pub fn systemIntegrationOption( b: *Build, name: []const u8, config: SystemIntegrationOptionConfig, ) bool { const graph = b.graph; const arena = graph.arena; const gop = graph.system_integration_options.getOrPut(arena, name) catch @panic("OOM"); if (gop.found_existing) switch (gop.value_ptr.*) { .user_disabled => { gop.value_ptr.* = .declared_disabled; return false; }, .user_enabled => { gop.value_ptr.* = .declared_enabled; return true; }, .declared_disabled => return false, .declared_enabled => return true, } else { gop.key_ptr.* = graph.dupeString(name); if (config.default orelse graph.system_package_mode) { gop.value_ptr.* = .declared_enabled; return true; } else { gop.value_ptr.* = .declared_disabled; return false; } } } /// Indicates that the build.zig logic depends on a particular file's contents. /// /// The file must exist when this function is called. /// /// If the file is deleted, or has its contents changed, the configure phase /// will be repeated. If the inode or mtime change, but the file contents remain /// the same, it will not cause the configure logic to be repeated. /// /// This is an alternative to `Graph.poisonCache` that avoids making every invocation /// of `zig build` into a cache miss. /// /// Only a subset of `LazyPath` are supported: /// - Relative to cwd /// - Relative to any package root /// - Relative to zig cache or zig installation /// /// If the file would be inside one of the search prefixes, then the dependency /// cannot be tracked; `Graph.poisonCache` must be used instead. pub fn dependOnFileContents(b: *Build, lazy_path: LazyPath) void { validateConfigureDependency(lazy_path); const graph = b.graph; graph.configure_dependencies.append(graph.arena, .{ .lazy_path = lazy_path.dupe(graph), .is_directory = false, .metadata_only = false, }) catch @panic("OOM"); } /// Indicates that the build.zig logic depends on a particular file's size, /// inode, mtime, and contents. /// /// If the file is created, deleted, has its contents changed, or the inode /// changes, or the mtime changes, the configure phase will be repeated. /// /// This is an alternative to `Graph.poisonCache` that avoids making every invocation /// of `zig build` into a cache miss. /// /// Only a subset of `LazyPath` are supported: /// - Relative to cwd /// - Relative to any package root /// - Relative to zig cache or zig installation /// /// If the file would be inside one of the search prefixes, then the dependency /// cannot be tracked; `Graph.poisonCache` must be used instead. pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void { validateConfigureDependency(lazy_path); const graph = b.graph; graph.configure_dependencies.append(graph.arena, .{ .lazy_path = lazy_path.dupe(graph), .is_directory = false, .metadata_only = true, }) catch @panic("OOM"); } /// Indicates that the build.zig logic depends on a particular directory's entries. /// /// The directory must exist when this function is called. /// /// This is an alternative to `Graph.poisonCache` that avoids making every invocation /// of `zig build` into a cache miss. /// /// If any file is created, deleted, or renamed in this directory, leaving the /// directory in a different state than last configuration with respect to /// existence and naming of entries, the configure phase will be repeated. /// /// Only a subset of `LazyPath` are supported: /// - Relative to cwd /// - Relative to any package root /// - Relative to zig cache or zig installation /// /// If the directory would be inside one of the search prefixes, then the dependency /// cannot be tracked; `Graph.poisonCache` must be used instead. /// /// Not recursive. pub fn dependOnDirectoryContents(b: *Build, lazy_path: LazyPath) void { validateConfigureDependency(lazy_path); const graph = b.graph; graph.configure_dependencies.append(graph.arena, .{ .lazy_path = lazy_path.dupe(graph), .is_directory = true, .metadata_only = false, }) catch @panic("OOM"); } /// Indicates that the build.zig logic depends on a particular directory's last /// modification date. /// /// This is an alternative to `Graph.poisonCache` that avoids making every invocation /// of `zig build` into a cache miss. /// /// If any file is created, deleted, or renamed in this directory, the /// configure phase will be repeated. /// /// Only a subset of `LazyPath` are supported: /// - Relative to cwd /// - Relative to any package root /// - Relative to zig cache or zig installation /// /// If the directory would be inside one of the search prefixes, then the dependency /// cannot be tracked; `Graph.poisonCache` must be used instead. /// /// Not recursive. pub fn dependOnDirectoryMetadata(b: *Build, lazy_path: LazyPath) void { validateConfigureDependency(lazy_path); const graph = b.graph; graph.configure_dependencies.append(graph.arena, .{ .lazy_path = lazy_path.dupe(graph), .is_directory = true, .metadata_only = true, }) catch @panic("OOM"); } fn validateConfigureDependency(lazy_path: LazyPath) void { switch (lazy_path) { .src_path, .cwd_relative, .dependency => {}, // OK .generated => @panic("configure phase cannot depend on files generated during make phase"), .relative => |relative| switch (relative.base) { .cwd, .build_root, .local_cache, .global_cache, .zig_lib => {}, // OK .zig_exe => if (relative.sub_path.len > 0) @panic("file base cannot have a sub path"), .install_prefix, .install_lib, .install_bin, .install_include, .deployed_prefix, .deployed_lib, .deployed_bin, .deployed_include, => @panic("configure phase cannot depend on files installed during make phase"), .libc_runtimes, => @panic("configure phase cannot depend on directory known only during make phase"), }, } } /// Build system implementation detail. pub fn serializeConfigurationExiting(b: *Build) noreturn { const graph = b.graph; const io = graph.io; var stdout_buffer: [1024]u8 = undefined; var file_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); Serialize.write(b, &graph.wip_configuration, &file_writer.interface) catch |err| switch (err) { error.WriteFailed => fatal("failed to write configuration output: {t}", .{file_writer.err.?}), error.OutOfMemory => @panic("OOM"), }; file_writer.flush() catch |err| fatal("failed to write configuration output: {t}", .{err}); // This executable is short-lived and run in Debug mode, so we'd rather // have `zig build` run faster than catch resource leaks in the user's // build.zig script (or, frankly, this configure runner), therefore we call // exit directly here rather than cleanExit. process.exit(0); } test { _ = Cache; _ = Configuration; _ = Module; _ = Step; _ = Configuration; _ = &findProgram; _ = abi; }