1const ScannedConfig = @This();
2
3const std = @import("std");
4const Configuration = std.Build.Configuration;
5const Writer = std.Io.Writer;
6const Serializer = std.zon.Serializer;
7
8const Graph = @import("Graph.zig");
9
10configuration: Configuration,
11top_level_steps: std.array_hash_map.String(Configuration.Step.Index),
12path: std.Build.Cache.Path,
13
14pub fn print(sc: *const ScannedConfig, w: *Writer) Writer.Error!void {
15 const c = &sc.configuration;
16 var serializer: Serializer = .{ .writer = w };
17 var s = try serializer.beginStruct(.{});
18
19 {
20 var tf = try s.beginTupleField("search_prefixes", .{});
21 for (c.search_prefixes) |string| try tf.field(string.slice(c), .{});
22 try tf.end();
23 }
24
25 try s.field("default_step", @backingInt(c.default_step), .{});
26 {
27 var sf = try s.beginStructField("top_level_steps", .{});
28 for (sc.top_level_steps.keys(), sc.top_level_steps.values()) |name, step| {
29 try sf.field(name, @backingInt(step), .{});
30 }
31 try sf.end();
32 }
33
34 {
35 var tf = try s.beginTupleField("steps", .{});
36 for (c.steps) |step| {
37 var step_field = try tf.beginStructField(.{});
38 try printStruct(sc, &step_field, Configuration.Step, step);
39 try step_field.end();
40 }
41 try tf.end();
42 }
43
44 {
45 var tf = try s.beginTupleField("path_deps", .{});
46 for (c.path_deps) |path_dep| {
47 var sf = try tf.beginStructField(.{});
48 try sf.field("base", @tagName(path_dep.flags.base), .{});
49 try sf.field("sub", path_dep.sub.slice(c), .{});
50 try sf.end();
51 }
52 try tf.end();
53 }
54
55 {
56 var tf = try s.beginTupleField("unlazy_deps", .{});
57 for (c.unlazy_deps) |dep| {
58 try tf.field(dep.slice(c), .{});
59 }
60 try tf.end();
61 }
62
63 {
64 var tf = try s.beginTupleField("system_integrations", .{});
65 for (c.system_integrations) |opt| {
66 var sf = try tf.beginStructField(.{});
67 try sf.field("name", opt.name.slice(c), .{});
68 try sf.field("status", opt.status, .{});
69 try sf.end();
70 }
71 try tf.end();
72 }
73
74 {
75 var tf = try s.beginTupleField("available_options", .{});
76 for (c.available_options) |opt| {
77 var sf = try tf.beginStructField(.{});
78 try sf.field("name", opt.name.slice(c), .{});
79 try sf.field("description", opt.description.slice(c), .{});
80 try sf.field("type", @tagName(opt.type), .{});
81 try sf.end();
82 }
83 try tf.end();
84 }
85
86 try s.end();
87}
88
89fn printStruct(sc: *const ScannedConfig, s: *Serializer.Struct, comptime S: type, v: S) !void {
90 const info = @typeInfo(S).@"struct";
91 inline for (info.field_names, info.field_types) |field_name, field_type| {
92 try s.fieldPrefix(field_name);
93 try printValue(sc, s.container.serializer, field_type, @field(v, field_name));
94 }
95}
96
97fn printValue(sc: *const ScannedConfig, s: *Serializer, comptime Field: type, field_value: Field) !void {
98 const c = &sc.configuration;
99 switch (Field) {
100 Configuration.String => {
101 try s.value(field_value.slice(c), .{});
102 },
103 Configuration.Deps.Index => {
104 try printValue(sc, s, []const Configuration.Step.Index, field_value.get(c).steps.slice);
105 },
106 Configuration.MaxRss => {
107 try s.value(field_value.toBytes(), .{});
108 },
109 Configuration.Step.Run.Arg.Index => {
110 var sub_struct = try s.beginStruct(.{});
111 try printStruct(sc, &sub_struct, Configuration.Step.Run.Arg, field_value.get(c));
112 try sub_struct.end();
113 },
114 Configuration.Step.ObjCopy.UpdateSection.Flags => {
115 var sub_struct = try s.beginStruct(.{});
116 try printStruct(sc, &sub_struct, Field, field_value);
117 try sub_struct.end();
118 },
119 Configuration.LazyPath.Index => {
120 switch (field_value.get(c)) {
121 inline else => |u| {
122 var sub_struct = try s.beginStruct(.{});
123 try printStruct(sc, &sub_struct, @TypeOf(u), u);
124 try sub_struct.end();
125 },
126 }
127 },
128 else => switch (@typeInfo(Field)) {
129 .int => try s.int(field_value),
130 .pointer => |info| switch (info.size) {
131 .slice => {
132 var slice_field = try s.beginTuple(.{});
133 for (field_value) |elem| {
134 try slice_field.fieldPrefix();
135 try printValue(sc, s, info.child, elem);
136 }
137 try slice_field.end();
138 },
139 else => comptime unreachable,
140 },
141 .@"enum" => {
142 if (@hasDecl(Field, "storage")) switch (Field.storage) {
143 .extended => {
144 var sub_struct = try s.beginStruct(.{});
145 switch (field_value.get(c.extra)) {
146 inline else => |u| {
147 try printStruct(sc, &sub_struct, @TypeOf(u), u);
148 },
149 }
150 try sub_struct.end();
151 },
152 .flag_optional => comptime unreachable,
153 .flag_length_prefixed_list => comptime unreachable,
154 .enum_optional => comptime unreachable,
155 .union_list => comptime unreachable,
156 .length_prefixed_list => comptime unreachable,
157 .flag_list => comptime unreachable,
158 .flag_union => comptime unreachable,
159 .multi_list => comptime unreachable,
160 } else if (std.enums.tagName(Field, field_value)) |name| {
161 try s.ident(name);
162 } else {
163 try s.int(@backingInt(field_value));
164 }
165 },
166 .@"struct" => |info| switch (info.layout) {
167 .@"packed" => {
168 try s.value(field_value, .{});
169 },
170 .@"extern" => {
171 var sub_struct = try s.beginStruct(.{});
172 try printStruct(sc, &sub_struct, Field, field_value);
173 try sub_struct.end();
174 },
175 .auto => switch (Field.storage) {
176 .flag_optional, .enum_optional => {
177 if (field_value.value) |some| {
178 try printValue(sc, s, Field.Value, some);
179 } else {
180 try s.value(null, .{});
181 }
182 },
183 .length_prefixed_list, .flag_length_prefixed_list, .flag_list => {
184 try printValue(sc, s, @TypeOf(field_value.slice), field_value.slice);
185 },
186 .extended => @compileError("TODO"),
187 .union_list => {
188 var slice_field = try s.beginTuple(.{});
189 for (field_value.slice(c.extra), 0..) |elem, i| switch (field_value.tag(c.extra, i)) {
190 inline else => |tag| {
191 var sub_struct = try s.beginStruct(.{});
192 try sub_struct.fieldPrefix(@tagName(tag));
193 try printValue(sc, s, @FieldType(Field.Union, @tagName(tag)), @fromBackingInt(@intCast(elem)));
194 try sub_struct.end();
195 },
196 };
197 try slice_field.end();
198 },
199 .flag_union => try printValue(sc, s, Field.Union, field_value.u),
200 .multi_list => @compileError("TODO"),
201 },
202 },
203 .@"union" => {
204 try printTaggedUnion(sc, s, field_value);
205 },
206 else => @compileError("not implemented: " ++ @typeName(Field)),
207 },
208 }
209}
210
211fn printTaggedUnion(sc: *const ScannedConfig, s: *Serializer, value: anytype) !void {
212 switch (value) {
213 inline else => |u, tag| {
214 if (@TypeOf(u) == void) {
215 try s.ident(@tagName(tag));
216 } else {
217 var sub_struct = try s.beginStruct(.{});
218 try sub_struct.fieldPrefix(@tagName(tag));
219 try printValue(sc, s, @TypeOf(u), u);
220 try sub_struct.end();
221 }
222 },
223 }
224}
225
226pub fn printSteps(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void {
227 const arena = graph.arena;
228 const c = &sc.configuration;
229 for (sc.top_level_steps.keys(), sc.top_level_steps.values()) |name, step_index| {
230 const step = step_index.ptr(c);
231 const decorated_name = if (step_index == c.default_step)
232 try std.fmt.allocPrint(arena, "{s} (default)", .{name})
233 else
234 name;
235 const top_level = step.extended.get(c.extra).top_level;
236 const description = top_level.description.slice(c);
237 try w.print(" {s:<28} {s}\n", .{ decorated_name, description });
238 }
239}
240
241pub fn printUsage(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void {
242 const arena = graph.arena;
243
244 try w.print(
245 \\Usage: {s} build [steps] [options]
246 \\
247 \\Steps:
248 \\
249 , .{graph.zig_exe});
250 try printSteps(sc, graph, w);
251 try w.writeAll(
252 \\
253 \\Project-Specific Options:
254 \\
255 );
256
257 const available_options = sc.configuration.available_options;
258 if (available_options.len == 0) {
259 try w.print(" (none)\n", .{});
260 } else {
261 for (available_options) |option| {
262 const name = option.name.slice(&sc.configuration);
263 const description = option.description.slice(&sc.configuration);
264 const help = try std.fmt.allocPrint(arena, " -D{s}=[{t}]", .{ name, option.type });
265 try w.print("{s:<30} {s}\n", .{ help, description });
266 if (option.enum_options.slice(&sc.configuration)) |enum_options| {
267 const padding: [33]u8 = @splat(' ');
268 try w.writeAll(padding ++ "Supported Values:\n");
269 for (enum_options) |enum_option_index| {
270 const enum_option = enum_option_index.slice(&sc.configuration);
271 try w.print(padding ++ " {s}\n", .{enum_option});
272 }
273 }
274 }
275 }
276
277 try w.writeAll(
278 \\
279 \\System Integration Options:
280 \\ --search-prefix [path] Add a path to look for binaries, libraries, headers
281 \\ --sysroot [path] Set the system root directory (usually /)
282 \\ --libc [file] Provide a file which specifies libc paths
283 \\
284 \\ --system [pkgdir] Disable package fetching; enable all integrations
285 \\ -fsys=[name] Enable a system integration
286 \\ -fno-sys=[name] Disable a system integration
287 \\
288 \\ -fdarling, -fno-darling Integration with system-installed Darling to
289 \\ execute macOS programs on Linux hosts
290 \\ (default: no)
291 \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute
292 \\ foreign-architecture programs on Linux hosts
293 \\ (default: no)
294 \\ --libc-runtimes [path] Enhances QEMU integration by providing dynamic libc
295 \\ (e.g. glibc or musl) built for multiple foreign
296 \\ architectures, allowing execution of non-native
297 \\ programs that link with libc.
298 \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
299 \\ ARM64 macOS hosts. (default: no)
300 \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
301 \\ execute WASI binaries. (default: no)
302 \\ -fwine, -fno-wine Integration with system-installed Wine to execute
303 \\ Windows programs on Linux hosts. (default: no)
304 \\
305 \\ Available System Integrations: Enabled:
306 \\
307 );
308 if (sc.configuration.system_integrations.len == 0) {
309 try w.writeAll(" (none) -\n");
310 } else {
311 for (sc.configuration.system_integrations) |system_integration| {
312 const name = system_integration.name.slice(&sc.configuration);
313 const status = switch (system_integration.status) {
314 .disabled => "no",
315 .enabled => "yes",
316 };
317 try w.print(" {s:<43} {s}\n", .{ name, status });
318 }
319 }
320
321 try w.writeAll(
322 \\
323 \\General Options:
324 \\ -h, --help Print this help to stdout and exit
325 \\ -l, --list-steps Print available steps to stdout and exit
326 \\
327 \\ -p, --prefix [path] Where to install files (default: zig-out)
328 \\ --prefix-lib-dir [path] Where to install libraries
329 \\ --prefix-exe-dir [path] Where to install executables
330 \\ --prefix-include-dir [path] Where to install C header files
331 \\ --release[=mode] Request release mode, optionally specifying a
332 \\ preferred optimization mode: fast, safe, small
333 \\
334 \\ --verbose Print commands before executing them
335 \\ --color [auto|off|on] Enable or disable colored error messages
336 \\ --error-style [style] Control how build errors are printed
337 \\ verbose (Default) Report errors with full context
338 \\ minimal Report errors after summary, excluding context like command lines
339 \\ verbose_clear Like 'verbose', but clear the terminal at the start of each update
340 \\ minimal_clear Like 'minimal', but clear the terminal at the start of each update
341 \\ --multiline-errors [style] Control how multi-line error messages are printed
342 \\ indent (Default) Indent non-initial lines to align with initial line
343 \\ newline Include a leading newline so that the error message is on its own lines
344 \\ none Print as usual so the first line is misaligned
345 \\ --summary [mode] Control the printing of the build summary
346 \\ all Print the build summary in its entirety
347 \\ new Omit cached steps
348 \\ failures (Default if short-lived) Only print failed steps
349 \\ line (Default if long-lived) Only print the single-line summary
350 \\ none Do not print the build summary
351 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
352 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
353 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
354 \\ --test-timeout <timeout> Limit execution time of unit tests, terminating if exceeded.
355 \\ The timeout must include a unit: ns, us, ms, s, m, h
356 \\ --watch Continuously rebuild when source files are modified
357 \\ --debounce <ms> Delay before rebuilding after changed file detected
358 \\ --webui[=ip] Enable the web interface on the given IP address
359 \\ --fuzz[=limit] Continuously search for unit test failures with an optional
360 \\ limit to the max number of iterations. The argument supports
361 \\ an optional 'K', 'M', or 'G' suffix (e.g. '10K'). Implies
362 \\ '--webui' when no limit is specified.
363 \\ --time-report Force full rebuild and provide detailed information on
364 \\ compilation time of Zig source code (implies '--webui')
365 \\ -fincremental Enable incremental compilation
366 \\ -fno-incremental Disable incremental compilation
367 \\
368 \\Package Management Options:
369 \\ --fetch[=mode] Fetch dependency tree (optionally choose laziness) and exit
370 \\ needed (Default) Lazy dependencies are fetched as needed
371 \\ all Lazy dependencies are always fetched
372 \\ --fork=[path], --fork [path] Override one or more projects from dependency tree
373 \\
374 \\Advanced Options:
375 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
376 \\ -fno-reference-trace Disable reference trace
377 \\ -fallow-so-scripts Allows .so files to be GNU ld scripts
378 \\ -fno-allow-so-scripts (default) .so files must be ELF files
379 \\ --error-limit [num] Set the maximum amount of distinct error values
380 \\ --build-file [file] Override path to build.zig
381 \\ --cache-dir [path] Override path to local Zig cache directory
382 \\ --zig-lib=[arg] Override path to Zig lib directory
383 \\ --seed [integer] For shuffling dependency traversal order (default: random)
384 \\ --cache-poison[=mode] Override configuration caching behavior
385 \\ pure (default) Avoid false positive cache hits
386 \\ poisoned Don't cache the configuration
387 \\ disallowed Panics when cache would be poisoned
388 \\ ignored A little poison never hurt anybody
389 \\ --print-configuration Render configuration as .zon to stdout
390 \\ --print-configuration-path Print the path to the binary configuration file to stdout
391 \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries
392 \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM)
393 \\ sha1, tree 20-byte cryptographic hash (ELF, WASM)
394 \\ md5 16-byte cryptographic hash (ELF)
395 \\ uuid 16-byte random UUID (ELF, WASM)
396 \\ 0x[hexstring] Constant ID, maximum 32 bytes (ELF, WASM)
397 \\ none (default) No build ID
398 \\ --debug-log [scope] Enable debugging the compiler
399 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
400 \\ --verbose-link Enable compiler debug output for linking
401 \\ --verbose-air Enable compiler debug output for Zig AIR
402 \\ --verbose-llvm-ir Enable compiler debug output for LLVM IR
403 \\ --verbose-cimport Enable compiler debug output for C imports
404 \\ --verbose-cc Enable compiler debug output for C compilation
405 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
406 \\
407 );
408}