| ... | @@ -127,7 +127,42 @@ dep_prefix: []const u8 = "", | ... | @@ -127,7 +127,42 @@ dep_prefix: []const u8 = "", |
| 127 | modules: std.StringArrayHashMap(*Module), | 127 | modules: std.StringArrayHashMap(*Module), |
| 128 | /// A map from build root dirs to the corresponding `*Dependency`. This is shared with all child | 128 | /// A map from build root dirs to the corresponding `*Dependency`. This is shared with all child |
| 129 | /// `Build`s. | 129 | /// `Build`s. |
| 130 | initialized_deps: *std.StringHashMap(*Dependency), | 130 | initialized_deps: *InitializedDepMap, |
| | 131 | |
| | 132 | const InitializedDepMap = std.HashMap(InitializedDepKey, *Dependency, InitializedDepContext, std.hash_map.default_max_load_percentage); |
| | 133 | const InitializedDepKey = struct { |
| | 134 | build_root_string: []const u8, |
| | 135 | user_input_options: UserInputOptionsMap, |
| | 136 | }; |
| | 137 | |
| | 138 | const 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 | }; |
| 131 | | 166 | |
| 132 | pub const ExecError = error{ | 167 | pub 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); |
| 215 | | 250 | |
| 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 }); |
| 218 | | 253 | |
| 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 | } |
| 289 | | 324 | |
| 290 | fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Directory) !*Build { | 325 | fn 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 | } |
| 363 | | 398 | |
| 364 | fn applyArgs(b: *Build, args: anytype) !void { | 399 | fn 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 | } |
| 414 | | 450 | |
| | 451 | return user_input_options; |
| | 452 | } |
| | 453 | |
| | 454 | const 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 | |
| | 506 | const 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. |
| | 531 | fn 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 | |
| | 544 | fn determineAndApplyInstallPrefix(b: *Build) !void { |
| 415 | // Create an installation directory local to this package. This will be used when | 545 | // 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 | } |
| 1599 | | 1733 | |
| | 1734 | fn 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 | |
| 1600 | pub fn dependencyInner( | 1781 | pub 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 same | 1789 | 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 | } | | |
| 1611 | | 1794 | |
| 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"); |
| 1623 | | 1806 | |
| 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 }; |
| 1630 | | 1813 | |
| 1631 | b.initialized_deps.put(build_root_string, dep) catch @panic("OOM"); | 1814 | b.initialized_deps.put(.{ |
| 1632 | | 1815 | .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 | } |
| 1635 | | 1820 | |