| ... | ... | @@ -763,6 +763,8 @@ pub fn main(init: process.Init.Minimal) !void { |
| 763 | 763 | break :s &protocol_server_allocation; |
| 764 | 764 | } else null; |
| 765 | 765 | |
| 766 | var discovered_unlazy_set: Fetch.JobQueue.UnlazySet = .empty; // uses global arena |
| 767 | |
| 766 | 768 | configure: while (true) { |
| 767 | 769 | // Set of files that, if modified, imply that recompiling and rerunning |
| 768 | 770 | // configurer is needed. |
| ... | ... | @@ -792,9 +794,12 @@ pub fn main(init: process.Init.Minimal) !void { |
| 792 | 794 | .system_pkg_dir_path = system_pkg_dir_path, |
| 793 | 795 | .print_configuration = print_configuration, |
| 794 | 796 | .forks = forks.items, |
| 797 | .unlazy_set = &discovered_unlazy_set, |
| 795 | 798 | .src_files = &configure_source_files, |
| 796 | 799 | .error_bundle = if (protocol_server != null) &configure_error_bundle else null, |
| 797 | 800 | })) |scanned_config| { |
| 801 | discovered_unlazy_set.clearRetainingCapacity(); |
| 802 | |
| 798 | 803 | if (help_menu) { |
| 799 | 804 | scanned_config.printUsage(&graph, initStdoutWriter(io)) catch |err| switch (err) { |
| 800 | 805 | error.WriteFailed => return stdout_writer_allocation.err.?, |
| ... | ... | @@ -914,14 +919,15 @@ pub fn main(init: process.Init.Minimal) !void { |
| 914 | 919 | try select.concurrent(.message, Server.receiveMessage, .{s}); |
| 915 | 920 | |
| 916 | 921 | maker.watch = body.flags.watch; |
| 917 | | maker.prepare(steps, &configure_source_files) catch |err| switch (err) { |
| 922 | maker.prepare(steps, &configure_source_files, &discovered_unlazy_set) catch |err| switch (err) { |
| 923 | error.OutOfMemory => |e| return e, |
| 918 | 924 | error.DependencyLoopDetected, error.InsufficientMemory => { |
| 919 | | // TODO handle DependencyLoopDetected as error.FailedButCacheIntact |
| 920 | | // and handle InsufficientMemory as error.AlreadyReported |
| 925 | // TODO handle error.DependencyLoopDetected as error.FailedButCacheIntact |
| 926 | // TODO handle error.InsufficientMemory as error.AlreadyReported |
| 921 | 927 | _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; |
| 922 | 928 | process.exit(1); |
| 923 | 929 | }, |
| 924 | | else => |e| return e, |
| 930 | error.LazyDependencyNeeded => continue :configure, |
| 925 | 931 | }; |
| 926 | 932 | |
| 927 | 933 | try maker.makeSteps(main_progress_node, null); |
| ... | ... | @@ -979,14 +985,15 @@ pub fn main(init: process.Init.Minimal) !void { |
| 979 | 985 | const initial_steps = try maker.resolveTopLevelSteps(step_names.items); |
| 980 | 986 | defer gpa.free(initial_steps); |
| 981 | 987 | |
| 982 | | maker.prepare(initial_steps, &configure_source_files) catch |err| switch (err) { |
| 988 | maker.prepare(initial_steps, &configure_source_files, &discovered_unlazy_set) catch |err| switch (err) { |
| 989 | error.OutOfMemory => |e| return e, |
| 983 | 990 | error.DependencyLoopDetected, error.InsufficientMemory => { |
| 984 | | // TODO handle DependencyLoopDetected as error.FailedButCacheIntact |
| 985 | | // and handle InsufficientMemory as error.AlreadyReported |
| 991 | // TODO handle error.DependencyLoopDetected as error.FailedButCacheIntact |
| 992 | // TODO handle error.InsufficientMemory as error.AlreadyReported |
| 986 | 993 | _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; |
| 987 | 994 | process.exit(1); |
| 988 | 995 | }, |
| 989 | | else => |e| return e, |
| 996 | error.LazyDependencyNeeded => continue :configure, |
| 990 | 997 | }; |
| 991 | 998 | |
| 992 | 999 | var w: Watch = w: { |
| ... | ... | @@ -1134,6 +1141,7 @@ const ConfigureOptions = struct { |
| 1134 | 1141 | system_pkg_dir_path: ?[]const u8, |
| 1135 | 1142 | print_configuration: PrintConfiguration, |
| 1136 | 1143 | forks: []Fork, |
| 1144 | unlazy_set: *Fetch.JobQueue.UnlazySet, |
| 1137 | 1145 | src_files: *Cache.Manifest.SelfContainedFiles, |
| 1138 | 1146 | error_bundle: ?*ErrorBundle = null, |
| 1139 | 1147 | }; |
| ... | ... | @@ -1149,8 +1157,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1149 | 1157 | var http_client: std.http.Client = .{ .allocator = gpa, .io = io }; |
| 1150 | 1158 | defer http_client.deinit(); |
| 1151 | 1159 | |
| 1152 | | var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{}; |
| 1153 | | var fork_set: Package.Fetch.JobQueue.ForkSet = .{}; |
| 1160 | var fork_set: Fetch.JobQueue.ForkSet = .{}; |
| 1154 | 1161 | |
| 1155 | 1162 | { |
| 1156 | 1163 | // Populate fork_set. |
| ... | ... | @@ -1285,7 +1292,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1285 | 1292 | // Reset fork match counts. |
| 1286 | 1293 | for (fork_set.keys()) |*fork| fork.uses = 0; |
| 1287 | 1294 | |
| 1288 | | var job_queue: Package.Fetch.JobQueue = .{ |
| 1295 | var job_queue: Fetch.JobQueue = .{ |
| 1289 | 1296 | .io = io, |
| 1290 | 1297 | .http_client = &http_client, |
| 1291 | 1298 | .global_cache = graph.global_cache_root, |
| ... | ... | @@ -1295,7 +1302,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1295 | 1302 | }, |
| 1296 | 1303 | .recursive = true, |
| 1297 | 1304 | .debug_hash = false, |
| 1298 | | .unlazy_set = unlazy_set, |
| 1305 | .unlazy_set = options.unlazy_set.*, |
| 1299 | 1306 | .fork_set = fork_set, |
| 1300 | 1307 | .mode = options.fetch_mode, |
| 1301 | 1308 | .prog_node = fetch_prog_node, |
| ... | ... | @@ -1312,7 +1319,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1312 | 1319 | |
| 1313 | 1320 | const phantom_package_root: Cache.Path = .{ .root_dir = options.build_root.directory }; |
| 1314 | 1321 | |
| 1315 | | var fetch: Package.Fetch = .{ |
| 1322 | var fetch: Fetch = .{ |
| 1316 | 1323 | .arena = std.heap.ArenaAllocator.init(gpa), |
| 1317 | 1324 | .location = .{ .relative_path = phantom_package_root }, |
| 1318 | 1325 | .location_tok = 0, |
| ... | ... | @@ -1344,11 +1351,11 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1344 | 1351 | job_queue.all_fetches.appendAssumeCapacity(&fetch); |
| 1345 | 1352 | |
| 1346 | 1353 | job_queue.table.putAssumeCapacityNoClobber( |
| 1347 | | Package.Fetch.relativePathDigest(phantom_package_root, graph.global_cache_root), |
| 1354 | Fetch.relativePathDigest(phantom_package_root, graph.global_cache_root), |
| 1348 | 1355 | &fetch, |
| 1349 | 1356 | ); |
| 1350 | 1357 | |
| 1351 | | job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" }); |
| 1358 | job_queue.group.async(io, Fetch.workerRun, .{ &fetch, "root" }); |
| 1352 | 1359 | try job_queue.group.await(io); |
| 1353 | 1360 | |
| 1354 | 1361 | { |
| ... | ... | @@ -1489,7 +1496,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1489 | 1496 | const dep_names = man.dependencies.keys(); |
| 1490 | 1497 | try mod.deps.ensureUnusedCapacity(arena, @intCast(dep_names.len)); |
| 1491 | 1498 | for (dep_names, man.dependencies.values()) |name, *dep| { |
| 1492 | | const dep_digest = Package.Fetch.depDigest( |
| 1499 | const dep_digest = Fetch.depDigest( |
| 1493 | 1500 | f.package_root, |
| 1494 | 1501 | graph.global_cache_root, |
| 1495 | 1502 | dep, |
| ... | ... | @@ -1608,30 +1615,33 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1608 | 1615 | |
| 1609 | 1616 | if (configuration.unlazy_deps.len != 0) { |
| 1610 | 1617 | var any_errors = false; |
| 1611 | | for (configuration.unlazy_deps) |hash_string| { |
| 1612 | | const hash = hash_string.slice(&configuration); |
| 1613 | | assert(hash.len != 0); |
| 1614 | | if (hash.len > Package.Hash.max_len) { |
| 1615 | | log.err("invalid digest (length {d} exceeds maximum): {q}", .{ hash.len, hash }); |
| 1616 | | any_errors = true; |
| 1617 | | continue; |
| 1618 | options.unlazy_set.clearRetainingCapacity(); |
| 1619 | for (configuration.unlazy_deps) |unlazy_dep| { |
| 1620 | if (unlazy_dep.step != .none) continue; // Handled in `prepare`. |
| 1621 | for (unlazy_dep.list.slice(&configuration)) |hash_string| { |
| 1622 | const hash = hash_string.slice(&configuration); |
| 1623 | assert(hash.len != 0); |
| 1624 | if (hash.len > Package.Hash.max_len) { |
| 1625 | log.err("invalid digest (length {d} exceeds maximum): {q}", .{ hash.len, hash }); |
| 1626 | any_errors = true; |
| 1627 | continue; |
| 1628 | } |
| 1629 | log.info("fetching lazy dependency {s}", .{hash}); |
| 1630 | try options.unlazy_set.put(arena, .fromSlice(hash), {}); |
| 1618 | 1631 | } |
| 1619 | | log.info("fetching lazy dependency {s}", .{hash}); |
| 1620 | | try unlazy_set.put(arena, .fromSlice(hash), {}); |
| 1621 | 1632 | } |
| 1622 | 1633 | if (any_errors) return error.FailedButCacheIntact; |
| 1623 | 1634 | if (options.system_pkg_dir_path) |p| { |
| 1624 | | // In this mode, the system needs to provide these packages; they |
| 1625 | | // cannot be fetched by Zig. |
| 1635 | // In this mode, the system needs to provide these packages; they cannot be fetched by Zig. |
| 1626 | 1636 | const s = Dir.path.sep_str; |
| 1627 | | for (unlazy_set.keys()) |*hash| { |
| 1637 | for (options.unlazy_set.keys()) |*hash| { |
| 1628 | 1638 | log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{ p, hash.toSlice() }); |
| 1629 | 1639 | } |
| 1630 | 1640 | log.info("remote package fetching disabled due to --system mode", .{}); |
| 1631 | 1641 | log.info("dependencies might be avoidable depending on build configuration", .{}); |
| 1632 | 1642 | return error.FailedButCacheIntact; |
| 1633 | 1643 | } |
| 1634 | | continue :cp; |
| 1644 | if (options.unlazy_set.count() != 0) continue :cp; |
| 1635 | 1645 | } |
| 1636 | 1646 | |
| 1637 | 1647 | if (config_man) |man| for (configuration.path_deps) |path_dep| { |
| ... | ... | @@ -1771,7 +1781,7 @@ const ValidFingerprintTable = std.array_hash_map.Custom( |
| 1771 | 1781 | fn validateFingerprints( |
| 1772 | 1782 | gpa: Allocator, |
| 1773 | 1783 | job_queue: *const Fetch.JobQueue, |
| 1774 | | fork_set: *const Package.Fetch.JobQueue.ForkSet, |
| 1784 | fork_set: *const Fetch.JobQueue.ForkSet, |
| 1775 | 1785 | ) !void { |
| 1776 | 1786 | var any_errors = false; |
| 1777 | 1787 | |
| ... | ... | @@ -1826,7 +1836,7 @@ fn printPkgTree( |
| 1826 | 1836 | global_cache_root: Cache.Directory, |
| 1827 | 1837 | job_queue: *const Fetch.JobQueue, |
| 1828 | 1838 | root_fetch: *const Fetch, |
| 1829 | | fork_set: *const Package.Fetch.JobQueue.ForkSet, |
| 1839 | fork_set: *const Fetch.JobQueue.ForkSet, |
| 1830 | 1840 | ) !void { |
| 1831 | 1841 | if (!root_fetch.have_manifest) return; |
| 1832 | 1842 | const names = root_fetch.manifest.dependencies.keys(); |
| ... | ... | @@ -1841,7 +1851,7 @@ fn printPkgTree( |
| 1841 | 1851 | try printPkgTreeChild(terminal, arena, global_cache_root, job_queue, &print_node, name, child_hash, fork_set); |
| 1842 | 1852 | }, |
| 1843 | 1853 | .path => { |
| 1844 | | const child_hash = Package.Fetch.depDigest(root_fetch.package_root, global_cache_root, dep); |
| 1854 | const child_hash = Fetch.depDigest(root_fetch.package_root, global_cache_root, dep); |
| 1845 | 1855 | try printPkgTreeChild(terminal, arena, global_cache_root, job_queue, &print_node, name, child_hash, fork_set); |
| 1846 | 1856 | }, |
| 1847 | 1857 | } |
| ... | ... | @@ -1856,7 +1866,7 @@ fn printPkgTreeChild( |
| 1856 | 1866 | parent_print_node: *const PrintNode, |
| 1857 | 1867 | dep_name: []const u8, |
| 1858 | 1868 | opt_pkg_hash: ?Package.Hash, |
| 1859 | | fork_set: *const Package.Fetch.JobQueue.ForkSet, |
| 1869 | fork_set: *const Fetch.JobQueue.ForkSet, |
| 1860 | 1870 | ) !void { |
| 1861 | 1871 | const w = terminal.writer; |
| 1862 | 1872 | try parent_print_node.printPrefixedChild(w, terminal.mode); |
| ... | ... | @@ -1913,7 +1923,7 @@ fn printPkgTreeChild( |
| 1913 | 1923 | try printPkgTreeChild(terminal, arena, global_cache_root, job_queue, &print_node, name, child_hash, fork_set); |
| 1914 | 1924 | }, |
| 1915 | 1925 | .path => { |
| 1916 | | const child_hash = Package.Fetch.depDigest(child_fetch.package_root, global_cache_root, dep); |
| 1926 | const child_hash = Fetch.depDigest(child_fetch.package_root, global_cache_root, dep); |
| 1917 | 1927 | try printPkgTreeChild(terminal, arena, global_cache_root, job_queue, &print_node, name, child_hash, fork_set); |
| 1918 | 1928 | }, |
| 1919 | 1929 | } |
| ... | ... | @@ -2640,7 +2650,8 @@ fn prepare( |
| 2640 | 2650 | maker: *Maker, |
| 2641 | 2651 | step_indices: []const Configuration.Step.Index, |
| 2642 | 2652 | configure_source_files: *const Cache.Manifest.SelfContainedFiles, |
| 2643 | | ) !void { |
| 2653 | discovered_unlazy_set: *Fetch.JobQueue.UnlazySet, |
| 2654 | ) error{ InsufficientMemory, OutOfMemory, DependencyLoopDetected, LazyDependencyNeeded }!void { |
| 2644 | 2655 | const gpa = maker.gpa; |
| 2645 | 2656 | const graph = maker.graph; |
| 2646 | 2657 | const arena = graph.arena; |
| ... | ... | @@ -2688,40 +2699,67 @@ fn prepare( |
| 2688 | 2699 | try constructGraphAndCheckForDependencyLoop(maker, s, &maker.step_stack, rand); |
| 2689 | 2700 | } |
| 2690 | 2701 | |
| 2691 | | { |
| 2692 | | // Check that we have enough memory to complete the build. |
| 2693 | | var any_problems = false; |
| 2694 | | var max_needed: u64 = 0; |
| 2695 | | for (step_stack.keys()) |step_index| { |
| 2696 | | const make_step = maker.stepByIndex(step_index); |
| 2697 | | const conf_step = step_index.ptr(c); |
| 2698 | | const max_rss = conf_step.max_rss.toBytes(); |
| 2699 | | if (max_rss == 0) continue; |
| 2700 | | max_needed = @max(max_needed, max_rss); |
| 2701 | | if (max_rss > maker.available_rss) { |
| 2702 | | if (maker.skip_oom_steps) { |
| 2703 | | make_step.state = .skipped_oom; |
| 2704 | | for (make_step.dependants.items) |dependant| { |
| 2705 | | maker.stepByIndex(dependant).pending_deps -= 1; |
| 2706 | | } |
| 2707 | | } else { |
| 2708 | | log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{ |
| 2709 | | conf_step.owner.depPrefixSlice(c), |
| 2710 | | conf_step.name.slice(c), |
| 2711 | | max_rss, |
| 2712 | | maker.available_rss, |
| 2713 | | }); |
| 2714 | | any_problems = true; |
| 2702 | // Notice when a step's participation in the evaluation graph implies that additional package dependencies |
| 2703 | // must be fetched. |
| 2704 | var needed_step_deps: std.array_hash_map.Auto(Configuration.Step.Index, Configuration.StringList) = .empty; |
| 2705 | defer needed_step_deps.deinit(gpa); |
| 2706 | |
| 2707 | for (c.unlazy_deps) |unlazy_dep| { |
| 2708 | const step_index = unlazy_dep.step.unwrap() orelse continue; // Handled in `configure`. |
| 2709 | try needed_step_deps.put(gpa, step_index, unlazy_dep.list); |
| 2710 | } |
| 2711 | |
| 2712 | assert(discovered_unlazy_set.count() == 0); |
| 2713 | for (step_stack.keys()) |step_index| { |
| 2714 | const interned_string_list = needed_step_deps.get(step_index) orelse continue; |
| 2715 | const hash_strings = interned_string_list.slice(c); |
| 2716 | for (hash_strings) |hash_string| { |
| 2717 | const hash = hash_string.slice(c); |
| 2718 | log.debug("due to step {s}, fetching lazy {s}", .{ step_index.ptr(c).name.slice(c), hash }); |
| 2719 | try discovered_unlazy_set.put(arena, .fromSlice(hash), {}); |
| 2720 | } |
| 2721 | } |
| 2722 | if (discovered_unlazy_set.count() != 0) return error.LazyDependencyNeeded; |
| 2723 | |
| 2724 | try checkSufficientMemory(maker); |
| 2725 | } |
| 2726 | |
| 2727 | fn checkSufficientMemory(maker: *Maker) error{InsufficientMemory}!void { |
| 2728 | const step_stack = &maker.step_stack; |
| 2729 | const c = &maker.scanned_config.configuration; |
| 2730 | |
| 2731 | // Check that we have enough memory to complete the build. |
| 2732 | var any_problems = false; |
| 2733 | var max_needed: u64 = 0; |
| 2734 | for (step_stack.keys()) |step_index| { |
| 2735 | const make_step = maker.stepByIndex(step_index); |
| 2736 | const conf_step = step_index.ptr(c); |
| 2737 | const max_rss = conf_step.max_rss.toBytes(); |
| 2738 | if (max_rss == 0) continue; |
| 2739 | max_needed = @max(max_needed, max_rss); |
| 2740 | if (max_rss > maker.available_rss) { |
| 2741 | if (maker.skip_oom_steps) { |
| 2742 | make_step.state = .skipped_oom; |
| 2743 | for (make_step.dependants.items) |dependant| { |
| 2744 | maker.stepByIndex(dependant).pending_deps -= 1; |
| 2715 | 2745 | } |
| 2746 | } else { |
| 2747 | log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{ |
| 2748 | conf_step.owner.depPrefixSlice(c), |
| 2749 | conf_step.name.slice(c), |
| 2750 | max_rss, |
| 2751 | maker.available_rss, |
| 2752 | }); |
| 2753 | any_problems = true; |
| 2716 | 2754 | } |
| 2717 | 2755 | } |
| 2718 | | if (any_problems) { |
| 2719 | | log.info("use --skip-oom-steps to proceed, skipping memory limited steps", .{}); |
| 2720 | | if (maker.max_rss_is_default) { |
| 2721 | | log.info("use --maxrss {d} to proceed, risking system memory exhaustion", .{max_needed}); |
| 2722 | | } |
| 2723 | | return error.InsufficientMemory; |
| 2756 | } |
| 2757 | if (any_problems) { |
| 2758 | log.info("use --skip-oom-steps to proceed, skipping memory limited steps", .{}); |
| 2759 | if (maker.max_rss_is_default) { |
| 2760 | log.info("use --maxrss {d} to proceed, risking system memory exhaustion", .{max_needed}); |
| 2724 | 2761 | } |
| 2762 | return error.InsufficientMemory; |
| 2725 | 2763 | } |
| 2726 | 2764 | } |
| 2727 | 2765 | |
| ... | ... | @@ -3441,17 +3479,17 @@ fn printTreeChildLast(writer: *Writer, mode: Io.Terminal.Mode) !void { |
| 3441 | 3479 | }); |
| 3442 | 3480 | } |
| 3443 | 3481 | |
| 3444 | | /// Traverse the dependency graph depth-first and make it undirected by having |
| 3445 | | /// steps know their dependants (they only know dependencies at start). |
| 3446 | | /// Along the way, check that there is no dependency loop, and record the steps |
| 3447 | | /// in traversal order in `step_stack`. |
| 3448 | | /// Each step has its dependencies traversed in random order, this accomplishes |
| 3449 | | /// two things: |
| 3450 | | /// - `step_stack` will be in randomized-depth-first order, so the build runner |
| 3451 | | /// spawns initial steps in a random order |
| 3452 | | /// - each step's `dependants` list is also filled in a random order, so that |
| 3453 | | /// when it finishes executing in `makeStep`, it spawns next steps to run in |
| 3482 | /// Traverse the dependency graph depth-first and make it undirected by having steps know their dependants |
| 3483 | /// (they only know dependencies at start). |
| 3484 | /// |
| 3485 | /// Along the way, check that there is no dependency loop, and record the steps in traversal order in |
| 3486 | /// `step_stack`. |
| 3487 | /// |
| 3488 | /// Each step has its dependencies traversed in random order, this accomplishes two things: |
| 3489 | /// - `step_stack` will be in randomized-depth-first order, so the build runner spawns initial steps in a |
| 3454 | 3490 | /// random order |
| 3491 | /// - each step's `dependants` list is also filled in a random order, so that when it finishes executing in |
| 3492 | /// `makeStep`, it spawns next steps to run in random order |
| 3455 | 3493 | fn constructGraphAndCheckForDependencyLoop( |
| 3456 | 3494 | maker: *Maker, |
| 3457 | 3495 | step_index: Configuration.Step.Index, |