authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-15 18:01:23-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-15 18:02:03-07:00
log215db865490b5d4d08171297603584841be97976
tree2fb0b8c2e8e6f3f33197b20814ec465a2beface7
parentd67152361ce131a7d49bbb8fbc0819578448d502

Maker: implement refetching from step-associated lazy deps

closes #37262

2 files changed, 83 insertions(+), 68 deletions(-)

lib/compiler/Maker.zig+80-65
......@@ -763,6 +763,8 @@ pub fn main(init: process.Init.Minimal) !void {
763763 break :s &protocol_server_allocation;
764764 } else null;
765765
766 var discovered_unlazy_set: Fetch.JobQueue.UnlazySet = .empty; // uses global arena
767
766768 configure: while (true) {
767769 // Set of files that, if modified, imply that recompiling and rerunning
768770 // configurer is needed.
......@@ -792,9 +794,12 @@ pub fn main(init: process.Init.Minimal) !void {
792794 .system_pkg_dir_path = system_pkg_dir_path,
793795 .print_configuration = print_configuration,
794796 .forks = forks.items,
797 .unlazy_set = &discovered_unlazy_set,
795798 .src_files = &configure_source_files,
796799 .error_bundle = if (protocol_server != null) &configure_error_bundle else null,
797800 })) |scanned_config| {
801 discovered_unlazy_set.clearRetainingCapacity();
802
798803 if (help_menu) {
799804 scanned_config.printUsage(&graph, initStdoutWriter(io)) catch |err| switch (err) {
800805 error.WriteFailed => return stdout_writer_allocation.err.?,
......@@ -914,20 +919,15 @@ pub fn main(init: process.Init.Minimal) !void {
914919 try select.concurrent(.message, Server.receiveMessage, .{s});
915920
916921 maker.watch = body.flags.watch;
917 maker.prepare(steps, &configure_source_files) catch |err| switch (err) {
918 error.DependencyLoopDetected => {
919 // TODO handle error.FailedButCacheIntact
920 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
921 process.exit(1);
922 },
923 else => |e| return e,
924 };
925 maker.checkSufficientMemory() catch |err| switch (err) {
926 error.InsufficientMemory => {
927 // TODO handle as error.AlreadyReported
922 maker.prepare(steps, &configure_source_files, &discovered_unlazy_set) catch |err| switch (err) {
923 error.OutOfMemory => |e| return e,
924 error.DependencyLoopDetected, error.InsufficientMemory => {
925 // TODO handle error.DependencyLoopDetected as error.FailedButCacheIntact
926 // TODO handle error.InsufficientMemory as error.AlreadyReported
928927 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
929928 process.exit(1);
930929 },
930 error.LazyDependencyNeeded => continue :configure,
931931 };
932932
933933 try maker.makeSteps(main_progress_node, null);
......@@ -985,20 +985,15 @@ pub fn main(init: process.Init.Minimal) !void {
985985 const initial_steps = try maker.resolveTopLevelSteps(step_names.items);
986986 defer gpa.free(initial_steps);
987987
988 maker.prepare(initial_steps, &configure_source_files) catch |err| switch (err) {
989 error.DependencyLoopDetected => {
990 // TODO handle as error.FailedButCacheIntact
991 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
992 process.exit(1);
993 },
994 else => |e| return e,
995 };
996 maker.checkSufficientMemory() catch |err| switch (err) {
997 error.InsufficientMemory => {
998 // TODO handle as error.AlreadyReported
988 maker.prepare(initial_steps, &configure_source_files, &discovered_unlazy_set) catch |err| switch (err) {
989 error.OutOfMemory => |e| return e,
990 error.DependencyLoopDetected, error.InsufficientMemory => {
991 // TODO handle error.DependencyLoopDetected as error.FailedButCacheIntact
992 // TODO handle error.InsufficientMemory as error.AlreadyReported
999993 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
1000994 process.exit(1);
1001995 },
996 error.LazyDependencyNeeded => continue :configure,
1002997 };
1003998
1004999 var w: Watch = w: {
......@@ -1146,6 +1141,7 @@ const ConfigureOptions = struct {
11461141 system_pkg_dir_path: ?[]const u8,
11471142 print_configuration: PrintConfiguration,
11481143 forks: []Fork,
1144 unlazy_set: *Fetch.JobQueue.UnlazySet,
11491145 src_files: *Cache.Manifest.SelfContainedFiles,
11501146 error_bundle: ?*ErrorBundle = null,
11511147};
......@@ -1161,8 +1157,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
11611157 var http_client: std.http.Client = .{ .allocator = gpa, .io = io };
11621158 defer http_client.deinit();
11631159
1164 var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{};
1165 var fork_set: Package.Fetch.JobQueue.ForkSet = .{};
1160 var fork_set: Fetch.JobQueue.ForkSet = .{};
11661161
11671162 {
11681163 // Populate fork_set.
......@@ -1297,7 +1292,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
12971292 // Reset fork match counts.
12981293 for (fork_set.keys()) |*fork| fork.uses = 0;
12991294
1300 var job_queue: Package.Fetch.JobQueue = .{
1295 var job_queue: Fetch.JobQueue = .{
13011296 .io = io,
13021297 .http_client = &http_client,
13031298 .global_cache = graph.global_cache_root,
......@@ -1307,7 +1302,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
13071302 },
13081303 .recursive = true,
13091304 .debug_hash = false,
1310 .unlazy_set = unlazy_set,
1305 .unlazy_set = options.unlazy_set.*,
13111306 .fork_set = fork_set,
13121307 .mode = options.fetch_mode,
13131308 .prog_node = fetch_prog_node,
......@@ -1324,7 +1319,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
13241319
13251320 const phantom_package_root: Cache.Path = .{ .root_dir = options.build_root.directory };
13261321
1327 var fetch: Package.Fetch = .{
1322 var fetch: Fetch = .{
13281323 .arena = std.heap.ArenaAllocator.init(gpa),
13291324 .location = .{ .relative_path = phantom_package_root },
13301325 .location_tok = 0,
......@@ -1356,11 +1351,11 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
13561351 job_queue.all_fetches.appendAssumeCapacity(&fetch);
13571352
13581353 job_queue.table.putAssumeCapacityNoClobber(
1359 Package.Fetch.relativePathDigest(phantom_package_root, graph.global_cache_root),
1354 Fetch.relativePathDigest(phantom_package_root, graph.global_cache_root),
13601355 &fetch,
13611356 );
13621357
1363 job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" });
1358 job_queue.group.async(io, Fetch.workerRun, .{ &fetch, "root" });
13641359 try job_queue.group.await(io);
13651360
13661361 {
......@@ -1501,7 +1496,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
15011496 const dep_names = man.dependencies.keys();
15021497 try mod.deps.ensureUnusedCapacity(arena, @intCast(dep_names.len));
15031498 for (dep_names, man.dependencies.values()) |name, *dep| {
1504 const dep_digest = Package.Fetch.depDigest(
1499 const dep_digest = Fetch.depDigest(
15051500 f.package_root,
15061501 graph.global_cache_root,
15071502 dep,
......@@ -1620,38 +1615,33 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
16201615
16211616 if (configuration.unlazy_deps.len != 0) {
16221617 var any_errors = false;
1618 options.unlazy_set.clearRetainingCapacity();
16231619 for (configuration.unlazy_deps) |unlazy_dep| {
1624 if (unlazy_dep.step.unwrap()) |step_index| {
1625 log.err("TODO implement step-associated lazy dependencies in maker", .{});
1626 any_errors = true;
1627 _ = step_index;
1628 } else {
1629 for (unlazy_dep.list.slice(&configuration)) |hash_string| {
1630 const hash = hash_string.slice(&configuration);
1631 assert(hash.len != 0);
1632 if (hash.len > Package.Hash.max_len) {
1633 log.err("invalid digest (length {d} exceeds maximum): {q}", .{ hash.len, hash });
1634 any_errors = true;
1635 continue;
1636 }
1637 log.info("fetching lazy dependency {s}", .{hash});
1638 try unlazy_set.put(arena, .fromSlice(hash), {});
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;
16391628 }
1629 log.info("fetching lazy dependency {s}", .{hash});
1630 try options.unlazy_set.put(arena, .fromSlice(hash), {});
16401631 }
16411632 }
16421633 if (any_errors) return error.FailedButCacheIntact;
16431634 if (options.system_pkg_dir_path) |p| {
1644 // In this mode, the system needs to provide these packages; they
1645 // cannot be fetched by Zig.
1635 // In this mode, the system needs to provide these packages; they cannot be fetched by Zig.
16461636 const s = Dir.path.sep_str;
1647 for (unlazy_set.keys()) |*hash| {
1637 for (options.unlazy_set.keys()) |*hash| {
16481638 log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{ p, hash.toSlice() });
16491639 }
16501640 log.info("remote package fetching disabled due to --system mode", .{});
16511641 log.info("dependencies might be avoidable depending on build configuration", .{});
16521642 return error.FailedButCacheIntact;
16531643 }
1654 continue :cp;
1644 if (options.unlazy_set.count() != 0) continue :cp;
16551645 }
16561646
16571647 if (config_man) |man| for (configuration.path_deps) |path_dep| {
......@@ -1791,7 +1781,7 @@ const ValidFingerprintTable = std.array_hash_map.Custom(
17911781fn validateFingerprints(
17921782 gpa: Allocator,
17931783 job_queue: *const Fetch.JobQueue,
1794 fork_set: *const Package.Fetch.JobQueue.ForkSet,
1784 fork_set: *const Fetch.JobQueue.ForkSet,
17951785) !void {
17961786 var any_errors = false;
17971787
......@@ -1846,7 +1836,7 @@ fn printPkgTree(
18461836 global_cache_root: Cache.Directory,
18471837 job_queue: *const Fetch.JobQueue,
18481838 root_fetch: *const Fetch,
1849 fork_set: *const Package.Fetch.JobQueue.ForkSet,
1839 fork_set: *const Fetch.JobQueue.ForkSet,
18501840) !void {
18511841 if (!root_fetch.have_manifest) return;
18521842 const names = root_fetch.manifest.dependencies.keys();
......@@ -1861,7 +1851,7 @@ fn printPkgTree(
18611851 try printPkgTreeChild(terminal, arena, global_cache_root, job_queue, &print_node, name, child_hash, fork_set);
18621852 },
18631853 .path => {
1864 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);
18651855 try printPkgTreeChild(terminal, arena, global_cache_root, job_queue, &print_node, name, child_hash, fork_set);
18661856 },
18671857 }
......@@ -1876,7 +1866,7 @@ fn printPkgTreeChild(
18761866 parent_print_node: *const PrintNode,
18771867 dep_name: []const u8,
18781868 opt_pkg_hash: ?Package.Hash,
1879 fork_set: *const Package.Fetch.JobQueue.ForkSet,
1869 fork_set: *const Fetch.JobQueue.ForkSet,
18801870) !void {
18811871 const w = terminal.writer;
18821872 try parent_print_node.printPrefixedChild(w, terminal.mode);
......@@ -1933,7 +1923,7 @@ fn printPkgTreeChild(
19331923 try printPkgTreeChild(terminal, arena, global_cache_root, job_queue, &print_node, name, child_hash, fork_set);
19341924 },
19351925 .path => {
1936 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);
19371927 try printPkgTreeChild(terminal, arena, global_cache_root, job_queue, &print_node, name, child_hash, fork_set);
19381928 },
19391929 }
......@@ -2660,7 +2650,8 @@ fn prepare(
26602650 maker: *Maker,
26612651 step_indices: []const Configuration.Step.Index,
26622652 configure_source_files: *const Cache.Manifest.SelfContainedFiles,
2663) !void {
2653 discovered_unlazy_set: *Fetch.JobQueue.UnlazySet,
2654) error{ InsufficientMemory, OutOfMemory, DependencyLoopDetected, LazyDependencyNeeded }!void {
26642655 const gpa = maker.gpa;
26652656 const graph = maker.graph;
26662657 const arena = graph.arena;
......@@ -2707,6 +2698,30 @@ fn prepare(
27072698 for (starting_steps) |s| {
27082699 try constructGraphAndCheckForDependencyLoop(maker, s, &maker.step_stack, rand);
27092700 }
2701
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);
27102725}
27112726
27122727fn checkSufficientMemory(maker: *Maker) error{InsufficientMemory}!void {
......@@ -3464,17 +3479,17 @@ fn printTreeChildLast(writer: *Writer, mode: Io.Terminal.Mode) !void {
34643479 });
34653480}
34663481
3467/// Traverse the dependency graph depth-first and make it undirected by having
3468/// steps know their dependants (they only know dependencies at start).
3469/// Along the way, check that there is no dependency loop, and record the steps
3470/// in traversal order in `step_stack`.
3471/// Each step has its dependencies traversed in random order, this accomplishes
3472/// two things:
3473/// - `step_stack` will be in randomized-depth-first order, so the build runner
3474/// spawns initial steps in a random order
3475/// - each step's `dependants` list is also filled in a random order, so that
3476/// 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
34773490/// 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
34783493fn constructGraphAndCheckForDependencyLoop(
34793494 maker: *Maker,
34803495 step_index: Configuration.Step.Index,
lib/compiler/Maker/Fetch.zig+3-3
......@@ -153,11 +153,11 @@ pub const JobQueue = struct {
153153 debug_hash: bool,
154154 mode: Mode,
155155 /// Set of hashes that will be additionally fetched even if they are marked
156 /// as lazy.
157 unlazy_set: UnlazySet = .{},
156 /// as lazy. Read-only.
157 unlazy_set: UnlazySet = .empty,
158158 /// Identifies paths that override all packages in the tree with matching
159159 /// project ids.
160 fork_set: ForkSet = .{},
160 fork_set: ForkSet = .empty,
161161
162162 pub const Mode = enum {
163163 /// Non-lazy dependencies are always fetched.