authorgravatar for mattnite@proton.meMatt Knight <mattnite@proton.me> 2023-08-10 15:32:55-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-29 14:28:55-07:00
logd1dd5aeb07e3b9d5c642533f1dd174aa445c0bd5
tree99547080ef7cb110167c5c4d86360169b160b970
parent6ef836bfffd90abec9768a69ecdf727745979167

Compare user input for multiple dependency build variants (#16600)


1 files changed, 216 insertions(+), 31 deletions(-)

lib/std/Build.zig+216-31
......@@ -127,7 +127,42 @@ dep_prefix: []const u8 = "",
127127modules: std.StringArrayHashMap(*Module),
128128/// A map from build root dirs to the corresponding `*Dependency`. This is shared with all child
129129/// `Build`s.
130initialized_deps: *std.StringHashMap(*Dependency),
130initialized_deps: *InitializedDepMap,
131
132const InitializedDepMap = std.HashMap(InitializedDepKey, *Dependency, InitializedDepContext, std.hash_map.default_max_load_percentage);
133const InitializedDepKey = struct {
134 build_root_string: []const u8,
135 user_input_options: UserInputOptionsMap,
136};
137
138const InitializedDepContext = struct {
139 allocator: Allocator,
140
141 pub fn hash(self: @This(), k: InitializedDepKey) u64 {
142 var hasher = std.hash.Wyhash.init(0);
143 hasher.update(k.build_root_string);
144 hashUserInputOptionsMap(self.allocator, k.user_input_options, &hasher);
145 return hasher.final();
146 }
147
148 pub fn eql(self: @This(), lhs: InitializedDepKey, rhs: InitializedDepKey) bool {
149 _ = self;
150 if (!std.mem.eql(u8, lhs.build_root_string, rhs.build_root_string))
151 return false;
152
153 if (lhs.user_input_options.count() != rhs.user_input_options.count())
154 return false;
155
156 var it = lhs.user_input_options.iterator();
157 while (it.next()) |lhs_entry| {
158 const rhs_value = rhs.user_input_options.get(lhs_entry.key_ptr.*) orelse return false;
159 if (!userValuesAreSame(lhs_entry.value_ptr.*.value, rhs_value.value))
160 return false;
161 }
162
163 return true;
164 }
165};
131166
132167pub const ExecError = error{
133168 ReadFailure,
......@@ -213,8 +248,8 @@ pub fn create(
213248 const env_map = try allocator.create(EnvMap);
214249 env_map.* = try process.getEnvMap(allocator);
215250
216 const initialized_deps = try allocator.create(std.StringHashMap(*Dependency));
217 initialized_deps.* = std.StringHashMap(*Dependency).init(allocator);
251 const initialized_deps = try allocator.create(InitializedDepMap);
252 initialized_deps.* = InitializedDepMap.initContext(allocator, .{ .allocator = allocator });
218253
219254 const self = try allocator.create(Build);
220255 self.* = .{
......@@ -280,14 +315,14 @@ fn createChild(
280315 parent: *Build,
281316 dep_name: []const u8,
282317 build_root: Cache.Directory,
283 args: anytype,
318 user_input_options: UserInputOptionsMap,
284319) !*Build {
285 const child = try createChildOnly(parent, dep_name, build_root);
286 try applyArgs(child, args);
320 const child = try createChildOnly(parent, dep_name, build_root, user_input_options);
321 try determineAndApplyInstallPrefix(child);
287322 return child;
288323}
289324
290fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Directory) !*Build {
325fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Directory, user_input_options: UserInputOptionsMap) !*Build {
291326 const allocator = parent.allocator;
292327 const child = try allocator.create(Build);
293328 child.* = .{
......@@ -309,7 +344,7 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
309344 }),
310345 .description = "Remove build artifacts from prefix path",
311346 },
312 .user_input_options = UserInputOptionsMap.init(allocator),
347 .user_input_options = user_input_options,
313348 .available_options_map = AvailableOptionsMap.init(allocator),
314349 .available_options_list = ArrayList(AvailableOption).init(allocator),
315350 .verbose = parent.verbose,
......@@ -361,57 +396,152 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
361396 return child;
362397}
363398
364fn applyArgs(b: *Build, args: anytype) !void {
399fn userInputOptionsFromArgs(allocator: Allocator, args: anytype) UserInputOptionsMap {
400 var user_input_options = UserInputOptionsMap.init(allocator);
365401 inline for (@typeInfo(@TypeOf(args)).Struct.fields) |field| {
366402 const v = @field(args, field.name);
367403 const T = @TypeOf(v);
368404 switch (T) {
369405 CrossTarget => {
370 try b.user_input_options.put(field.name, .{
406 user_input_options.put(field.name, .{
371407 .name = field.name,
372 .value = .{ .scalar = try v.zigTriple(b.allocator) },
408 .value = .{ .scalar = v.zigTriple(allocator) catch @panic("OOM") },
373409 .used = false,
374 });
375 try b.user_input_options.put("cpu", .{
410 }) catch @panic("OOM");
411 user_input_options.put("cpu", .{
376412 .name = "cpu",
377 .value = .{ .scalar = try serializeCpu(b.allocator, v.getCpu()) },
413 .value = .{ .scalar = serializeCpu(allocator, v.getCpu()) catch unreachable },
378414 .used = false,
379 });
415 }) catch @panic("OOM");
380416 },
381417 []const u8 => {
382 try b.user_input_options.put(field.name, .{
418 user_input_options.put(field.name, .{
383419 .name = field.name,
384420 .value = .{ .scalar = v },
385421 .used = false,
386 });
422 }) catch @panic("OOM");
387423 },
388424 else => switch (@typeInfo(T)) {
389425 .Bool => {
390 try b.user_input_options.put(field.name, .{
426 user_input_options.put(field.name, .{
391427 .name = field.name,
392428 .value = .{ .scalar = if (v) "true" else "false" },
393429 .used = false,
394 });
430 }) catch @panic("OOM");
395431 },
396432 .Enum, .EnumLiteral => {
397 try b.user_input_options.put(field.name, .{
433 user_input_options.put(field.name, .{
398434 .name = field.name,
399435 .value = .{ .scalar = @tagName(v) },
400436 .used = false,
401 });
437 }) catch @panic("OOM");
402438 },
403439 .Int => {
404 try b.user_input_options.put(field.name, .{
440 user_input_options.put(field.name, .{
405441 .name = field.name,
406 .value = .{ .scalar = try std.fmt.allocPrint(b.allocator, "{d}", .{v}) },
442 .value = .{ .scalar = std.fmt.allocPrint(allocator, "{d}", .{v}) catch @panic("OOM") },
407443 .used = false,
408 });
444 }) catch @panic("OOM");
409445 },
410446 else => @compileError("option '" ++ field.name ++ "' has unsupported type: " ++ @typeName(T)),
411447 },
412448 }
413449 }
414450
451 return user_input_options;
452}
453
454const OrderedUserValue = union(enum) {
455 flag: void,
456 scalar: []const u8,
457 list: ArrayList([]const u8),
458 map: ArrayList(Pair),
459
460 const Pair = struct {
461 name: []const u8,
462 value: OrderedUserValue,
463 fn lessThan(_: void, lhs: Pair, rhs: Pair) bool {
464 return std.ascii.lessThanIgnoreCase(lhs.name, rhs.name);
465 }
466 };
467
468 fn hash(self: OrderedUserValue, hasher: *std.hash.Wyhash) void {
469 switch (self) {
470 .flag => {},
471 .scalar => |scalar| hasher.update(scalar),
472 // lists are already ordered
473 .list => |list| for (list.items) |list_entry|
474 hasher.update(list_entry),
475 .map => |map| for (map.items) |map_entry| {
476 hasher.update(map_entry.name);
477 map_entry.value.hash(hasher);
478 },
479 }
480 }
481
482 fn mapFromUnordered(allocator: Allocator, unordered: std.StringHashMap(*const UserValue)) ArrayList(Pair) {
483 var ordered = ArrayList(Pair).init(allocator);
484 var it = unordered.iterator();
485 while (it.next()) |entry| {
486 ordered.append(.{
487 .name = entry.key_ptr.*,
488 .value = OrderedUserValue.fromUnordered(allocator, entry.value_ptr.*.*),
489 }) catch @panic("OOM");
490 }
491
492 std.mem.sortUnstable(Pair, ordered.items, {}, Pair.lessThan);
493 return ordered;
494 }
495
496 fn fromUnordered(allocator: Allocator, unordered: UserValue) OrderedUserValue {
497 return switch (unordered) {
498 .flag => .{ .flag = {} },
499 .scalar => |scalar| .{ .scalar = scalar },
500 .list => |list| .{ .list = list },
501 .map => |map| .{ .map = OrderedUserValue.mapFromUnordered(allocator, map) },
502 };
503 }
504};
505
506const OrderedUserInputOption = struct {
507 name: []const u8,
508 value: OrderedUserValue,
509 used: bool,
510
511 fn hash(self: OrderedUserInputOption, hasher: *std.hash.Wyhash) void {
512 hasher.update(self.name);
513 self.value.hash(hasher);
514 }
515
516 fn fromUnordered(allocator: Allocator, user_input_option: UserInputOption) OrderedUserInputOption {
517 return OrderedUserInputOption{
518 .name = user_input_option.name,
519 .used = user_input_option.used,
520 .value = OrderedUserValue.fromUnordered(allocator, user_input_option.value),
521 };
522 }
523
524 fn lessThan(_: void, lhs: OrderedUserInputOption, rhs: OrderedUserInputOption) bool {
525 return std.ascii.lessThanIgnoreCase(lhs.name, rhs.name);
526 }
527};
528
529// The hash should be consistent with the same values given a different order.
530// This function takes a user input map, orders it, then hashes the contents.
531fn hashUserInputOptionsMap(allocator: Allocator, user_input_options: UserInputOptionsMap, hasher: *std.hash.Wyhash) void {
532 var ordered = ArrayList(OrderedUserInputOption).init(allocator);
533 var it = user_input_options.iterator();
534 while (it.next()) |entry|
535 ordered.append(OrderedUserInputOption.fromUnordered(allocator, entry.value_ptr.*)) catch @panic("OOM");
536
537 std.mem.sortUnstable(OrderedUserInputOption, ordered.items, {}, OrderedUserInputOption.lessThan);
538
539 // juice it
540 for (ordered.items) |user_option|
541 user_option.hash(hasher);
542}
543
544fn determineAndApplyInstallPrefix(b: *Build) !void {
415545 // Create an installation directory local to this package. This will be used when
416546 // dependant packages require a standard prefix, such as include directories for C headers.
417547 var hash = b.cache.hash;
......@@ -419,7 +549,11 @@ fn applyArgs(b: *Build, args: anytype) !void {
419549 // implementation is modified in a non-backwards-compatible way.
420550 hash.add(@as(u32, 0xd8cb0055));
421551 hash.addBytes(b.dep_prefix);
422 // TODO additionally update the hash with `args`.
552
553 var wyhash = std.hash.Wyhash.init(0);
554 hashUserInputOptionsMap(b.allocator, b.user_input_options, &wyhash);
555 hash.add(wyhash.final());
556
423557 const digest = hash.final();
424558 const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &digest });
425559 b.resolveInstallPrefix(install_prefix, .{});
......@@ -1597,6 +1731,53 @@ pub fn anonymousDependency(
15971731 return dependencyInner(b, name, build_root, build_zig, args);
15981732}
15991733
1734fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool {
1735 switch (lhs) {
1736 .flag => {},
1737 .scalar => |lhs_scalar| {
1738 const rhs_scalar = switch (rhs) {
1739 .scalar => |scalar| scalar,
1740 else => return false,
1741 };
1742
1743 if (!std.mem.eql(u8, lhs_scalar, rhs_scalar))
1744 return false;
1745 },
1746 .list => |lhs_list| {
1747 const rhs_list = switch (rhs) {
1748 .list => |list| list,
1749 else => return false,
1750 };
1751
1752 if (lhs_list.items.len != rhs_list.items.len)
1753 return false;
1754
1755 for (lhs_list.items, rhs_list.items) |lhs_list_entry, rhs_list_entry| {
1756 if (!std.mem.eql(u8, lhs_list_entry, rhs_list_entry))
1757 return false;
1758 }
1759 },
1760 .map => |lhs_map| {
1761 const rhs_map = switch (rhs) {
1762 .map => |map| map,
1763 else => return false,
1764 };
1765
1766 if (lhs_map.count() != rhs_map.count())
1767 return false;
1768
1769 var lhs_it = lhs_map.iterator();
1770 while (lhs_it.next()) |lhs_entry| {
1771 const rhs_value = rhs_map.get(lhs_entry.key_ptr.*) orelse return false;
1772 if (!userValuesAreSame(lhs_entry.value_ptr.*.*, rhs_value.*))
1773 return false;
1774 }
1775 },
1776 }
1777
1778 return true;
1779}
1780
16001781pub fn dependencyInner(
16011782 b: *Build,
16021783 name: []const u8,
......@@ -1604,10 +1785,12 @@ pub fn dependencyInner(
16041785 comptime build_zig: type,
16051786 args: anytype,
16061787) *Dependency {
1607 if (b.initialized_deps.get(build_root_string)) |dep| {
1608 // TODO: check args are the same
1788 const user_input_options = userInputOptionsFromArgs(b.allocator, args);
1789 if (b.initialized_deps.get(.{
1790 .build_root_string = build_root_string,
1791 .user_input_options = user_input_options,
1792 })) |dep|
16091793 return dep;
1610 }
16111794
16121795 const build_root: std.Build.Cache.Directory = .{
16131796 .path = build_root_string,
......@@ -1618,7 +1801,7 @@ pub fn dependencyInner(
16181801 process.exit(1);
16191802 },
16201803 };
1621 const sub_builder = b.createChild(name, build_root, args) catch @panic("unhandled error");
1804 const sub_builder = b.createChild(name, build_root, user_input_options) catch @panic("unhandled error");
16221805 sub_builder.runBuild(build_zig) catch @panic("unhandled error");
16231806
16241807 if (sub_builder.validateUserInputDidItFail()) {
......@@ -1628,8 +1811,10 @@ pub fn dependencyInner(
16281811 const dep = b.allocator.create(Dependency) catch @panic("OOM");
16291812 dep.* = .{ .builder = sub_builder };
16301813
1631 b.initialized_deps.put(build_root_string, dep) catch @panic("OOM");
1632
1814 b.initialized_deps.put(.{
1815 .build_root_string = build_root_string,
1816 .user_input_options = user_input_options,
1817 }, dep) catch @panic("OOM");
16331818 return dep;
16341819}
16351820