| ... | ... | @@ -127,7 +127,42 @@ dep_prefix: []const u8 = "", |
| 127 | 127 | modules: std.StringArrayHashMap(*Module), |
| 128 | 128 | /// A map from build root dirs to the corresponding `*Dependency`. This is shared with all child |
| 129 | 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 | 167 | pub const ExecError = error{ |
| 133 | 168 | ReadFailure, |
| ... | ... | @@ -213,8 +248,8 @@ pub fn create( |
| 213 | 248 | const env_map = try allocator.create(EnvMap); |
| 214 | 249 | env_map.* = try process.getEnvMap(allocator); |
| 215 | 250 | |
| 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 }); |
| 218 | 253 | |
| 219 | 254 | const self = try allocator.create(Build); |
| 220 | 255 | self.* = .{ |
| ... | ... | @@ -280,14 +315,14 @@ fn createChild( |
| 280 | 315 | parent: *Build, |
| 281 | 316 | dep_name: []const u8, |
| 282 | 317 | build_root: Cache.Directory, |
| 283 | | args: anytype, |
| 318 | user_input_options: UserInputOptionsMap, |
| 284 | 319 | ) !*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); |
| 287 | 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 | 326 | const allocator = parent.allocator; |
| 292 | 327 | const child = try allocator.create(Build); |
| 293 | 328 | child.* = .{ |
| ... | ... | @@ -309,7 +344,7 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc |
| 309 | 344 | }), |
| 310 | 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 | 348 | .available_options_map = AvailableOptionsMap.init(allocator), |
| 314 | 349 | .available_options_list = ArrayList(AvailableOption).init(allocator), |
| 315 | 350 | .verbose = parent.verbose, |
| ... | ... | @@ -361,57 +396,152 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc |
| 361 | 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 | 401 | inline for (@typeInfo(@TypeOf(args)).Struct.fields) |field| { |
| 366 | 402 | const v = @field(args, field.name); |
| 367 | 403 | const T = @TypeOf(v); |
| 368 | 404 | switch (T) { |
| 369 | 405 | CrossTarget => { |
| 370 | | try b.user_input_options.put(field.name, .{ |
| 406 | user_input_options.put(field.name, .{ |
| 371 | 407 | .name = field.name, |
| 372 | | .value = .{ .scalar = try v.zigTriple(b.allocator) }, |
| 408 | .value = .{ .scalar = v.zigTriple(allocator) catch @panic("OOM") }, |
| 373 | 409 | .used = false, |
| 374 | | }); |
| 375 | | try b.user_input_options.put("cpu", .{ |
| 410 | }) catch @panic("OOM"); |
| 411 | user_input_options.put("cpu", .{ |
| 376 | 412 | .name = "cpu", |
| 377 | | .value = .{ .scalar = try serializeCpu(b.allocator, v.getCpu()) }, |
| 413 | .value = .{ .scalar = serializeCpu(allocator, v.getCpu()) catch unreachable }, |
| 378 | 414 | .used = false, |
| 379 | | }); |
| 415 | }) catch @panic("OOM"); |
| 380 | 416 | }, |
| 381 | 417 | []const u8 => { |
| 382 | | try b.user_input_options.put(field.name, .{ |
| 418 | user_input_options.put(field.name, .{ |
| 383 | 419 | .name = field.name, |
| 384 | 420 | .value = .{ .scalar = v }, |
| 385 | 421 | .used = false, |
| 386 | | }); |
| 422 | }) catch @panic("OOM"); |
| 387 | 423 | }, |
| 388 | 424 | else => switch (@typeInfo(T)) { |
| 389 | 425 | .Bool => { |
| 390 | | try b.user_input_options.put(field.name, .{ |
| 426 | user_input_options.put(field.name, .{ |
| 391 | 427 | .name = field.name, |
| 392 | 428 | .value = .{ .scalar = if (v) "true" else "false" }, |
| 393 | 429 | .used = false, |
| 394 | | }); |
| 430 | }) catch @panic("OOM"); |
| 395 | 431 | }, |
| 396 | 432 | .Enum, .EnumLiteral => { |
| 397 | | try b.user_input_options.put(field.name, .{ |
| 433 | user_input_options.put(field.name, .{ |
| 398 | 434 | .name = field.name, |
| 399 | 435 | .value = .{ .scalar = @tagName(v) }, |
| 400 | 436 | .used = false, |
| 401 | | }); |
| 437 | }) catch @panic("OOM"); |
| 402 | 438 | }, |
| 403 | 439 | .Int => { |
| 404 | | try b.user_input_options.put(field.name, .{ |
| 440 | user_input_options.put(field.name, .{ |
| 405 | 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 | 443 | .used = false, |
| 408 | | }); |
| 444 | }) catch @panic("OOM"); |
| 409 | 445 | }, |
| 410 | 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 | 545 | // Create an installation directory local to this package. This will be used when |
| 416 | 546 | // dependant packages require a standard prefix, such as include directories for C headers. |
| 417 | 547 | var hash = b.cache.hash; |
| ... | ... | @@ -419,7 +549,11 @@ fn applyArgs(b: *Build, args: anytype) !void { |
| 419 | 549 | // implementation is modified in a non-backwards-compatible way. |
| 420 | 550 | hash.add(@as(u32, 0xd8cb0055)); |
| 421 | 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 | 557 | const digest = hash.final(); |
| 424 | 558 | const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &digest }); |
| 425 | 559 | b.resolveInstallPrefix(install_prefix, .{}); |
| ... | ... | @@ -1597,6 +1731,53 @@ pub fn anonymousDependency( |
| 1597 | 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 | 1781 | pub fn dependencyInner( |
| 1601 | 1782 | b: *Build, |
| 1602 | 1783 | name: []const u8, |
| ... | ... | @@ -1604,10 +1785,12 @@ pub fn dependencyInner( |
| 1604 | 1785 | comptime build_zig: type, |
| 1605 | 1786 | args: anytype, |
| 1606 | 1787 | ) *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| |
| 1609 | 1793 | return dep; |
| 1610 | | } |
| 1611 | 1794 | |
| 1612 | 1795 | const build_root: std.Build.Cache.Directory = .{ |
| 1613 | 1796 | .path = build_root_string, |
| ... | ... | @@ -1618,7 +1801,7 @@ pub fn dependencyInner( |
| 1618 | 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 | 1805 | sub_builder.runBuild(build_zig) catch @panic("unhandled error"); |
| 1623 | 1806 | |
| 1624 | 1807 | if (sub_builder.validateUserInputDidItFail()) { |
| ... | ... | @@ -1628,8 +1811,10 @@ pub fn dependencyInner( |
| 1628 | 1811 | const dep = b.allocator.create(Dependency) catch @panic("OOM"); |
| 1629 | 1812 | dep.* = .{ .builder = sub_builder }; |
| 1630 | 1813 | |
| 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"); |
| 1633 | 1818 | return dep; |
| 1634 | 1819 | } |
| 1635 | 1820 | |