From 4a0cadf85b3dd751908158bd3703ea50456d7d8a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 14 Oct 2026 13:33:43 -0700 Subject: [PATCH 1/5] Fetch: slightly more helpful debug log when forking --- lib/compiler/Maker/Fetch.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compiler/Maker/Fetch.zig b/lib/compiler/Maker/Fetch.zig index 21b7b2597c3e33cf7f10e31c82c82bbf052d2785..d63408021f555006c2ceaec6c32105c524bdd778 100644 --- a/lib/compiler/Maker/Fetch.zig +++ b/lib/compiler/Maker/Fetch.zig @@ -595,7 +595,7 @@ pub fn run(f: *Fetch) RunError!void { if (remote.hash) |expected_hash| { const expected_project_id: Package.ProjectId = expected_hash.projectId(); if (job_queue.fork_set.getKeyPtrAdapted(expected_project_id, @as(JobQueue.Fork.Adapter, .{}))) |fork| { - log.debug("using fork {f} for {s}", .{ fork.path, fork.manifest.name }); + log.debug("using fork {f} for {s}", .{ fork.path, expected_hash.toSlice() }); fork.uses += 1; f.package_root = fork.path; f.remote_package_root = f.package_root; -- 2.54.0 From 35c2541cf9924cdcccf666afb173320fd610e9ef Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 14 Oct 2026 13:34:03 -0700 Subject: [PATCH 2/5] Maker: exempt forked packages from validateFingerprints because they intentionally override multiple packages hashes to have the forked contents. --- lib/compiler/Maker.zig | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index 325fa1ff1d5c4aa940c9e668a53615cf367e9ea0..253fd5df095883167e32cb5ebecc135c370732a8 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -1398,7 +1398,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { process.exit(0); } - try validateFingerprints(gpa, &job_queue); + try validateFingerprints(gpa, &job_queue, &fork_set); // Create the dependencies.zig file for configurer to // obtain via `@import("@dependencies")`. @@ -1749,7 +1749,11 @@ const ValidFingerprintTable = std.array_hash_map.Custom( /// Ensure that the dependency tree did not have any fingerprint matches with the same semantic version but /// differing package hashes. -fn validateFingerprints(gpa: Allocator, job_queue: *const Fetch.JobQueue) !void { +fn validateFingerprints( + gpa: Allocator, + job_queue: *const Fetch.JobQueue, + fork_set: *const Package.Fetch.JobQueue.ForkSet, +) !void { var any_errors = false; var table: ValidFingerprintTable = .empty; @@ -1757,8 +1761,13 @@ fn validateFingerprints(gpa: Allocator, job_queue: *const Fetch.JobQueue) !void for (job_queue.table.keys(), job_queue.table.values()) |pkg_hash, fetch| { if (!fetch.have_manifest) continue; + + // Forked packages are exempt since they overide multiple package hashes to have the forked contents. + const project_id: Package.ProjectId = .init(fetch.manifest.name, fetch.manifest.id); + if (fork_set.containsAdapted(project_id, @as(Fetch.JobQueue.Fork.Adapter, .{}))) continue; + const gop = try table.getOrPut(gpa, .{ - .project_id = .init(fetch.manifest.name, fetch.manifest.id), + .project_id = project_id, .version = fetch.manifest.version, }); if (gop.found_existing) { -- 2.54.0 From 5b4e91bc391b7c0f537e3eaeda3beb60b8813ad9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 14 Oct 2026 14:12:00 -0700 Subject: [PATCH 3/5] Maker: --print-tree supports paths and forks --- lib/compiler/Maker.zig | 52 +++++++++++++++++++++++++++++------- lib/compiler/Maker/Fetch.zig | 6 ++--- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index 253fd5df095883167e32cb5ebecc135c370732a8..d0f8117ce8eaf9a1f7e34f84ba97b6740abad397 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -1385,7 +1385,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { printPkgTree(.{ .writer = initStdoutWriter(io), .mode = try .detect(io, .stdout(), NO_COLOR, CLICOLOR_FORCE), - }, arena, &job_queue, &fetch) catch |err| switch (err) { + }, arena, graph.global_cache_root, &job_queue, &fetch, &fork_set) catch |err| switch (err) { error.WriteFailed => return stdout_writer_allocation.err.?, else => |e| return e, }; @@ -1469,7 +1469,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { const man = &f.manifest; const dep_names = man.dependencies.keys(); try mod.deps.ensureUnusedCapacity(arena, @intCast(dep_names.len)); - for (dep_names, man.dependencies.values()) |name, dep| { + for (dep_names, man.dependencies.values()) |name, *dep| { const dep_digest = Package.Fetch.depDigest( f.package_root, graph.global_cache_root, @@ -1804,8 +1804,10 @@ fn validateFingerprints( fn printPkgTree( terminal: Io.Terminal, arena: Allocator, + global_cache_root: Cache.Directory, job_queue: *const Fetch.JobQueue, root_fetch: *const Fetch, + fork_set: *const Package.Fetch.JobQueue.ForkSet, ) !void { if (!root_fetch.have_manifest) return; const names = root_fetch.manifest.dependencies.keys(); @@ -1814,37 +1816,49 @@ fn printPkgTree( .parent = null, .last = i == names.len - 1, }; - try printPkgTreeChild(terminal, arena, job_queue, &print_node, name, dep.hash); + switch (dep.location) { + .url => { + const child_hash = if (dep.hash) |h| Package.Hash.fromSlice(h) else null; + try printPkgTreeChild(terminal, arena, global_cache_root, job_queue, &print_node, name, child_hash, fork_set); + }, + .path => { + const child_hash = Package.Fetch.depDigest(root_fetch.package_root, global_cache_root, dep); + try printPkgTreeChild(terminal, arena, global_cache_root, job_queue, &print_node, name, child_hash, fork_set); + }, + } } } fn printPkgTreeChild( terminal: Io.Terminal, arena: Allocator, + global_cache_root: Cache.Directory, job_queue: *const Fetch.JobQueue, parent_print_node: *const PrintNode, dep_name: []const u8, - opt_hash: ?[]const u8, + opt_pkg_hash: ?Package.Hash, + fork_set: *const Package.Fetch.JobQueue.ForkSet, ) !void { const w = terminal.writer; try parent_print_node.printPrefixedChild(w, terminal.mode); try w.writeAll(dep_name); - const hash = opt_hash orelse { + const pkg_hash = opt_pkg_hash orelse { try terminal.setColor(.red); try w.writeAll(" (missing hash)\n"); try terminal.setColor(.reset); return; }; try terminal.setColor(.dim); - try w.print(" {s}", .{hash}); + try w.print(" {s}", .{pkg_hash.toSlice()}); try terminal.setColor(.reset); - const child_fetch = job_queue.table.get(.fromSlice(hash)) orelse { + const child_fetch = job_queue.table.get(pkg_hash) orelse { try terminal.setColor(.red); try w.writeAll(" (fetch failed)\n"); try terminal.setColor(.reset); return; }; + switch (child_fetch.lazy_status) { .eager, .available => {}, .unavailable => { @@ -1854,8 +1868,19 @@ fn printPkgTreeChild( return; }, } + if (!child_fetch.have_manifest) { + try w.writeByte('\n'); + return; + } + + const project_id: Package.ProjectId = .init(child_fetch.manifest.name, child_fetch.manifest.id); + if (fork_set.getKeyPtrAdapted(project_id, @as(Fetch.JobQueue.Fork.Adapter, .{}))) |fork| { + try terminal.setColor(.yellow); + try w.print(" (fork: {f})", .{fork.path}); + try terminal.setColor(.reset); + } + try w.writeByte('\n'); - if (!child_fetch.have_manifest) return; const names = child_fetch.manifest.dependencies.keys(); for (names, child_fetch.manifest.dependencies.values(), 0..) |name, *dep, i| { @@ -1863,7 +1888,16 @@ fn printPkgTreeChild( .parent = parent_print_node, .last = i == names.len - 1, }; - try printPkgTreeChild(terminal, arena, job_queue, &print_node, name, dep.hash); + switch (dep.location) { + .url => { + const child_hash = if (dep.hash) |h| Package.Hash.fromSlice(h) else null; + try printPkgTreeChild(terminal, arena, global_cache_root, job_queue, &print_node, name, child_hash, fork_set); + }, + .path => { + const child_hash = Package.Fetch.depDigest(child_fetch.package_root, global_cache_root, dep); + try printPkgTreeChild(terminal, arena, global_cache_root, job_queue, &print_node, name, child_hash, fork_set); + }, + } } } diff --git a/lib/compiler/Maker/Fetch.zig b/lib/compiler/Maker/Fetch.zig index d63408021f555006c2ceaec6c32105c524bdd778..0da66401f92fb09d61961db830ad4a0a7776630d 100644 --- a/lib/compiler/Maker/Fetch.zig +++ b/lib/compiler/Maker/Fetch.zig @@ -302,7 +302,7 @@ pub const JobQueue = struct { \\ pub const deps: []const struct { []const u8, []const u8 } = &.{ \\ ); - for (manifest.dependencies.keys(), manifest.dependencies.values()) |name, dep| { + for (manifest.dependencies.keys(), manifest.dependencies.values()) |name, *dep| { const h = depDigest(fetch.package_root, jq.global_cache, dep) orelse continue; try w.print( " .{{ \"{f}\", \"{f}\" }},\n", @@ -335,7 +335,7 @@ pub const JobQueue = struct { assert(root_fetch.have_manifest); const root_manifest = &root_fetch.manifest; - for (root_manifest.dependencies.keys(), root_manifest.dependencies.values()) |name, dep| { + for (root_manifest.dependencies.keys(), root_manifest.dependencies.values()) |name, *dep| { const h = depDigest(root_fetch.package_root, jq.global_cache, dep) orelse continue; try w.print( " .{{ \"{f}\", \"{f}\" }},\n", @@ -2033,7 +2033,7 @@ const Filter = struct { } }; -pub fn depDigest(pkg_root: Path, cache_root: Directory, dep: Manifest.Dependency) ?Package.Hash { +pub fn depDigest(pkg_root: Path, cache_root: Directory, dep: *const Manifest.Dependency) ?Package.Hash { if (dep.hash) |h| return .fromSlice(h); switch (dep.location) { -- 2.54.0 From 6cecad145d569bf9794760ca13bd2bf479a357c7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 14 Oct 2026 17:38:43 -0700 Subject: [PATCH 4/5] Maker: fix use case when --fork matches 2 or more pkgs Previously this always caused a compilation failure because it would correctly substitute multiple packages with the provided forked package, however, this ends up constructing a zig build-exe command line for the configurer that included multiple differnt modules that used the same files. Closes #31677 --- lib/compiler/Maker.zig | 37 ++++++++++++++++++++++++++---------- lib/compiler/Maker/Fetch.zig | 16 ++++++++++++---- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index d0f8117ce8eaf9a1f7e34f84ba97b6740abad397..829c28ff55bcd4e71bc336caca0c7623fe4a4c04 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -1444,17 +1444,34 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { // The first one is a dummy package for the current project. continue; } - if (!f.has_build_zig) - continue; - const hash_slice = try arena.dupe(u8, hash.toSlice()); + if (!f.has_build_zig) continue; - const m = try arena.create(CliModule); - m.* = .{ - .root_path = try f.package_root.toString(arena), - .name = hash_slice, - }; - deps_mod.deps.putAssumeCapacityNoClobber(hash_slice, m); - f.cli_module = m; + const project_id: Package.ProjectId = .init(f.manifest.name, f.manifest.id); + if (fork_set.getKeyPtrAdapted(project_id, @as(Fetch.JobQueue.Fork.Adapter, .{}))) |fork| { + const path = try fork.path.toString(arena); + const gop = deps_mod.deps.getOrPutAssumeCapacity(path); + if (gop.found_existing) { + assert(fork.uses > 1); + f.cli_module = gop.value_ptr.*; + } else { + const m = try arena.create(CliModule); + m.* = .{ + .root_path = try f.package_root.toString(arena), + .name = path, + }; + gop.value_ptr.* = m; + f.cli_module = m; + } + } else { + const hash_slice = try arena.dupe(u8, hash.toSlice()); + const m = try arena.create(CliModule); + m.* = .{ + .root_path = try f.package_root.toString(arena), + .name = hash_slice, + }; + deps_mod.deps.putAssumeCapacityNoClobber(hash_slice, m); + f.cli_module = m; + } } // Each build.zig module needs access to each of its dependencies' build.zig modules by diff --git a/lib/compiler/Maker/Fetch.zig b/lib/compiler/Maker/Fetch.zig index 0da66401f92fb09d61961db830ad4a0a7776630d..9fa9b121399b55d5745172bda914b1dc3a87a206 100644 --- a/lib/compiler/Maker/Fetch.zig +++ b/lib/compiler/Maker/Fetch.zig @@ -290,10 +290,18 @@ pub const JobQueue = struct { , .{std.fmt.alt(fetch.package_root, .formatEscapeString)}); if (fetch.has_build_zig) { - try w.print( - \\ pub const build_zig = @import("{f}"); - \\ - , .{std.zig.fmtString(hash_slice)}); + const project_id: Package.ProjectId = .init(fetch.manifest.name, fetch.manifest.id); + if (jq.fork_set.getKeyPtrAdapted(project_id, @as(JobQueue.Fork.Adapter, .{}))) |fork| { + try w.print( + \\ pub const build_zig = @import({qf}); + \\ + , .{fork.path}); + } else { + try w.print( + \\ pub const build_zig = @import({q}); + \\ + , .{hash_slice}); + } } if (fetch.have_manifest) { -- 2.54.0 From f6a00a4751c7d49f7a4406e9c640ebaee85662f9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 14 Oct 2026 19:00:42 -0700 Subject: [PATCH 5/5] Maker: guard project id construction with have_manifest --- lib/compiler/Maker.zig | 50 +++++++++++++++++++----------------- lib/compiler/Maker/Fetch.zig | 26 ++++++++++--------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index 829c28ff55bcd4e71bc336caca0c7623fe4a4c04..d9f0b132550e12a42f92acdda4be520b465439c5 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -1446,32 +1446,34 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { } if (!f.has_build_zig) continue; - const project_id: Package.ProjectId = .init(f.manifest.name, f.manifest.id); - if (fork_set.getKeyPtrAdapted(project_id, @as(Fetch.JobQueue.Fork.Adapter, .{}))) |fork| { - const path = try fork.path.toString(arena); - const gop = deps_mod.deps.getOrPutAssumeCapacity(path); - if (gop.found_existing) { - assert(fork.uses > 1); - f.cli_module = gop.value_ptr.*; - } else { - const m = try arena.create(CliModule); - m.* = .{ - .root_path = try f.package_root.toString(arena), - .name = path, - }; - gop.value_ptr.* = m; - f.cli_module = m; + if (f.have_manifest) { + const project_id: Package.ProjectId = .init(f.manifest.name, f.manifest.id); + if (fork_set.getKeyPtrAdapted(project_id, @as(Fetch.JobQueue.Fork.Adapter, .{}))) |fork| { + const path = try fork.path.toString(arena); + const gop = deps_mod.deps.getOrPutAssumeCapacity(path); + if (gop.found_existing) { + assert(fork.uses > 1); + f.cli_module = gop.value_ptr.*; + } else { + const m = try arena.create(CliModule); + m.* = .{ + .root_path = try f.package_root.toString(arena), + .name = path, + }; + gop.value_ptr.* = m; + f.cli_module = m; + } + continue; } - } else { - const hash_slice = try arena.dupe(u8, hash.toSlice()); - const m = try arena.create(CliModule); - m.* = .{ - .root_path = try f.package_root.toString(arena), - .name = hash_slice, - }; - deps_mod.deps.putAssumeCapacityNoClobber(hash_slice, m); - f.cli_module = m; } + const hash_slice = try arena.dupe(u8, hash.toSlice()); + const m = try arena.create(CliModule); + m.* = .{ + .root_path = try f.package_root.toString(arena), + .name = hash_slice, + }; + deps_mod.deps.putAssumeCapacityNoClobber(hash_slice, m); + f.cli_module = m; } // Each build.zig module needs access to each of its dependencies' build.zig modules by diff --git a/lib/compiler/Maker/Fetch.zig b/lib/compiler/Maker/Fetch.zig index 9fa9b121399b55d5745172bda914b1dc3a87a206..68294ffe3a856cd564bb4e365cb93b08189ab6a4 100644 --- a/lib/compiler/Maker/Fetch.zig +++ b/lib/compiler/Maker/Fetch.zig @@ -289,19 +289,21 @@ pub const JobQueue = struct { \\ , .{std.fmt.alt(fetch.package_root, .formatEscapeString)}); - if (fetch.has_build_zig) { - const project_id: Package.ProjectId = .init(fetch.manifest.name, fetch.manifest.id); - if (jq.fork_set.getKeyPtrAdapted(project_id, @as(JobQueue.Fork.Adapter, .{}))) |fork| { - try w.print( - \\ pub const build_zig = @import({qf}); - \\ - , .{fork.path}); - } else { - try w.print( - \\ pub const build_zig = @import({q}); - \\ - , .{hash_slice}); + if (fetch.has_build_zig) b: { + if (fetch.have_manifest) { + const project_id: Package.ProjectId = .init(fetch.manifest.name, fetch.manifest.id); + if (jq.fork_set.getKeyPtrAdapted(project_id, @as(JobQueue.Fork.Adapter, .{}))) |fork| { + try w.print( + \\ pub const build_zig = @import({qf}); + \\ + , .{fork.path}); + } + break :b; } + try w.print( + \\ pub const build_zig = @import({q}); + \\ + , .{hash_slice}); } if (fetch.have_manifest) { -- 2.54.0