| 1 | const ScannedConfig = @This(); |
| 2 | |
| 3 | const std = @import("std"); |
| 4 | const Configuration = std.Build.Configuration; |
| 5 | const Writer = std.Io.Writer; |
| 6 | const Serializer = std.zon.Serializer; |
| 7 | |
| 8 | const Graph = @import("Graph.zig"); |
| 9 | |
| 10 | configuration: Configuration, |
| 11 | top_level_steps: std.array_hash_map.String(Configuration.Step.Index), |
| 12 | path: std.Build.Cache.Path, |
| 13 | |
| 14 | pub 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 | |
| 89 | fn 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 | |
| 97 | fn 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 | |
| 211 | fn 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 | |
| 226 | pub 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 | |
| 241 | pub 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 | } |