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