authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-13 23:21:35-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-13 23:40:03-07:00
loge0f7e43a540bb1c401f44505941ae1f4b3645587
treeeba50dc1fc5a69b0d5b9ee8a54d79eccf91bfcc0
parent896945d8ddaac93d987788039c7e101b2bf32d22

build system: rework user input options

followups from 8e48a5c524f970c4630725e2efe69243d8d79fde along with other related changes. - user input options should not be serialized into Configuration. - more helpful log messages when invalid -D arguments are provided - use array hash maps in various places, simplifying iteration and sorting. - when creating a package instance, sort the user input options once, before the lookup, rather than creating a fresh hash map and sorting it on every call to hash (wtf!) - when sorting, don't use ascii case insensitivity (wtf!!) - simplify the detection of invalid arguments passed to zig build - use package hash rather than file system path in package instance key

5 files changed, 328 insertions(+), 608 deletions(-)

lib/compiler/Maker/ScannedConfig.zig-8
......@@ -116,14 +116,6 @@ pub fn print(sc: *const ScannedConfig, w: *Writer) Writer.Error!void {
116116 try sf.container.serializer.int(@backingInt(inst.package));
117117 }
118118
119 var otf = try sf.beginTupleField("user_input_options", .{});
120 for (inst.user_input_options.slice(c)) |option| {
121 var osf = try otf.beginStructField(.{});
122 try sc.printStruct(&osf, Configuration.Package.Instance.UserInputOption, option.get(c));
123 try osf.end();
124 }
125 try otf.end();
126
127119 var msf = try sf.beginStructField("modules", .{});
128120 for (inst.modules.keys.slice(c), inst.modules.values.slice(c)) |key, value| {
129121 var msf2 = try msf.beginStructField(key.slice(c), .{});
lib/compiler/configurer.zig+27-6
......@@ -86,11 +86,15 @@ pub fn main(init: process.Init.Minimal) !void {
8686 if (mem.findScalar(u8, option_contents, '=')) |name_end| {
8787 const option_name = option_contents[0..name_end];
8888 const option_value = option_contents[name_end + 1 ..];
89 if (try builder.addUserInputOption(option_name, option_value))
90 fatal(" access the help menu with 'zig build -h'", .{});
89 if (try builder.addUserInputOption(option_name, option_value)) {
90 log.info("to access the help menu: zig build -h", .{});
91 process.exit(1);
92 }
9193 } else {
92 if (try builder.addUserInputFlag(option_contents))
93 fatal(" access the help menu with 'zig build -h'", .{});
94 if (try builder.addUserInputFlag(option_contents)) {
95 log.info("to access the help menu: zig build -h", .{});
96 process.exit(1);
97 }
9498 }
9599 } else if (mem.cutPrefix(u8, arg, "-fsys=")) |name| {
96100 try graph.system_integration_options.put(arena, name, .user_enabled);
......@@ -133,8 +137,25 @@ pub fn main(init: process.Init.Minimal) !void {
133137
134138 builder.runPackageScript(root);
135139
136 if (builder.validateUserInputDidItFail()) {
137 fatal(" access the help menu with 'zig build -h'", .{});
140 // Even though the root package's user input options are not serialized,
141 // this is done for consistency, since the rest of the dependency tree
142 // sorts user_input_options before calling validateUserInputDidItFail,
143 // which has user-visible behavior (the order of errors reported).
144 std.Build.PackageOptions.sort(&builder.user_input_options);
145
146 // Make sure the package actually provides all the arguments specified.
147 for (builder.user_input_options.keys()) |name| {
148 if (!builder.available_options_map.contains(name)) {
149 log.err("invalid option: {q}", .{name});
150 builder.invalid_user_input = true;
151 }
152 }
153 if (builder.invalid_user_input) {
154 for (builder.available_options_map.keys(), builder.available_options_map.values()) |name, *available| {
155 log.info("available option: {q}: {s}", .{ name, available.description });
156 }
157 log.info("to access the help menu: zig build -h", .{});
158 process.exit(1);
138159 }
139160
140161 try Serialize.packageOptions(builder, &graph.wip_configuration);
lib/std/Build.zig+295-489
......@@ -9,7 +9,6 @@ const mem = std.mem;
99const panic = std.debug.panic;
1010const assert = std.debug.assert;
1111const log = std.log;
12const StringHashMap = std.StringHashMap;
1312const Allocator = std.mem.Allocator;
1413const Target = std.Target;
1514const process = std.process;
......@@ -32,9 +31,6 @@ graph: *Graph,
3231install_tls: Step.TopLevel,
3332uninstall_tls: Step.TopLevel,
3433allocator: Allocator,
35user_input_options: UserInputOptionsMap,
36available_options_map: std.array_hash_map.String(AvailableOption) = .empty,
37invalid_user_input: bool,
3834default_step: *Step,
3935top_level_steps: std.array_hash_map.String(*Step.TopLevel),
4036/// Path to the directory containing build.zig.
......@@ -45,6 +41,10 @@ debug_log_scopes: []const []const u8 = &.{},
4541/// Set to 0 to disable stack collection.
4642debug_stack_frames_count: u8 = 8,
4743
44user_input_options: PackageOptions.Map,
45available_options_map: std.array_hash_map.String(AvailableOption) = .empty,
46invalid_user_input: bool,
47
4848dep_prefix: []const u8 = "",
4949
5050modules: std.array_hash_map.String(*Module),
......@@ -82,7 +82,7 @@ pub const Graph = struct {
8282 needed_lazy_dependencies: std.array_hash_map.String(void) = .empty,
8383 /// Information about the native target. Computed before build() is invoked.
8484 host: ResolvedTarget,
85 dependency_cache: InitializedDepMap = .empty,
85 dependency_cache: PackageInstanceMap = .empty,
8686 allow_so_scripts: ?bool = null,
8787 time_report: bool = false,
8888 verbose: bool = false,
......@@ -238,65 +238,123 @@ pub const SystemLibraryMode = enum {
238238 declared_enabled,
239239};
240240
241const InitializedDepMap = std.HashMapUnmanaged(InitializedDepKey, *Dependency, InitializedDepContext, std.hash_map.default_max_load_percentage);
242const InitializedDepKey = struct {
243 build_root_string: []const u8,
244 user_input_options: UserInputOptionsMap,
245};
246
247const InitializedDepContext = struct {
248 allocator: Allocator,
249
250 pub fn hash(ctx: @This(), k: InitializedDepKey) u64 {
241const PackageInstanceMap = std.array_hash_map.Custom(PackageInstanceKey, *Dependency, struct {
242 pub fn hash(_: @This(), k: PackageInstanceKey) u32 {
251243 var hasher = std.hash.Wyhash.init(0);
252 hasher.update(k.build_root_string);
253 hashUserInputOptionsMap(ctx.allocator, k.user_input_options, &hasher);
254 return hasher.final();
244 hasher.update(k.pkg_hash);
245 for (k.options.keys(), k.options.values()) |option_key, option_value| {
246 hasher.update(option_key);
247 option_value.hash(&hasher);
248 }
249 return @truncate(hasher.final());
250 }
251
252 pub fn eql(_: @This(), a: PackageInstanceKey, b: PackageInstanceKey, _: usize) bool {
253 if (!mem.eql(u8, a.pkg_hash, b.pkg_hash)) return false;
254 if (a.options.count() != b.options.count()) return false;
255 for (
256 a.options.keys(),
257 b.options.keys(),
258 a.options.values(),
259 b.options.values(),
260 ) |a_key, b_key, a_val, b_val| {
261 if (!mem.eql(u8, a_key, b_key)) return false;
262 if (!a_val.eql(b_val)) return false;
263 }
264 return true;
255265 }
266}, true);
256267
257 pub fn eql(_: @This(), lhs: InitializedDepKey, rhs: InitializedDepKey) bool {
258 if (!std.mem.eql(u8, lhs.build_root_string, rhs.build_root_string))
259 return false;
268const PackageInstanceKey = struct {
269 pkg_hash: []const u8,
270 options: *const PackageOptions.Map,
271};
260272
261 if (lhs.user_input_options.count() != rhs.user_input_options.count())
262 return false;
273/// Build system implementation details.
274pub const PackageOptions = struct {
275 pub const Map = std.array_hash_map.String(UserProvided);
276
277 pub const UserProvided = union(enum) {
278 flag: void,
279 scalar: []const u8,
280 list: std.ArrayList([]const u8),
281 map: std.array_hash_map.String(*const UserProvided),
282 lazy_path: LazyPath,
283 lazy_path_list: std.ArrayList(LazyPath),
284
285 fn eql(a: UserProvided, b: UserProvided) bool {
286 if (std.meta.activeTag(a) != b) return false;
287 return switch (a) {
288 .flag => true,
289 .scalar => |a_scalar| return mem.eql(u8, a_scalar, b.scalar),
290 .list => |a_list| {
291 if (a_list.items.len != b.list.items.len) return false;
292 for (a_list.items, b.list.items) |a_elem, b_elem| {
293 if (!mem.eql(u8, a_elem, b_elem))
294 return false;
295 }
296 return true;
297 },
298 .map => |a_map| {
299 if (a_map.count() != b.map.count()) return false;
300 for (a_map.keys(), a_map.values(), b.map.keys(), b.map.values()) |a_key, a_val, b_key, b_val| {
301 if (!mem.eql(u8, a_key, b_key)) return false;
302 if (!a_val.eql(b_val.*)) return false;
303 }
304 return true;
305 },
306 .lazy_path => |a_lazy_path| return a_lazy_path.eql(b.lazy_path),
307 .lazy_path_list => |a_lazy_path_list| {
308 if (a_lazy_path_list.items.len != b.lazy_path_list.items.len) return false;
309 for (a_lazy_path_list.items, b.lazy_path_list.items) |a_lp, b_lp| {
310 if (!a_lp.eql(b_lp)) return false;
311 }
312 return true;
313 },
314 };
315 }
316
317 fn hash(a: UserProvided, hasher: *std.hash.Wyhash) void {
318 hasher.update(&mem.toBytes(std.meta.activeTag(a)));
319 switch (a) {
320 .flag => {},
321 .scalar => |scalar| hasher.update(scalar),
322 .list => |*list| for (list.items) |elem| hasher.update(elem),
323 .map => |*map| for (map.keys(), map.values()) |key, val| {
324 hasher.update(key);
325 val.hash(hasher);
326 },
327 .lazy_path => |lp| lp.hash(hasher),
328 .lazy_path_list => |*list| for (list.items) |lp| lp.hash(hasher),
329 }
330 }
331 };
263332
264 var it = lhs.user_input_options.iterator();
265 while (it.next()) |lhs_entry| {
266 const rhs_value = rhs.user_input_options.get(lhs_entry.key_ptr.*) orelse return false;
267 if (!userValuesAreSame(lhs_entry.value_ptr.*.value, rhs_value.value))
268 return false;
333 fn fromArgs(arena: Allocator, map: *PackageOptions.Map, args: anytype) void {
334 const args_info = @typeInfo(@TypeOf(args)).@"struct";
335 inline for (args_info.field_names, args_info.field_types) |field_name, field_type| {
336 if (field_type == @TypeOf(null)) continue;
337 addPackageOptionFromArg(arena, map, field_name, field_type, @field(args, field_name));
269338 }
339 }
270340
271 return true;
341 pub fn sort(map: *Map) void {
342 map.sortUnstable(@as(struct {
343 keys: []const []const u8,
344 pub fn lessThan(this: @This(), a_index: usize, b_index: usize) bool {
345 return mem.lessThan(u8, this.keys[a_index], this.keys[b_index]);
346 }
347 }, .{ .keys = map.keys() }));
272348 }
273349};
274350
275pub const UserInputOptionsMap = StringHashMap(UserInputOption);
276
277351const AvailableOption = struct {
278 name: []const u8,
279352 type_id: Configuration.AvailableOption.Type,
280353 description: []const u8,
281354 /// If the `type_id` is `enum` or `enum_list` this provides the list of enum options
282355 enum_options: ?[]const []const u8,
283356};
284357
285pub const UserInputOption = struct {
286 name: []const u8,
287 value: UserValue,
288 used: bool,
289};
290
291pub const UserValue = union(enum) {
292 flag: void,
293 scalar: []const u8,
294 list: std.array_list.Managed([]const u8),
295 map: StringHashMap(*const UserValue),
296 lazy_path: LazyPath,
297 lazy_path_list: std.array_list.Managed(LazyPath),
298};
299
300358/// Build system implementation detail.
301359pub fn create(
302360 graph: *Graph,
......@@ -311,7 +369,7 @@ pub fn create(
311369 .root = root,
312370 .invalid_user_input = false,
313371 .allocator = arena,
314 .user_input_options = UserInputOptionsMap.init(arena),
372 .user_input_options = .empty,
315373 .top_level_steps = .{},
316374 .default_step = undefined,
317375 .install_tls = .{
......@@ -348,7 +406,7 @@ fn createChild(
348406 root: Cache.Path,
349407 pkg_hash: []const u8,
350408 pkg_deps: AvailableDeps,
351 user_input_options: UserInputOptionsMap,
409 user_input_options: PackageOptions.Map,
352410) error{OutOfMemory}!*Build {
353411 const arena = parent.graph.arena;
354412 const child = try arena.create(Build);
......@@ -390,167 +448,96 @@ fn createChild(
390448 return child;
391449}
392450
393fn userInputOptionsFromArgs(arena: Allocator, args: anytype) UserInputOptionsMap {
394 var map = UserInputOptionsMap.init(arena);
395 const args_info = @typeInfo(@TypeOf(args)).@"struct";
396 inline for (args_info.field_names, args_info.field_types) |field_name, field_type| {
397 if (field_type == @TypeOf(null)) continue;
398 addUserInputOptionFromArg(arena, &map, field_name, field_type, @field(args, field_name));
399 }
400 return map;
401}
402
403fn addUserInputOptionFromArg(
451fn addPackageOptionFromArg(
404452 arena: Allocator,
405 map: *UserInputOptionsMap,
453 map: *PackageOptions.Map,
406454 field_name: [:0]const u8,
407455 comptime T: type,
408456 /// If null, the value won't be added, but `T` will still be type-checked.
409457 maybe_value: ?T,
410458) void {
459 map.ensureUnusedCapacity(arena, 2) catch @panic("OOM");
411460 switch (T) {
412461 Target.Query => return if (maybe_value) |v| {
413 map.put(field_name, .{
414 .name = field_name,
415 .value = .{ .scalar = v.zigTriple(arena) catch @panic("OOM") },
416 .used = false,
417 }) catch @panic("OOM");
418 map.put("cpu", .{
419 .name = "cpu",
420 .value = .{ .scalar = v.serializeCpuAlloc(arena) catch @panic("OOM") },
421 .used = false,
422 }) catch @panic("OOM");
462 map.putAssumeCapacity(field_name, .{ .scalar = v.zigTriple(arena) catch @panic("OOM") });
463 map.putAssumeCapacity("cpu", .{ .scalar = v.serializeCpuAlloc(arena) catch @panic("OOM") });
423464 },
424465 ResolvedTarget => return if (maybe_value) |v| {
425 map.put(field_name, .{
426 .name = field_name,
427 .value = .{ .scalar = v.query.zigTriple(arena) catch @panic("OOM") },
428 .used = false,
429 }) catch @panic("OOM");
430 map.put("cpu", .{
431 .name = "cpu",
432 .value = .{ .scalar = v.query.serializeCpuAlloc(arena) catch @panic("OOM") },
433 .used = false,
434 }) catch @panic("OOM");
466 map.putAssumeCapacity(field_name, .{ .scalar = v.query.zigTriple(arena) catch @panic("OOM") });
467 map.putAssumeCapacity("cpu", .{ .scalar = v.query.serializeCpuAlloc(arena) catch @panic("OOM") });
435468 },
436469 std.zig.BuildId => return if (maybe_value) |v| {
437 map.put(field_name, .{
438 .name = field_name,
439 .value = .{ .scalar = std.fmt.allocPrint(arena, "{f}", .{v}) catch @panic("OOM") },
440 .used = false,
441 }) catch @panic("OOM");
470 map.putAssumeCapacity(field_name, .{
471 .scalar = std.fmt.allocPrint(arena, "{f}", .{v}) catch @panic("OOM"),
472 });
442473 },
443474 LazyPath => return if (maybe_value) |v| {
444 map.put(field_name, .{
445 .name = field_name,
446 .value = .{ .lazy_path = v.dupeInner(arena) },
447 .used = false,
448 }) catch @panic("OOM");
475 map.putAssumeCapacity(field_name, .{ .lazy_path = v.dupeInner(arena) });
449476 },
450477 []const LazyPath => return if (maybe_value) |v| {
451 var list = std.array_list.Managed(LazyPath).initCapacity(arena, v.len) catch @panic("OOM");
452 for (v) |lp| list.appendAssumeCapacity(lp.dupeInner(arena));
453 map.put(field_name, .{
454 .name = field_name,
455 .value = .{ .lazy_path_list = list },
456 .used = false,
457 }) catch @panic("OOM");
478 var list: std.ArrayList(LazyPath) = .empty;
479 const elems = list.addManyAsSlice(arena, v.len) catch @panic("OOM");
480 for (v, elems) |lp, *elem| elem.* = lp.dupeInner(arena);
481 map.putAssumeCapacity(field_name, .{ .lazy_path_list = list });
458482 },
459483 []const u8 => return if (maybe_value) |v| {
460 map.put(field_name, .{
461 .name = field_name,
462 .value = .{ .scalar = arena.dupe(u8, v) catch @panic("OOM") },
463 .used = false,
464 }) catch @panic("OOM");
484 map.putAssumeCapacity(field_name, .{ .scalar = arena.dupe(u8, v) catch @panic("OOM") });
465485 },
466486 []const []const u8 => return if (maybe_value) |v| {
467 var list = std.array_list.Managed([]const u8).initCapacity(arena, v.len) catch @panic("OOM");
468 for (v) |s| list.appendAssumeCapacity(arena.dupe(u8, s) catch @panic("OOM"));
469 map.put(field_name, .{
470 .name = field_name,
471 .value = .{ .list = list },
472 .used = false,
473 }) catch @panic("OOM");
487 var list: std.ArrayList([]const u8) = .empty;
488 const elems = list.addManyAsSlice(arena, v.len) catch @panic("OOM");
489 for (v, elems) |s, *elem| elem.* = arena.dupe(u8, s) catch @panic("OOM");
490 map.putAssumeCapacity(field_name, .{ .list = list });
474491 },
475492 else => switch (@typeInfo(T)) {
476493 .bool => return if (maybe_value) |v| {
477 map.put(field_name, .{
478 .name = field_name,
479 .value = .{ .scalar = if (v) "true" else "false" },
480 .used = false,
481 }) catch @panic("OOM");
494 map.putAssumeCapacity(field_name, .{ .scalar = if (v) "true" else "false" });
482495 },
483496 .@"enum", .enum_literal => return if (maybe_value) |v| {
484 map.put(field_name, .{
485 .name = field_name,
486 .value = .{ .scalar = @tagName(v) },
487 .used = false,
488 }) catch @panic("OOM");
497 map.putAssumeCapacity(field_name, .{ .scalar = @tagName(v) });
489498 },
490499 .comptime_int, .int => return if (maybe_value) |v| {
491 map.put(field_name, .{
492 .name = field_name,
493 .value = .{ .scalar = std.fmt.allocPrint(arena, "{d}", .{v}) catch @panic("OOM") },
494 .used = false,
495 }) catch @panic("OOM");
500 map.putAssumeCapacity(field_name, .{
501 .scalar = std.fmt.allocPrint(arena, "{d}", .{v}) catch @panic("OOM"),
502 });
496503 },
497504 .comptime_float, .float => return if (maybe_value) |v| {
498 map.put(field_name, .{
499 .name = field_name,
500 .value = .{ .scalar = std.fmt.allocPrint(arena, "{x}", .{v}) catch @panic("OOM") },
501 .used = false,
502 }) catch @panic("OOM");
505 map.putAssumeCapacity(field_name, .{
506 .scalar = std.fmt.allocPrint(arena, "{x}", .{v}) catch @panic("OOM"),
507 });
503508 },
504509 .pointer => |ptr_info| switch (ptr_info.size) {
505510 .one => switch (@typeInfo(ptr_info.child)) {
506 .array => |array_info| {
507 addUserInputOptionFromArg(
508 arena,
509 map,
510 field_name,
511 @Pointer(.slice, .{ .@"const" = true }, array_info.child, null),
512 maybe_value orelse null,
513 );
514 return;
515 },
511 .array => |array_info| return addPackageOptionFromArg(
512 arena,
513 map,
514 field_name,
515 @Pointer(.slice, .{ .@"const" = true }, array_info.child, null),
516 maybe_value orelse null,
517 ),
516518 else => {},
517519 },
518520 .slice => switch (@typeInfo(ptr_info.child)) {
519521 .@"enum" => return if (maybe_value) |v| {
520 var list = std.array_list.Managed([]const u8).initCapacity(arena, v.len) catch @panic("OOM");
521 for (v) |tag| list.appendAssumeCapacity(@tagName(tag));
522 map.put(field_name, .{
523 .name = field_name,
524 .value = .{ .list = list },
525 .used = false,
526 }) catch @panic("OOM");
527 },
528 else => {
529 addUserInputOptionFromArg(
530 arena,
531 map,
532 field_name,
533 @Pointer(ptr_info.size, .{ .@"const" = true }, ptr_info.child, null),
534 maybe_value orelse null,
535 );
536 return;
522 var list: std.ArrayList([]const u8) = .empty;
523 const elems = list.addManyAsSlice(arena, v.len) catch @panic("OOM");
524 for (elems, v) |*elem, tag| elem.* = @tagName(tag);
525 map.putAssumeCapacity(field_name, .{ .list = list });
537526 },
527 else => return addPackageOptionFromArg(
528 arena,
529 map,
530 field_name,
531 @Pointer(ptr_info.size, .{ .@"const" = true }, ptr_info.child, null),
532 maybe_value orelse null,
533 ),
538534 },
539535 else => {},
540536 },
541537 .null => unreachable,
542538 .optional => |info| switch (@typeInfo(info.child)) {
543539 .optional => {},
544 else => {
545 addUserInputOptionFromArg(
546 arena,
547 map,
548 field_name,
549 info.child,
550 maybe_value orelse null,
551 );
552 return;
553 },
540 else => return addPackageOptionFromArg(arena, map, field_name, info.child, maybe_value orelse null),
554541 },
555542 else => {},
556543 },
......@@ -558,130 +545,6 @@ fn addUserInputOptionFromArg(
558545 @compileError("option '" ++ field_name ++ "' has unsupported type: " ++ @typeName(T));
559546}
560547
561const OrderedUserValue = union(enum) {
562 flag: void,
563 scalar: []const u8,
564 list: std.array_list.Managed([]const u8),
565 map: std.array_list.Managed(Pair),
566 lazy_path: LazyPath,
567 lazy_path_list: std.array_list.Managed(LazyPath),
568
569 const Pair = struct {
570 name: []const u8,
571 value: OrderedUserValue,
572 fn lessThan(_: void, lhs: Pair, rhs: Pair) bool {
573 return std.ascii.lessThanIgnoreCase(lhs.name, rhs.name);
574 }
575 };
576
577 fn hash(val: OrderedUserValue, hasher: *std.hash.Wyhash) void {
578 hasher.update(&std.mem.toBytes(std.meta.activeTag(val)));
579 switch (val) {
580 .flag => {},
581 .scalar => |scalar| hasher.update(scalar),
582 // lists are already ordered
583 .list => |list| for (list.items) |list_entry|
584 hasher.update(list_entry),
585 .map => |map| for (map.items) |map_entry| {
586 hasher.update(map_entry.name);
587 map_entry.value.hash(hasher);
588 },
589 .lazy_path => |lp| hashLazyPath(lp, hasher),
590 .lazy_path_list => |lp_list| for (lp_list.items) |lp| {
591 hashLazyPath(lp, hasher);
592 },
593 }
594 }
595
596 fn hashLazyPath(lp: LazyPath, hasher: *std.hash.Wyhash) void {
597 switch (lp) {
598 .src_path => |sp| {
599 hasher.update(sp.owner.pkg_hash);
600 hasher.update(sp.sub_path);
601 },
602 .generated => |gen| {
603 hasher.update(@ptrCast(&gen.index));
604 hasher.update(@ptrCast(&gen.up));
605 hasher.update(gen.sub_path);
606 },
607 .cwd_relative => |rel_path| {
608 hasher.update(rel_path);
609 },
610 .relative => |r| {
611 hasher.update(@ptrCast(&r.base));
612 hasher.update(@ptrCast(&r.sub_path));
613 },
614 .dependency => |dep| {
615 hasher.update(dep.dependency.builder.pkg_hash);
616 hasher.update(dep.sub_path);
617 },
618 }
619 }
620
621 fn mapFromUnordered(allocator: Allocator, unordered: std.StringHashMap(*const UserValue)) std.array_list.Managed(Pair) {
622 var ordered = std.array_list.Managed(Pair).init(allocator);
623 var it = unordered.iterator();
624 while (it.next()) |entry| {
625 ordered.append(.{
626 .name = entry.key_ptr.*,
627 .value = OrderedUserValue.fromUnordered(allocator, entry.value_ptr.*.*),
628 }) catch @panic("OOM");
629 }
630
631 std.mem.sortUnstable(Pair, ordered.items, {}, Pair.lessThan);
632 return ordered;
633 }
634
635 fn fromUnordered(allocator: Allocator, unordered: UserValue) OrderedUserValue {
636 return switch (unordered) {
637 .flag => .{ .flag = {} },
638 .scalar => |scalar| .{ .scalar = scalar },
639 .list => |list| .{ .list = list },
640 .map => |map| .{ .map = OrderedUserValue.mapFromUnordered(allocator, map) },
641 .lazy_path => |lp| .{ .lazy_path = lp },
642 .lazy_path_list => |list| .{ .lazy_path_list = list },
643 };
644 }
645};
646
647const OrderedUserInputOption = struct {
648 name: []const u8,
649 value: OrderedUserValue,
650 used: bool,
651
652 fn hash(opt: OrderedUserInputOption, hasher: *std.hash.Wyhash) void {
653 hasher.update(opt.name);
654 opt.value.hash(hasher);
655 }
656
657 fn fromUnordered(allocator: Allocator, user_input_option: UserInputOption) OrderedUserInputOption {
658 return OrderedUserInputOption{
659 .name = user_input_option.name,
660 .used = user_input_option.used,
661 .value = OrderedUserValue.fromUnordered(allocator, user_input_option.value),
662 };
663 }
664
665 fn lessThan(_: void, lhs: OrderedUserInputOption, rhs: OrderedUserInputOption) bool {
666 return std.ascii.lessThanIgnoreCase(lhs.name, rhs.name);
667 }
668};
669
670// The hash should be consistent with the same values given a different order.
671// This function takes a user input map, orders it, then hashes the contents.
672fn hashUserInputOptionsMap(allocator: Allocator, user_input_options: UserInputOptionsMap, hasher: *std.hash.Wyhash) void {
673 var ordered = std.array_list.Managed(OrderedUserInputOption).init(allocator);
674 var it = user_input_options.iterator();
675 while (it.next()) |entry|
676 ordered.append(OrderedUserInputOption.fromUnordered(allocator, entry.value_ptr.*)) catch @panic("OOM");
677
678 std.mem.sortUnstable(OrderedUserInputOption, ordered.items, {}, OrderedUserInputOption.lessThan);
679
680 // juice it
681 for (ordered.items) |user_option|
682 user_option.hash(hasher);
683}
684
685548/// Create a set of key-value pairs that can be converted into a Zig source
686549/// file and then inserted into a Zig compilation's module table for importing.
687550///
......@@ -1107,31 +970,20 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
1107970 const name = graph.dupeString(name_raw);
1108971 const description = graph.dupeString(description_raw);
1109972 const type_id = comptime typeToEnum(T);
1110 const enum_options = if (type_id == .@"enum" or type_id == .enum_list) blk: {
1111 const EnumType = if (type_id == .enum_list) @typeInfo(T).pointer.child else T;
1112 const field_names = @typeInfo(EnumType).@"enum".field_names;
1113 var options = std.array_list.Managed([]const u8).initCapacity(b.allocator, field_names.len) catch @panic("OOM");
1114
1115 inline for (field_names) |field_name| {
1116 options.appendAssumeCapacity(field_name);
1117 }
1118
1119 break :blk options.toOwnedSlice() catch @panic("OOM");
1120 } else null;
1121 const available_option = AvailableOption{
1122 .name = name,
973 const available_option: AvailableOption = .{
1123974 .type_id = type_id,
1124975 .description = description,
1125 .enum_options = enum_options,
976 .enum_options = if (type_id == .@"enum" or type_id == .enum_list) blk: {
977 const E = if (type_id == .enum_list) @typeInfo(T).pointer.child else T;
978 break :blk @typeInfo(E).@"enum".field_names;
979 } else null,
1126980 };
1127981 if ((b.available_options_map.fetchPut(arena, name, available_option) catch @panic("OOM")) != null) {
1128982 panic("option {q} declared twice", .{name});
1129983 }
1130
1131 const option_ptr = b.user_input_options.getPtr(name) orelse return null;
1132 option_ptr.used = true;
984 const user_provided = b.user_input_options.get(name) orelse return null;
1133985 switch (type_id) {
1134 .bool => switch (option_ptr.value) {
986 .bool => switch (user_provided) {
1135987 .flag => return true,
1136988 .scalar => |s| {
1137989 if (mem.eql(u8, s, "true")) {
......@@ -1145,14 +997,14 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
1145997 }
1146998 },
1147999 .list, .map, .lazy_path, .lazy_path_list => {
1148 log.err("expected -D{s} to be a boolean; received: {t}", .{ name, option_ptr.value });
1000 log.err("expected -D{s} to be a boolean; received: {t}", .{ name, user_provided });
11491001 b.markInvalidUserInput();
11501002 return null;
11511003 },
11521004 },
1153 .int => switch (option_ptr.value) {
1005 .int => switch (user_provided) {
11541006 .flag, .list, .map, .lazy_path, .lazy_path_list => {
1155 log.err("expected -D{s} to be an integer; received: {t}", .{ name, option_ptr.value });
1007 log.err("expected -D{s} to be an integer; received: {t}", .{ name, user_provided });
11561008 b.markInvalidUserInput();
11571009 return null;
11581010 },
......@@ -1172,9 +1024,9 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
11721024 return n;
11731025 },
11741026 },
1175 .float => switch (option_ptr.value) {
1027 .float => switch (user_provided) {
11761028 .flag, .map, .list, .lazy_path, .lazy_path_list => {
1177 log.err("expected -D{s} to be a float; received: {t}", .{ name, option_ptr.value });
1029 log.err("expected -D{s} to be a float; received: {t}", .{ name, user_provided });
11781030 b.markInvalidUserInput();
11791031 return null;
11801032 },
......@@ -1187,9 +1039,9 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
11871039 return n;
11881040 },
11891041 },
1190 .@"enum" => switch (option_ptr.value) {
1042 .@"enum" => switch (user_provided) {
11911043 .flag, .map, .list, .lazy_path, .lazy_path_list => {
1192 log.err("expected -D{s} to be an enum; received: {t}.", .{ name, option_ptr.value });
1044 log.err("expected -D{s} to be an enum; received: {t}.", .{ name, user_provided });
11931045 b.markInvalidUserInput();
11941046 return null;
11951047 },
......@@ -1206,17 +1058,17 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
12061058 return null;
12071059 },
12081060 },
1209 .string => switch (option_ptr.value) {
1061 .string => switch (user_provided) {
12101062 .flag, .list, .map, .lazy_path, .lazy_path_list => {
1211 log.err("expected -D{s} to be a string; received: {t}", .{ name, option_ptr.value });
1063 log.err("expected -D{s} to be a string; received: {t}", .{ name, user_provided });
12121064 b.markInvalidUserInput();
12131065 return null;
12141066 },
12151067 .scalar => |s| return s,
12161068 },
1217 .build_id => switch (option_ptr.value) {
1069 .build_id => switch (user_provided) {
12181070 .flag, .map, .list, .lazy_path, .lazy_path_list => {
1219 log.err("expected -D{s} to be an enum; received: {t}.", .{ name, option_ptr.value });
1071 log.err("expected -D{s} to be an enum; received: {t}.", .{ name, user_provided });
12201072 b.markInvalidUserInput();
12211073 return null;
12221074 },
......@@ -1230,9 +1082,9 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
12301082 }
12311083 },
12321084 },
1233 .list => switch (option_ptr.value) {
1085 .list => switch (user_provided) {
12341086 .flag, .map, .lazy_path, .lazy_path_list => {
1235 log.err("expected -D{s} to be a list; received: {t}", .{ name, option_ptr.value });
1087 log.err("expected -D{s} to be a list; received: {t}", .{ name, user_provided });
12361088 b.markInvalidUserInput();
12371089 return null;
12381090 },
......@@ -1241,9 +1093,9 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
12411093 },
12421094 .list => |lst| return lst.items,
12431095 },
1244 .enum_list => switch (option_ptr.value) {
1096 .enum_list => switch (user_provided) {
12451097 .flag, .map, .lazy_path, .lazy_path_list => {
1246 log.err("expected -D{s} to be a list; received: {t}", .{ name, option_ptr.value });
1098 log.err("expected -D{s} to be a list; received: {t}", .{ name, user_provided });
12471099 b.markInvalidUserInput();
12481100 return null;
12491101 },
......@@ -1283,16 +1135,16 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
12831135 return new_list;
12841136 },
12851137 },
1286 .lazy_path => switch (option_ptr.value) {
1138 .lazy_path => switch (user_provided) {
12871139 .scalar => |s| return .{ .cwd_relative = s },
12881140 .lazy_path => |lp| return lp,
12891141 .flag, .map, .list, .lazy_path_list => {
1290 log.err("expected -D{s} to be a path; received: {t}", .{ name, option_ptr.value });
1142 log.err("expected -D{s} to be a path; received: {t}", .{ name, user_provided });
12911143 b.markInvalidUserInput();
12921144 return null;
12931145 },
12941146 },
1295 .lazy_path_list => switch (option_ptr.value) {
1147 .lazy_path_list => switch (user_provided) {
12961148 .scalar => |s| return arena.dupe(LazyPath, &[_]LazyPath{.{ .cwd_relative = s }}) catch @panic("OOM"),
12971149 .lazy_path => |lp| return arena.dupe(LazyPath, &[_]LazyPath{lp}) catch @panic("OOM"),
12981150 .list => |lst| {
......@@ -1304,7 +1156,7 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
13041156 },
13051157 .lazy_path_list => |lp_list| return lp_list.items,
13061158 .flag, .map => {
1307 log.err("expected -D{s} to be a path; received: {t}", .{ name, option_ptr.value });
1159 log.err("expected -D{s} to be a path; received: {t}", .{ name, user_provided });
13081160 b.markInvalidUserInput();
13091161 return null;
13101162 },
......@@ -1497,88 +1349,63 @@ pub fn standardTargetOptionsQueryOnly(b: *Build, args: StandardTargetOptionsArgs
14971349}
14981350
14991351/// Build system implementation detail.
1500pub fn addUserInputOption(b: *Build, name_raw: []const u8, value_raw: []const u8) error{OutOfMemory}!bool {
1352pub fn addUserInputOption(b: *Build, name: []const u8, value_raw: []const u8) error{OutOfMemory}!bool {
15011353 const graph = b.graph;
15021354 const arena = graph.arena;
1503 const name = graph.dupeString(name_raw);
15041355 const value = graph.dupeString(value_raw);
1505 const gop = try b.user_input_options.getOrPut(name);
1356 const gop = try b.user_input_options.getOrPut(arena, name);
1357
15061358 if (!gop.found_existing) {
1507 gop.value_ptr.* = UserInputOption{
1508 .name = name,
1509 .value = .{ .scalar = value },
1510 .used = false,
1511 };
1359 gop.key_ptr.* = graph.dupeString(name);
1360 gop.value_ptr.* = .{ .scalar = value };
15121361 return false;
15131362 }
15141363
1515 // option already exists
1516 switch (gop.value_ptr.value) {
1364 // Option already exists.
1365 switch (gop.value_ptr.*) {
15171366 .scalar => |s| {
1518 // turn it into a list
1519 var list = std.array_list.Managed([]const u8).init(arena);
1520 try list.append(s);
1521 try list.append(value);
1522 try b.user_input_options.put(name, .{
1523 .name = name,
1524 .value = .{ .list = list },
1525 .used = false,
1526 });
1527 },
1528 .list => |*list| {
1529 // append to the list
1530 try list.append(value);
1531 try b.user_input_options.put(name, .{
1532 .name = name,
1533 .value = .{ .list = list.* },
1534 .used = false,
1535 });
1367 // Turn it into a list.
1368 var list: std.ArrayList([]const u8) = .empty;
1369 (try list.addManyAsArray(arena, 2)).* = .{ s, value };
1370 gop.value_ptr.* = .{ .list = list };
15361371 },
1372 .list => |*list| try list.append(arena, value),
15371373 .flag => {
1538 log.warn("option '-D{s}={s}' conflicts with flag '-D{s}'.", .{ name, value, name });
1374 log.err("option -D{s}={s} conflicts with flag -D{s}", .{ name, value, name });
15391375 return true;
15401376 },
15411377 .map => |*map| {
15421378 _ = map;
1543 log.warn("TODO maps as command line arguments is not implemented yet.", .{});
1544 return true;
1545 },
1546 .lazy_path, .lazy_path_list => {
1547 log.warn("the lazy path value type isn't added from the CLI, but somehow {q} is a .{f}", .{
1548 name, std.zig.fmtId(@tagName(gop.value_ptr.value)),
1549 });
1550 return true;
1379 unreachable; // TODO implement maps as command line arguments
15511380 },
1381 .lazy_path => unreachable,
1382 .lazy_path_list => unreachable,
15521383 }
15531384 return false;
15541385}
15551386
15561387/// Build system implementation detail.
1557pub fn addUserInputFlag(b: *Build, name_raw: []const u8) error{OutOfMemory}!bool {
1388pub fn addUserInputFlag(b: *Build, name: []const u8) error{OutOfMemory}!bool {
15581389 const graph = b.graph;
1559 const name = graph.dupeString(name_raw);
1560 const gop = try b.user_input_options.getOrPut(name);
1390 const arena = graph.arena;
1391 const gop = try b.user_input_options.getOrPut(arena, name);
15611392 if (!gop.found_existing) {
1562 gop.value_ptr.* = .{
1563 .name = name,
1564 .value = .{ .flag = {} },
1565 .used = false,
1566 };
1393 gop.key_ptr.* = graph.dupeString(name);
1394 gop.value_ptr.* = .{ .flag = {} };
15671395 return false;
15681396 }
1569
1570 // option already exists
1571 switch (gop.value_ptr.value) {
1397 // Option already exists.
1398 switch (gop.value_ptr.*) {
15721399 .scalar => |s| {
1573 log.err("Flag '-D{s}' conflicts with option '-D{s}={s}'.", .{ name, name, s });
1400 log.err("flag -D{s} conflicts with option -D{s}={s}", .{ name, name, s });
15741401 return true;
15751402 },
15761403 .list, .map, .lazy_path_list => {
1577 log.err("Flag '-D{s}' conflicts with multiple options of the same name.", .{name});
1404 log.err("flag -D{s} conflicts with multiple options of the same name", .{name});
15781405 return true;
15791406 },
15801407 .lazy_path => |lp| {
1581 log.err("Flag '-D{s}' conflicts with option '-D{s}={f}'.", .{ name, name, lp });
1408 log.err("flag -D{s} conflicts with option -D{s}={f}", .{ name, name, lp });
15821409 return true;
15831410 },
15841411
......@@ -1614,17 +1441,16 @@ fn markInvalidUserInput(b: *Build) void {
16141441 b.invalid_user_input = true;
16151442}
16161443
1617/// Build system implementation detail.
1618pub fn validateUserInputDidItFail(b: *Build) bool {
1619 // Make sure all args are used.
1620 var it = b.user_input_options.iterator();
1621 while (it.next()) |entry| {
1622 if (!entry.value_ptr.used) {
1623 log.err("invalid option: -D{s}", .{entry.key_ptr.*});
1444fn validateUserInputDidItFail(b: *Build) bool {
1445 for (b.user_input_options.keys()) |name| {
1446 if (!b.available_options_map.contains(name)) {
1447 for (b.available_options_map.keys(), b.available_options_map.values()) |available_name, *available| {
1448 log.info("available option: {q}: {s}", .{ available_name, available.description });
1449 }
1450 log.err("invalid option: {q}", .{name});
16241451 b.markInvalidUserInput();
16251452 }
16261453 }
1627
16281454 return b.invalid_user_input;
16291455}
16301456
......@@ -2092,14 +1918,14 @@ inline fn findImportPkgHashOrFatal(b: *Build, comptime asking_build_zig: type, c
20921918 const pkg = @field(deps.packages, pkg_hash);
20931919 if (@hasDecl(pkg, "build_zig") and pkg.build_zig == asking_build_zig) break .{ pkg_hash, pkg.deps };
20941920 } else .{ "", deps.root_deps };
2095 if (!std.mem.eql(u8, b_pkg_hash, b.pkg_hash)) {
1921 if (!mem.eql(u8, b_pkg_hash, b.pkg_hash)) {
20961922 const build_zig_path = b.root.join(arena, "build.zig") catch @panic("OOM");
20971923 panic("{} is not the struct that corresponds to {f}", .{
20981924 asking_build_zig, build_zig_path,
20991925 });
21001926 }
21011927 comptime for (b_pkg_deps) |dep| {
2102 if (std.mem.eql(u8, dep[0], dep_name)) return dep[1];
1928 if (mem.eql(u8, dep[0], dep_name)) return dep[1];
21031929 };
21041930
21051931 const full_path = b.root.join(arena, "build.zig.zon") catch @panic("OOM");
......@@ -2141,7 +1967,9 @@ pub fn dependencyLazy(b: *Build, name: []const u8, args: anytype) error{LazyDepe
21411967 markNeededLazyDep(b, pkg_hash);
21421968 return error.LazyDependencyNeeded;
21431969 }
2144 return dependencyResolved(b, name, entry, userInputOptionsFromArgs(b.graph.arena, args));
1970 var map: PackageOptions.Map = .empty;
1971 PackageOptions.fromArgs(b.graph.arena, &map, args);
1972 return dependencyResolved(b, name, entry, &map);
21451973}
21461974
21471975pub const PackageEntry = struct {
......@@ -2232,12 +2060,14 @@ pub inline fn lazyImport(
22322060 comptime unreachable; // Bad @dependencies source
22332061}
22342062
2235fn pkgHashFromBuildZig(comptime build_zig: type) ?[]const u8 {
2236 const deps = @import("root").dependencies;
2237 return comptime for (@typeInfo(deps.packages).@"struct".decl_names) |pkg_hash| {
2238 const pkg = @field(deps.packages, pkg_hash);
2239 if (@hasDecl(pkg, "build_zig") and pkg.build_zig == build_zig) break pkg_hash;
2240 } else null;
2063inline fn pkgHashFromBuildZig(comptime build_zig: type) ?[]const u8 {
2064 comptime {
2065 const deps = @import("root").dependencies;
2066 return for (@typeInfo(deps.packages).@"struct".decl_names) |pkg_hash| {
2067 const pkg = @field(deps.packages, pkg_hash);
2068 if (@hasDecl(pkg, "build_zig") and pkg.build_zig == build_zig) break pkg_hash;
2069 } else null;
2070 }
22412071}
22422072
22432073/// Build system implementation detail.
......@@ -2251,114 +2081,37 @@ pub fn dependencyFromBuildZig(
22512081 const arena = b.graph.arena;
22522082
22532083 find_dep: {
2254 const pkg_hash = comptime pkgHashFromBuildZig(build_zig) orelse break :find_dep;
2084 const pkg_hash = pkgHashFromBuildZig(build_zig) orelse break :find_dep;
22552085 const dep_name = for (b.available_deps) |dep| {
22562086 if (mem.eql(u8, dep[1], pkg_hash)) break dep[1];
22572087 } else break :find_dep;
22582088 const entry = package_map.get(pkg_hash) orelse break :find_dep;
2259 return dependencyResolved(b, dep_name, entry, userInputOptionsFromArgs(arena, args));
2089 var map: PackageOptions.Map = .empty;
2090 PackageOptions.fromArgs(arena, &map, args);
2091 return dependencyResolved(b, dep_name, entry, &map);
22602092 }
22612093
22622094 const full_path = b.root.join(arena, "build.zig.zon") catch @panic("OOM");
22632095 panic("{} is not a build.zig struct of a dependency in {f}", .{ build_zig, full_path });
22642096}
22652097
2266fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool {
2267 if (std.meta.activeTag(lhs) != rhs) return false;
2268 switch (lhs) {
2269 .flag => {},
2270 .scalar => |lhs_scalar| {
2271 const rhs_scalar = rhs.scalar;
2272
2273 if (!std.mem.eql(u8, lhs_scalar, rhs_scalar))
2274 return false;
2275 },
2276 .list => |lhs_list| {
2277 const rhs_list = rhs.list;
2278
2279 if (lhs_list.items.len != rhs_list.items.len)
2280 return false;
2281
2282 for (lhs_list.items, rhs_list.items) |lhs_list_entry, rhs_list_entry| {
2283 if (!std.mem.eql(u8, lhs_list_entry, rhs_list_entry))
2284 return false;
2285 }
2286 },
2287 .map => |lhs_map| {
2288 const rhs_map = rhs.map;
2289
2290 if (lhs_map.count() != rhs_map.count())
2291 return false;
2292
2293 var lhs_it = lhs_map.iterator();
2294 while (lhs_it.next()) |lhs_entry| {
2295 const rhs_value = rhs_map.get(lhs_entry.key_ptr.*) orelse return false;
2296 if (!userValuesAreSame(lhs_entry.value_ptr.*.*, rhs_value.*))
2297 return false;
2298 }
2299 },
2300 .lazy_path => |lhs_lp| {
2301 const rhs_lp = rhs.lazy_path;
2302 return userLazyPathsAreTheSame(lhs_lp, rhs_lp);
2303 },
2304 .lazy_path_list => |lhs_lp_list| {
2305 const rhs_lp_list = rhs.lazy_path_list;
2306 if (lhs_lp_list.items.len != rhs_lp_list.items.len) return false;
2307 for (lhs_lp_list.items, rhs_lp_list.items) |lhs_lp, rhs_lp| {
2308 if (!userLazyPathsAreTheSame(lhs_lp, rhs_lp)) return false;
2309 }
2310 return true;
2311 },
2312 }
2313
2314 return true;
2315}
2316
2317fn userLazyPathsAreTheSame(lhs_lp: LazyPath, rhs_lp: LazyPath) bool {
2318 if (std.meta.activeTag(lhs_lp) != rhs_lp) return false;
2319 switch (lhs_lp) {
2320 .src_path => |lhs_sp| {
2321 const rhs_sp = rhs_lp.src_path;
2322
2323 if (lhs_sp.owner != rhs_sp.owner) return false;
2324 if (std.mem.eql(u8, lhs_sp.sub_path, rhs_sp.sub_path)) return false;
2325 },
2326 .generated => |*lhs_gen| {
2327 const rhs_gen = &rhs_lp.generated;
2328
2329 if (lhs_gen.index != rhs_gen.index) return false;
2330 if (lhs_gen.up != rhs_gen.up) return false;
2331 if (std.mem.eql(u8, lhs_gen.sub_path, rhs_gen.sub_path)) return false;
2332 },
2333 .cwd_relative => |lhs_rel_path| {
2334 const rhs_rel_path = rhs_lp.cwd_relative;
2335
2336 if (!std.mem.eql(u8, lhs_rel_path, rhs_rel_path)) return false;
2337 },
2338 .relative => |lhs| return lhs.eql(rhs_lp.relative),
2339 .dependency => |lhs_dep| {
2340 const rhs_dep = rhs_lp.dependency;
2341
2342 if (lhs_dep.dependency != rhs_dep.dependency) return false;
2343 if (!std.mem.eql(u8, lhs_dep.sub_path, rhs_dep.sub_path)) return false;
2344 },
2345 }
2346 return true;
2347}
2348
2098/// Takes ownership of `package_options`, which may be unsorted.
23492099fn dependencyResolved(
23502100 b: *Build,
23512101 name: []const u8,
23522102 entry: PackageEntry,
2353 user_input_options: UserInputOptionsMap,
2103 package_options: *PackageOptions.Map,
23542104) *Dependency {
23552105 const graph = b.graph;
23562106 const io = graph.io;
23572107 const arena = graph.arena;
2108
2109 PackageOptions.sort(package_options);
2110
23582111 if (graph.dependency_cache.getContext(.{
2359 .build_root_string = entry.build_root,
2360 .user_input_options = user_input_options,
2361 }, .{ .allocator = arena })) |dep| return dep;
2112 .pkg_hash = entry.hash,
2113 .options = package_options,
2114 }, .{})) |dep| return dep;
23622115
23632116 const dep_root: Cache.Path = .{
23642117 .root_dir = .{
......@@ -2368,7 +2121,7 @@ fn dependencyResolved(
23682121 },
23692122 };
23702123
2371 const sub_builder = b.createChild(name, dep_root, entry.hash, entry.deps, user_input_options) catch @panic("OOM");
2124 const sub_builder = b.createChild(name, dep_root, entry.hash, entry.deps, package_options.*) catch @panic("OOM");
23722125 if (entry.run_build) |run_build| {
23732126 run_build(sub_builder);
23742127
......@@ -2381,9 +2134,9 @@ fn dependencyResolved(
23812134 dep.* = .{ .builder = sub_builder };
23822135
23832136 graph.dependency_cache.putContext(arena, .{
2384 .build_root_string = entry.build_root,
2385 .user_input_options = user_input_options,
2386 }, dep, .{ .allocator = arena }) catch @panic("OOM");
2137 .pkg_hash = entry.hash,
2138 .options = &sub_builder.user_input_options,
2139 }, dep, .{}) catch @panic("OOM");
23872140 return dep;
23882141}
23892142
......@@ -2650,6 +2403,59 @@ pub const LazyPath = union(enum) {
26502403 } },
26512404 };
26522405 }
2406
2407 fn eql(a: LazyPath, b: LazyPath) bool {
2408 if (std.meta.activeTag(a) != b) return false;
2409 switch (a) {
2410 .src_path => |a_sp| {
2411 const b_sp = b.src_path;
2412 if (a_sp.owner != b_sp.owner) return false;
2413 if (mem.eql(u8, a_sp.sub_path, b_sp.sub_path)) return false;
2414 },
2415 .generated => |*a_gen| {
2416 const b_gen = &b.generated;
2417 if (a_gen.index != b_gen.index) return false;
2418 if (a_gen.up != b_gen.up) return false;
2419 if (mem.eql(u8, a_gen.sub_path, b_gen.sub_path)) return false;
2420 },
2421 .cwd_relative => |a_rel_path| {
2422 const b_rel_path = b.cwd_relative;
2423 if (!mem.eql(u8, a_rel_path, b_rel_path)) return false;
2424 },
2425 .relative => |a_relative| return a_relative.eql(b.relative),
2426 .dependency => |a_dep| {
2427 const b_dep = b.dependency;
2428 if (a_dep.dependency != b_dep.dependency) return false;
2429 if (!mem.eql(u8, a_dep.sub_path, b_dep.sub_path)) return false;
2430 },
2431 }
2432 return true;
2433 }
2434
2435 fn hash(lp: LazyPath, hasher: *std.hash.Wyhash) void {
2436 switch (lp) {
2437 .src_path => |sp| {
2438 hasher.update(sp.owner.pkg_hash);
2439 hasher.update(sp.sub_path);
2440 },
2441 .generated => |gen| {
2442 hasher.update(@ptrCast(&gen.index));
2443 hasher.update(@ptrCast(&gen.up));
2444 hasher.update(gen.sub_path);
2445 },
2446 .cwd_relative => |rel_path| {
2447 hasher.update(rel_path);
2448 },
2449 .relative => |r| {
2450 hasher.update(@ptrCast(&r.base));
2451 hasher.update(@ptrCast(&r.sub_path));
2452 },
2453 .dependency => |dep| {
2454 hasher.update(dep.dependency.builder.pkg_hash);
2455 hasher.update(dep.sub_path);
2456 },
2457 }
2458 }
26532459};
26542460
26552461fn dumpBadDirnameHelp(
lib/std/Build/Configuration.zig-74
......@@ -1677,82 +1677,8 @@ pub const Package = extern struct {
16771677
16781678 pub const Instance = extern struct {
16791679 package: Package.Index,
1680 user_input_options: UserInputOption.List.Index,
16811680 modules: PublicModules,
16821681
1683 pub const UserInputOption = struct {
1684 flags: Flags,
1685 name: String,
1686 value: Storage.FlagUnion(.flags, .tag, UserValue),
1687
1688 pub const Flags = packed struct(u32) {
1689 tag: UserValue.Tag,
1690 used: bool,
1691 _: u28 = 0,
1692 };
1693
1694 pub const List = struct {
1695 options: Storage.LengthPrefixedList(UserInputOption.Index),
1696
1697 pub const Index = enum(u32) {
1698 _,
1699
1700 pub fn get(this: @This(), c: *const Configuration) List {
1701 return extraData(c, List, @backingInt(this));
1702 }
1703
1704 pub fn slice(this: @This(), c: *const Configuration) []const UserInputOption.Index {
1705 return this.get(c).options.slice;
1706 }
1707 };
1708 };
1709
1710 pub const Index = IndexType(@This());
1711 };
1712
1713 pub const UserValue = union(Tag) {
1714 flag,
1715 scalar: String,
1716 list: StringList,
1717 map: Map.Index,
1718 lazy_path: LazyPath.Index,
1719 lazy_path_list: Storage.LengthPrefixedList(LazyPath.Index),
1720
1721 pub const Standalone = struct {
1722 flags: Flags,
1723 value: Storage.FlagUnion(.flags, .tag, UserValue),
1724
1725 pub const Flags = packed struct(u32) {
1726 tag: UserValue.Tag,
1727 _: u29 = 0,
1728 };
1729
1730 pub const Index = IndexType(@This());
1731 };
1732
1733 pub const Tag = enum(u3) {
1734 flag,
1735 scalar,
1736 list,
1737 map,
1738 lazy_path,
1739 lazy_path_list,
1740
1741 pub fn init(uv: @typeInfo(std.Build.UserValue).@"union".tag_type.?) @This() {
1742 return switch (uv) {
1743 inline else => |tag| @field(@This(), @tagName(tag)),
1744 };
1745 }
1746 };
1747
1748 pub const Map = struct {
1749 keys: StringList,
1750 values: Storage.LengthPrefixedList(UserValue.Standalone.Index),
1751
1752 pub const Index = IndexType(@This());
1753 };
1754 };
1755
17561682 pub const PublicModules = extern struct {
17571683 keys: StringList,
17581684 values: Module.List.Index,
lib/std/Build/Serialize.zig+6-31
......@@ -39,16 +39,14 @@ pub fn write(b: *std.Build, wc: *Configuration.Wip, writer: *std.Io.Writer) !voi
3939 // instance. Otherwise, addModule may access a package instance that hasn't
4040 // been created yet with packageInstanceFromBuilder.
4141
42 {
43 s.package_instance_map.putAssumeCapacityNoClobber(b, {});
44 var it = b.graph.dependency_cache.valueIterator();
45 while (it.next()) |dep| s.package_instance_map.putAssumeCapacityNoClobber(dep.*.builder, {});
42 s.package_instance_map.putAssumeCapacityNoClobber(b, {});
43 for (b.graph.dependency_cache.values()) |dep| {
44 s.package_instance_map.putAssumeCapacityNoClobber(dep.builder, {});
4645 }
4746
48 {
49 try s.addPackageInstance(b);
50 var it = b.graph.dependency_cache.valueIterator();
51 while (it.next()) |dep| try s.addPackageInstance(dep.*.builder);
47 try s.addPackageInstance(b);
48 for (b.graph.dependency_cache.values()) |dep| {
49 try s.addPackageInstance(dep.builder);
5250 }
5351
5452 try wc.path_deps.ensureTotalCapacityPrecise(gpa, graph.configure_dependencies.items.len);
......@@ -818,26 +816,6 @@ fn addPackageInstance(s: *Serialize, b: *std.Build) Allocator.Error!void {
818816
819817 const index = s.package_instance_map.getIndex(b).?;
820818
821 const options = try arena.alloc(
822 Configuration.Package.Instance.UserInputOption.Index,
823 b.user_input_options.count(),
824 );
825
826 {
827 var i: usize = 0;
828 var iter = b.user_input_options.valueIterator();
829 while (iter.next()) |option| : (i += 1) {
830 options[i] = try wc.addExtra(Configuration.Package.Instance.UserInputOption, .{
831 .flags = .{
832 .tag = .init(option.value),
833 .used = option.used,
834 },
835 .name = try wc.addString(option.name),
836 .value = .{ .u = try s.makeUserValue(&option.value) },
837 });
838 }
839 }
840
841819 const modules_values = try arena.alloc(Configuration.Module.Index, b.modules.count());
842820 for (modules_values, b.modules.values()) |*dest_value, value| {
843821 dest_value.* = try s.addModule(value);
......@@ -845,9 +823,6 @@ fn addPackageInstance(s: *Serialize, b: *std.Build) Allocator.Error!void {
845823
846824 wc.package_instances.items[index] = .{
847825 .package = s.packageFromHash(b.pkg_hash),
848 .user_input_options = try wc.addDeduped(Configuration.Package.Instance.UserInputOption.List, .{
849 .options = .{ .slice = options },
850 }),
851826 .modules = .{
852827 .keys = try wc.addStringList(b.modules.keys()),
853828 .values = try wc.addDeduped(Configuration.Module.List, .{