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