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 = "",...@@ -127,7 +127,42 @@ dep_prefix: []const u8 = "",
127modules: std.StringArrayHashMap(*Module),127modules: std.StringArrayHashMap(*Module),
128/// A map from build root dirs to the corresponding `*Dependency`. This is shared with all child128/// A map from build root dirs to the corresponding `*Dependency`. This is shared with all child
129/// `Build`s.129/// `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
132pub const ExecError = error{167pub const ExecError = error{
133 ReadFailure,168 ReadFailure,
...@@ -213,8 +248,8 @@ pub fn create(...@@ -213,8 +248,8 @@ pub fn create(
213 const env_map = try allocator.create(EnvMap);248 const env_map = try allocator.create(EnvMap);
214 env_map.* = try process.getEnvMap(allocator);249 env_map.* = try process.getEnvMap(allocator);
215250
216 const initialized_deps = try allocator.create(std.StringHashMap(*Dependency));251 const initialized_deps = try allocator.create(InitializedDepMap);
217 initialized_deps.* = std.StringHashMap(*Dependency).init(allocator);252 initialized_deps.* = InitializedDepMap.initContext(allocator, .{ .allocator = allocator });
218253
219 const self = try allocator.create(Build);254 const self = try allocator.create(Build);
220 self.* = .{255 self.* = .{
...@@ -280,14 +315,14 @@ fn createChild(...@@ -280,14 +315,14 @@ fn createChild(
280 parent: *Build,315 parent: *Build,
281 dep_name: []const u8,316 dep_name: []const u8,
282 build_root: Cache.Directory,317 build_root: Cache.Directory,
283 args: anytype,318 user_input_options: UserInputOptionsMap,
284) !*Build {319) !*Build {
285 const child = try createChildOnly(parent, dep_name, build_root);320 const child = try createChildOnly(parent, dep_name, build_root, user_input_options);
286 try applyArgs(child, args);321 try determineAndApplyInstallPrefix(child);
287 return child;322 return child;
288}323}
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 {
291 const allocator = parent.allocator;326 const allocator = parent.allocator;
292 const child = try allocator.create(Build);327 const child = try allocator.create(Build);
293 child.* = .{328 child.* = .{
...@@ -309,7 +344,7 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc...@@ -309,7 +344,7 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
309 }),344 }),
310 .description = "Remove build artifacts from prefix path",345 .description = "Remove build artifacts from prefix path",
311 },346 },
312 .user_input_options = UserInputOptionsMap.init(allocator),347 .user_input_options = user_input_options,
313 .available_options_map = AvailableOptionsMap.init(allocator),348 .available_options_map = AvailableOptionsMap.init(allocator),
314 .available_options_list = ArrayList(AvailableOption).init(allocator),349 .available_options_list = ArrayList(AvailableOption).init(allocator),
315 .verbose = parent.verbose,350 .verbose = parent.verbose,
...@@ -361,57 +396,152 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc...@@ -361,57 +396,152 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
361 return child;396 return child;
362}397}
363398
364fn applyArgs(b: *Build, args: anytype) !void {399fn userInputOptionsFromArgs(allocator: Allocator, args: anytype) UserInputOptionsMap {
400 var user_input_options = UserInputOptionsMap.init(allocator);
365 inline for (@typeInfo(@TypeOf(args)).Struct.fields) |field| {401 inline for (@typeInfo(@TypeOf(args)).Struct.fields) |field| {
366 const v = @field(args, field.name);402 const v = @field(args, field.name);
367 const T = @TypeOf(v);403 const T = @TypeOf(v);
368 switch (T) {404 switch (T) {
369 CrossTarget => {405 CrossTarget => {
370 try b.user_input_options.put(field.name, .{406 user_input_options.put(field.name, .{
371 .name = field.name,407 .name = field.name,
372 .value = .{ .scalar = try v.zigTriple(b.allocator) },408 .value = .{ .scalar = v.zigTriple(allocator) catch @panic("OOM") },
373 .used = false,409 .used = false,
374 });410 }) catch @panic("OOM");
375 try b.user_input_options.put("cpu", .{411 user_input_options.put("cpu", .{
376 .name = "cpu",412 .name = "cpu",
377 .value = .{ .scalar = try serializeCpu(b.allocator, v.getCpu()) },413 .value = .{ .scalar = serializeCpu(allocator, v.getCpu()) catch unreachable },
378 .used = false,414 .used = false,
379 });415 }) catch @panic("OOM");
380 },416 },
381 []const u8 => {417 []const u8 => {
382 try b.user_input_options.put(field.name, .{418 user_input_options.put(field.name, .{
383 .name = field.name,419 .name = field.name,
384 .value = .{ .scalar = v },420 .value = .{ .scalar = v },
385 .used = false,421 .used = false,
386 });422 }) catch @panic("OOM");
387 },423 },
388 else => switch (@typeInfo(T)) {424 else => switch (@typeInfo(T)) {
389 .Bool => {425 .Bool => {
390 try b.user_input_options.put(field.name, .{426 user_input_options.put(field.name, .{
391 .name = field.name,427 .name = field.name,
392 .value = .{ .scalar = if (v) "true" else "false" },428 .value = .{ .scalar = if (v) "true" else "false" },
393 .used = false,429 .used = false,
394 });430 }) catch @panic("OOM");
395 },431 },
396 .Enum, .EnumLiteral => {432 .Enum, .EnumLiteral => {
397 try b.user_input_options.put(field.name, .{433 user_input_options.put(field.name, .{
398 .name = field.name,434 .name = field.name,
399 .value = .{ .scalar = @tagName(v) },435 .value = .{ .scalar = @tagName(v) },
400 .used = false,436 .used = false,
401 });437 }) catch @panic("OOM");
402 },438 },
403 .Int => {439 .Int => {
404 try b.user_input_options.put(field.name, .{440 user_input_options.put(field.name, .{
405 .name = field.name,441 .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") },
407 .used = false,443 .used = false,
408 });444 }) catch @panic("OOM");
409 },445 },
410 else => @compileError("option '" ++ field.name ++ "' has unsupported type: " ++ @typeName(T)),446 else => @compileError("option '" ++ field.name ++ "' has unsupported type: " ++ @typeName(T)),
411 },447 },
412 }448 }
413 }449 }
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 {
415 // Create an installation directory local to this package. This will be used when545 // Create an installation directory local to this package. This will be used when
416 // dependant packages require a standard prefix, such as include directories for C headers.546 // dependant packages require a standard prefix, such as include directories for C headers.
417 var hash = b.cache.hash;547 var hash = b.cache.hash;
...@@ -419,7 +549,11 @@ fn applyArgs(b: *Build, args: anytype) !void {...@@ -419,7 +549,11 @@ fn applyArgs(b: *Build, args: anytype) !void {
419 // implementation is modified in a non-backwards-compatible way.549 // implementation is modified in a non-backwards-compatible way.
420 hash.add(@as(u32, 0xd8cb0055));550 hash.add(@as(u32, 0xd8cb0055));
421 hash.addBytes(b.dep_prefix);551 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
423 const digest = hash.final();557 const digest = hash.final();
424 const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &digest });558 const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &digest });
425 b.resolveInstallPrefix(install_prefix, .{});559 b.resolveInstallPrefix(install_prefix, .{});
...@@ -1597,6 +1731,53 @@ pub fn anonymousDependency(...@@ -1597,6 +1731,53 @@ pub fn anonymousDependency(
1597 return dependencyInner(b, name, build_root, build_zig, args);1731 return dependencyInner(b, name, build_root, build_zig, args);
1598}1732}
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
1600pub fn dependencyInner(1781pub fn dependencyInner(
1601 b: *Build,1782 b: *Build,
1602 name: []const u8,1783 name: []const u8,
...@@ -1604,10 +1785,12 @@ pub fn dependencyInner(...@@ -1604,10 +1785,12 @@ pub fn dependencyInner(
1604 comptime build_zig: type,1785 comptime build_zig: type,
1605 args: anytype,1786 args: anytype,
1606) *Dependency {1787) *Dependency {
1607 if (b.initialized_deps.get(build_root_string)) |dep| {1788 const user_input_options = userInputOptionsFromArgs(b.allocator, args);
1608 // TODO: check args are the same1789 if (b.initialized_deps.get(.{
1790 .build_root_string = build_root_string,
1791 .user_input_options = user_input_options,
1792 })) |dep|
1609 return dep;1793 return dep;
1610 }
16111794
1612 const build_root: std.Build.Cache.Directory = .{1795 const build_root: std.Build.Cache.Directory = .{
1613 .path = build_root_string,1796 .path = build_root_string,
...@@ -1618,7 +1801,7 @@ pub fn dependencyInner(...@@ -1618,7 +1801,7 @@ pub fn dependencyInner(
1618 process.exit(1);1801 process.exit(1);
1619 },1802 },
1620 };1803 };
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");
1622 sub_builder.runBuild(build_zig) catch @panic("unhandled error");1805 sub_builder.runBuild(build_zig) catch @panic("unhandled error");
16231806
1624 if (sub_builder.validateUserInputDidItFail()) {1807 if (sub_builder.validateUserInputDidItFail()) {
...@@ -1628,8 +1811,10 @@ pub fn dependencyInner(...@@ -1628,8 +1811,10 @@ pub fn dependencyInner(
1628 const dep = b.allocator.create(Dependency) catch @panic("OOM");1811 const dep = b.allocator.create(Dependency) catch @panic("OOM");
1629 dep.* = .{ .builder = sub_builder };1812 dep.* = .{ .builder = sub_builder };
16301813
1631 b.initialized_deps.put(build_root_string, dep) catch @panic("OOM");1814 b.initialized_deps.put(.{
16321815 .build_root_string = build_root_string,
1816 .user_input_options = user_input_options,
1817 }, dep) catch @panic("OOM");
1633 return dep;1818 return dep;
1634}1819}
16351820