authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-07 02:46:47+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-07 02:46:47+01:00
log12cb5b92851f601c44d48deadea9934146edcffa
tree12d722c76642c4f501e027c56a0e51f5b7a1f0a3
parent36b65ab59e5e514ad06a11cde96b87c565d86ac8
parent355c6260015292642badf0e8b786e676fbb8c961

Merge pull request 'ability to override packages locally' (#31138) from fork-cli into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31138

6 files changed, 343 insertions(+), 358 deletions(-)

lib/compiler/build_runner.zig+26-23
......@@ -1573,6 +1573,23 @@ fn printUsage(b: *std.Build, w: *Writer) !void {
15731573 \\ -fsys=[name] Enable a system integration
15741574 \\ -fno-sys=[name] Disable a system integration
15751575 \\
1576 \\ -fdarling, -fno-darling Integration with system-installed Darling to
1577 \\ execute macOS programs on Linux hosts
1578 \\ (default: no)
1579 \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute
1580 \\ foreign-architecture programs on Linux hosts
1581 \\ (default: no)
1582 \\ --libc-runtimes [path] Enhances QEMU integration by providing dynamic libc
1583 \\ (e.g. glibc or musl) built for multiple foreign
1584 \\ architectures, allowing execution of non-native
1585 \\ programs that link with libc.
1586 \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
1587 \\ ARM64 macOS hosts. (default: no)
1588 \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
1589 \\ execute WASI binaries. (default: no)
1590 \\ -fwine, -fno-wine Integration with system-installed Wine to execute
1591 \\ Windows programs on Linux hosts. (default: no)
1592 \\
15761593 \\ Available System Integrations: Enabled:
15771594 \\
15781595 );
......@@ -1592,33 +1609,16 @@ fn printUsage(b: *std.Build, w: *Writer) !void {
15921609 try w.writeAll(
15931610 \\
15941611 \\General Options:
1612 \\ -h, --help Print this help and exit
1613 \\ -l, --list-steps Print available steps
1614 \\
15951615 \\ -p, --prefix [path] Where to install files (default: zig-out)
15961616 \\ --prefix-lib-dir [path] Where to install libraries
15971617 \\ --prefix-exe-dir [path] Where to install executables
15981618 \\ --prefix-include-dir [path] Where to install C header files
1599 \\
16001619 \\ --release[=mode] Request release mode, optionally specifying a
16011620 \\ preferred optimization mode: fast, safe, small
16021621 \\
1603 \\ -fdarling, -fno-darling Integration with system-installed Darling to
1604 \\ execute macOS programs on Linux hosts
1605 \\ (default: no)
1606 \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute
1607 \\ foreign-architecture programs on Linux hosts
1608 \\ (default: no)
1609 \\ --libc-runtimes [path] Enhances QEMU integration by providing dynamic libc
1610 \\ (e.g. glibc or musl) built for multiple foreign
1611 \\ architectures, allowing execution of non-native
1612 \\ programs that link with libc.
1613 \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
1614 \\ ARM64 macOS hosts. (default: no)
1615 \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
1616 \\ execute WASI binaries. (default: no)
1617 \\ -fwine, -fno-wine Integration with system-installed Wine to execute
1618 \\ Windows programs on Linux hosts. (default: no)
1619 \\
1620 \\ -h, --help Print this help and exit
1621 \\ -l, --list-steps Print available steps
16221622 \\ --verbose Print commands before executing them
16231623 \\ --color [auto|off|on] Enable or disable colored error messages
16241624 \\ --error-style [style] Control how build errors are printed
......@@ -1641,9 +1641,6 @@ fn printUsage(b: *std.Build, w: *Writer) !void {
16411641 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
16421642 \\ --test-timeout <timeout> Limit execution time of unit tests, terminating if exceeded.
16431643 \\ The timeout must include a unit: ns, us, ms, s, m, h
1644 \\ --fetch[=mode] Fetch dependency tree (optionally choose laziness) and exit
1645 \\ needed (Default) Lazy dependencies are fetched as needed
1646 \\ all Lazy dependencies are always fetched
16471644 \\ --watch Continuously rebuild when source files are modified
16481645 \\ --debounce <ms> Delay before rebuilding after changed file detected
16491646 \\ --webui[=ip] Enable the web interface on the given IP address
......@@ -1656,6 +1653,12 @@ fn printUsage(b: *std.Build, w: *Writer) !void {
16561653 \\ -fincremental Enable incremental compilation
16571654 \\ -fno-incremental Disable incremental compilation
16581655 \\
1656 \\Package Management Options:
1657 \\ --fetch[=mode] Fetch dependency tree (optionally choose laziness) and exit
1658 \\ needed (Default) Lazy dependencies are fetched as needed
1659 \\ all Lazy dependencies are always fetched
1660 \\ --fork=[path] Override one or more projects from dependency tree
1661 \\
16591662 \\Advanced Options:
16601663 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
16611664 \\ -fno-reference-trace Disable reference trace
src/Package.zig+50-58
......@@ -6,10 +6,6 @@ pub const Fetch = @import("Package/Fetch.zig");
66pub const build_zig_basename = "build.zig";
77pub const Manifest = @import("Package/Manifest.zig");
88
9pub const multihash_len = 1 + 1 + Hash.Algo.digest_length;
10pub const multihash_hex_digest_len = 2 * multihash_len;
11pub const MultiHashHexDigest = [multihash_hex_digest_len]u8;
12
139pub const Fingerprint = packed struct(u64) {
1410 id: u32,
1511 checksum: u32,
......@@ -77,20 +73,6 @@ pub const Hash = struct {
7773 return std.mem.eql(u8, &a.bytes, &b.bytes);
7874 }
7975
80 /// Distinguishes whether the legacy multihash format is being stored here.
81 pub fn isOld(h: *const Hash) bool {
82 if (h.bytes.len < 2) return false;
83 const their_multihash_func = std.fmt.parseInt(u8, h.bytes[0..2], 16) catch return false;
84 if (@as(MultihashFunction, @enumFromInt(their_multihash_func)) != multihash_function) return false;
85 if (h.toSlice().len != multihash_hex_digest_len) return false;
86 return std.mem.indexOfScalar(u8, &h.bytes, '-') == null;
87 }
88
89 test isOld {
90 const h: Hash = .fromSlice("1220138f4aba0c01e66b68ed9e1e1e74614c06e4743d88bc58af4f1c3dd0aae5fea7");
91 try std.testing.expect(h.isOld());
92 }
93
9476 /// Produces "$name-$semver-$hashplus".
9577 /// * name is the name field from build.zig.zon, asserted to be at most 32
9678 /// bytes and assumed be a valid zig identifier
......@@ -137,55 +119,65 @@ pub const Hash = struct {
137119 _ = std.fmt.bufPrint(result.bytes[i..], "{x}", .{&bin_digest}) catch unreachable;
138120 return result;
139121 }
140};
141122
142pub const MultihashFunction = enum(u16) {
143 identity = 0x00,
144 sha1 = 0x11,
145 @"sha2-256" = 0x12,
146 @"sha2-512" = 0x13,
147 @"sha3-512" = 0x14,
148 @"sha3-384" = 0x15,
149 @"sha3-256" = 0x16,
150 @"sha3-224" = 0x17,
151 @"sha2-384" = 0x20,
152 @"sha2-256-trunc254-padded" = 0x1012,
153 @"sha2-224" = 0x1013,
154 @"sha2-512-224" = 0x1014,
155 @"sha2-512-256" = 0x1015,
156 @"blake2b-256" = 0xb220,
157 _,
158};
123 pub fn projectId(hash: *const Hash) ProjectId {
124 const bytes = hash.toSlice();
125 const name = std.mem.sliceTo(bytes, '-');
126 const encoded_hashplus = bytes[bytes.len - 44 ..];
127 var hashplus: [33]u8 = undefined;
128 std.base64.url_safe_no_pad.Decoder.decode(&hashplus, encoded_hashplus) catch unreachable;
129 const fingerprint_id = std.mem.readInt(u32, hashplus[0..4], .little);
130 return .init(name, fingerprint_id);
131 }
159132
160pub const multihash_function: MultihashFunction = switch (Hash.Algo) {
161 std.crypto.hash.sha2.Sha256 => .@"sha2-256",
162 else => unreachable,
163};
133 test projectId {
134 const hash: Hash = .fromSlice("pulseaudio-16.1.1-9-mk_62MZkNwBaFwiZ7ZVrYRIf_3dTqqJR5PbMRCJzSuLw");
135 const project_id = hash.projectId();
164136
165pub fn multiHashHexDigest(digest: Hash.Digest) MultiHashHexDigest {
166 const hex_charset = std.fmt.hex_charset;
137 var expected_name: [32]u8 = @splat(0);
138 expected_name[0.."pulseaudio".len].* = "pulseaudio".*;
139 try std.testing.expectEqualSlices(u8, &expected_name, &project_id.padded_name);
167140
168 var result: MultiHashHexDigest = undefined;
141 try std.testing.expectEqual(0xd8fa4f9a, project_id.fingerprint_id);
142 }
169143
170 result[0] = hex_charset[@intFromEnum(multihash_function) >> 4];
171 result[1] = hex_charset[@intFromEnum(multihash_function) & 15];
144 test "projectId with dashes in the base64" {
145 const hash: Hash = .fromSlice("dvui-0.4.0-dev-AQFJmayi2gAKE7FeJoF61v5U1IV9-SupoEcFutIZYpkC");
146 const project_id = hash.projectId();
172147
173 result[2] = hex_charset[Hash.Algo.digest_length >> 4];
174 result[3] = hex_charset[Hash.Algo.digest_length & 15];
148 var expected_name: [32]u8 = @splat(0);
149 expected_name[0.."dvui".len].* = "dvui".*;
150 try std.testing.expectEqualSlices(u8, &expected_name, &project_id.padded_name);
175151
176 for (digest, 0..) |byte, i| {
177 result[4 + i * 2] = hex_charset[byte >> 4];
178 result[5 + i * 2] = hex_charset[byte & 15];
152 try std.testing.expectEqual(0x99490101, project_id.fingerprint_id);
179153 }
180 return result;
181}
154};
182155
183comptime {
184 // We avoid unnecessary uleb128 code in hexDigest by asserting here the
185 // values are small enough to be contained in the one-byte encoding.
186 assert(@intFromEnum(multihash_function) < 127);
187 assert(Hash.Algo.digest_length < 127);
188}
156/// Minimum information required to identify whether a package is an artifact
157/// of a given project.
158pub const ProjectId = struct {
159 /// Bytes after name.len are set to zero.
160 padded_name: [32]u8,
161 fingerprint_id: u32,
162
163 pub fn init(name: []const u8, fingerprint_id: u32) ProjectId {
164 var padded_name: [32]u8 = @splat(0);
165 @memcpy(padded_name[0..name.len], name);
166 return .{
167 .padded_name = padded_name,
168 .fingerprint_id = fingerprint_id,
169 };
170 }
171
172 pub fn eql(a: *const ProjectId, b: *const ProjectId) bool {
173 return a.fingerprint_id == b.fingerprint_id and std.mem.eql(u8, &a.padded_name, &b.padded_name);
174 }
175
176 pub fn hash(a: *const ProjectId) u64 {
177 const x: u64 = @bitCast(a.padded_name[0..8].*);
178 return std.hash.int(x | a.fingerprint_id);
179 }
180};
189181
190182test Hash {
191183 const example_digest: Hash.Digest = .{
src/Package/Fetch.zig+79-252
......@@ -76,8 +76,9 @@ use_latest_commit: bool,
7676/// Relative to the build root of the root package.
7777package_root: Cache.Path,
7878error_bundle: ErrorBundle.Wip,
79manifest: ?Manifest,
79manifest: Manifest,
8080manifest_ast: std.zig.Ast,
81have_manifest: bool,
8182computed_hash: ComputedHash,
8283/// Fetch logic notices whether a package has a build.zig file and sets this flag.
8384has_build_zig: bool,
......@@ -142,6 +143,9 @@ pub const JobQueue = struct {
142143 /// Set of hashes that will be additionally fetched even if they are marked
143144 /// as lazy.
144145 unlazy_set: UnlazySet = .{},
146 /// Identifies paths that override all packages in the tree with matching
147 /// project ids.
148 fork_set: ForkSet = .{},
145149
146150 pub const Mode = enum {
147151 /// Non-lazy dependencies are always fetched.
......@@ -152,6 +156,38 @@ pub const JobQueue = struct {
152156 };
153157 pub const Table = std.AutoArrayHashMapUnmanaged(Package.Hash, *Fetch);
154158 pub const UnlazySet = std.AutoArrayHashMapUnmanaged(Package.Hash, void);
159 pub const ForkSet = std.ArrayHashMapUnmanaged(Fork, void, Fork.Context, false);
160
161 pub const Fork = struct {
162 path: Cache.Path,
163 manifest_ast: std.zig.Ast,
164 manifest: Package.Manifest,
165 uses: usize,
166
167 pub const Context = struct {
168 pub fn hash(_: @This(), a: Fork) u32 {
169 const project_id: Package.ProjectId = .init(a.manifest.name, a.manifest.id);
170 return @truncate(project_id.hash());
171 }
172
173 pub fn eql(_: @This(), a: Fork, b: Fork, _: usize) bool {
174 const a_project_id: Package.ProjectId = .init(a.manifest.name, a.manifest.id);
175 const b_project_id: Package.ProjectId = .init(b.manifest.name, b.manifest.id);
176 return a_project_id.eql(&b_project_id);
177 }
178 };
179
180 pub const Adapter = struct {
181 pub fn hash(_: @This(), a: Package.ProjectId) u32 {
182 return @truncate(a.hash());
183 }
184
185 pub fn eql(_: @This(), a_project_id: Package.ProjectId, b: Fork, _: usize) bool {
186 const b_project_id: Package.ProjectId = .init(b.manifest.name, b.manifest.id);
187 return a_project_id.eql(&b_project_id);
188 }
189 };
190 };
155191
156192 pub fn deinit(jq: *JobQueue) void {
157193 const io = jq.io;
......@@ -248,7 +284,8 @@ pub const JobQueue = struct {
248284 , .{std.zig.fmtString(hash_slice)});
249285 }
250286
251 if (fetch.manifest) |*manifest| {
287 if (fetch.have_manifest) {
288 const manifest = &fetch.manifest;
252289 try buf.appendSlice(
253290 \\ pub const deps: []const struct { []const u8, []const u8 } = &.{
254291 \\
......@@ -283,7 +320,8 @@ pub const JobQueue = struct {
283320 );
284321
285322 const root_fetch = jq.all_fetches.items[0];
286 const root_manifest = &root_fetch.manifest.?;
323 assert(root_fetch.have_manifest);
324 const root_manifest = &root_fetch.manifest;
287325
288326 for (root_manifest.dependencies.keys(), root_manifest.dependencies.values()) |name, dep| {
289327 const h = depDigest(root_fetch.package_root, jq.global_cache, dep) orelse continue;
......@@ -536,6 +574,19 @@ pub fn run(f: *Fetch) RunError!void {
536574 var resource_buffer: [init_resource_buffer_size]u8 = undefined;
537575
538576 if (remote.hash) |expected_hash| {
577 const expected_project_id: Package.ProjectId = expected_hash.projectId();
578 if (job_queue.fork_set.getKeyPtrAdapted(expected_project_id, @as(JobQueue.Fork.Adapter, .{}))) |fork| {
579 log.debug("using fork {f} for {s}", .{ fork.path, fork.manifest.name });
580 fork.uses += 1;
581 f.package_root = fork.path;
582 f.manifest_ast = fork.manifest_ast;
583 f.manifest = fork.manifest;
584 f.have_manifest = true;
585 try checkBuildFileExistence(f);
586 if (!job_queue.recursive) return;
587 return queueJobsForDeps(f);
588 }
589
539590 const package_root = try job_queue.root_pkg_path.join(arena, expected_hash.toSlice());
540591 if (package_root.root_dir.handle.access(io, package_root.sub_path, .{})) |_| {
541592 assert(f.lazy_status != .unavailable);
......@@ -673,7 +724,7 @@ fn runResource(
673724 try loadManifest(f, pkg_path);
674725
675726 const filter: Filter = .{
676 .include_paths = if (f.manifest) |m| m.paths else .{},
727 .include_paths = if (f.have_manifest) f.manifest.paths else .{},
677728 };
678729
679730 // Ignore errors that were excluded by manifest, such as failure to
......@@ -728,21 +779,11 @@ fn runResource(
728779
729780 if (remote_hash) |declared_hash| {
730781 const hash_tok = f.hash_tok.unwrap().?;
731 if (declared_hash.isOld()) {
732 const actual_hex = Package.multiHashHexDigest(f.computed_hash.digest);
733 if (!std.mem.eql(u8, declared_hash.toSlice(), &actual_hex)) {
734 return f.fail(hash_tok, try eb.printString(
735 "hash mismatch: manifest declares '{s}' but the fetched package has '{s}'",
736 .{ declared_hash.toSlice(), actual_hex },
737 ));
738 }
739 } else {
740 if (!computed_package_hash.eql(&declared_hash)) {
741 return f.fail(hash_tok, try eb.printString(
742 "hash mismatch: manifest declares '{s}' but the fetched package has '{s}'",
743 .{ declared_hash.toSlice(), computed_package_hash.toSlice() },
744 ));
745 }
782 if (!computed_package_hash.eql(&declared_hash)) {
783 return f.fail(hash_tok, try eb.printString(
784 "hash mismatch: manifest declares '{s}' but the fetched package has '{s}'",
785 .{ declared_hash.toSlice(), computed_package_hash.toSlice() },
786 ));
746787 }
747788 } else if (!f.omit_missing_hash_error) {
748789 const notes_len = 1;
......@@ -766,7 +807,8 @@ fn runResource(
766807
767808pub fn computedPackageHash(f: *const Fetch) Package.Hash {
768809 const saturated_size = std.math.cast(u32, f.computed_hash.total_size) orelse std.math.maxInt(u32);
769 if (f.manifest) |man| {
810 if (f.have_manifest) {
811 const man = &f.manifest;
770812 var version_buffer: [32]u8 = undefined;
771813 const version: []const u8 = std.fmt.bufPrint(&version_buffer, "{f}", .{man.version}) catch &version_buffer;
772814 return .init(f.computed_hash.digest, man.name, version, man.id, saturated_size);
......@@ -801,45 +843,28 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void {
801843 const io = f.job_queue.io;
802844 const eb = &f.error_bundle;
803845 const arena = f.arena.allocator();
804 const manifest_bytes = pkg_root.root_dir.handle.readFileAllocOptions(
846 const manifest_path = try pkg_root.join(arena, Manifest.basename);
847
848 Manifest.load(
805849 io,
806 try fs.path.join(arena, &.{ pkg_root.sub_path, Manifest.basename }),
807850 arena,
808 .limited(Manifest.max_bytes),
809 .@"1",
810 0,
851 manifest_path,
852 &f.manifest_ast,
853 eb,
854 &f.manifest,
855 f.allow_missing_paths_field,
811856 ) catch |err| switch (err) {
812857 error.FileNotFound => return,
858 error.Canceled => |e| return e,
859 error.ErrorsBundled => return error.FetchFailed,
813860 else => |e| {
814 const file_path = try pkg_root.join(arena, Manifest.basename);
815861 try eb.addRootErrorMessage(.{
816 .msg = try eb.printString("unable to load package manifest '{f}': {t}", .{ file_path, e }),
862 .msg = try eb.printString("unable to load package manifest '{f}': {t}", .{ manifest_path, e }),
817863 });
818864 return error.FetchFailed;
819865 },
820866 };
821
822 const ast = &f.manifest_ast;
823 ast.* = try std.zig.Ast.parse(arena, manifest_bytes, .zon);
824
825 if (ast.errors.len > 0) {
826 const file_path = try std.fmt.allocPrint(arena, "{f}" ++ fs.path.sep_str ++ Manifest.basename, .{pkg_root});
827 try std.zig.putAstErrorsIntoBundle(arena, ast.*, file_path, eb);
828 return error.FetchFailed;
829 }
830
831 const rng: std.Random.IoSource = .{ .io = io };
832
833 f.manifest = try Manifest.parse(arena, ast.*, rng.interface(), .{
834 .allow_missing_paths_field = f.allow_missing_paths_field,
835 });
836 const manifest = &f.manifest.?;
837
838 if (manifest.errors.len > 0) {
839 const src_path = try eb.printString("{f}" ++ fs.path.sep_str ++ "{s}", .{ pkg_root, Manifest.basename });
840 try manifest.copyErrorsIntoBundle(ast.*, src_path, eb);
841 return error.FetchFailed;
842 }
867 f.have_manifest = true;
843868}
844869
845870fn queueJobsForDeps(f: *Fetch) RunError!void {
......@@ -848,7 +873,8 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {
848873 assert(f.job_queue.recursive);
849874
850875 // If the package does not have a build.zig.zon file then there are no dependencies.
851 const manifest = f.manifest orelse return;
876 if (!f.have_manifest) return;
877 const manifest = &f.manifest;
852878
853879 const new_fetches, const prog_names = nf: {
854880 const parent_arena = f.arena.allocator();
......@@ -953,8 +979,9 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {
953979
954980 .package_root = undefined,
955981 .error_bundle = undefined,
956 .manifest = null,
982 .manifest = undefined,
957983 .manifest_ast = undefined,
984 .have_manifest = false,
958985 .computed_hash = undefined,
959986 .has_build_zig = false,
960987 .oom_flag = false,
......@@ -1931,7 +1958,7 @@ const Filter = struct {
19311958 include_paths: std.StringArrayHashMapUnmanaged(void) = .empty,
19321959
19331960 /// sub_path is relative to the package root.
1934 pub fn includePath(self: Filter, sub_path: []const u8) bool {
1961 pub fn includePath(self: *const Filter, sub_path: []const u8) bool {
19351962 if (self.include_paths.count() == 0) return true;
19361963 if (self.include_paths.contains("")) return true;
19371964 if (self.include_paths.contains(".")) return true;
......@@ -2202,206 +2229,6 @@ const UnpackResult = struct {
22022229 }
22032230};
22042231
2205test "set executable bit based on file content" {
2206 if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest;
2207 const gpa = std.testing.allocator;
2208 const io = std.testing.io;
2209
2210 var tmp = std.testing.tmpDir(.{});
2211 defer tmp.cleanup();
2212
2213 const tarball_name = "executables.tar.gz";
2214 try saveEmbedFile(io, tarball_name, tmp.dir);
2215 const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });
2216 defer gpa.free(tarball_path);
2217
2218 // $ tar -tvf executables.tar.gz
2219 // drwxrwxr-x 0 executables/
2220 // -rwxrwxr-x 170 executables/hello
2221 // lrwxrwxrwx 0 executables/hello_ln -> hello
2222 // -rw-rw-r-- 0 executables/file1
2223 // -rw-rw-r-- 17 executables/script_with_shebang_without_exec_bit
2224 // -rwxrwxr-x 7 executables/script_without_shebang
2225 // -rwxrwxr-x 17 executables/script
2226
2227 var fb: TestFetchBuilder = undefined;
2228 var fetch = try fb.build(gpa, io, tmp.dir, tarball_path);
2229 defer fb.deinit();
2230
2231 try fetch.run();
2232 try std.testing.expectEqualStrings(
2233 "1220fecb4c06a9da8673c87fe8810e15785f1699212f01728eadce094d21effeeef3",
2234 &Package.multiHashHexDigest(fetch.computed_hash.digest),
2235 );
2236
2237 var out = try fb.packageDir();
2238 defer out.close(io);
2239 const S = std.posix.S;
2240 // expect executable bit not set
2241 try std.testing.expect((try out.statFile(io, "file1", .{})).permissions.toMode() & S.IXUSR == 0);
2242 try std.testing.expect((try out.statFile(io, "script_without_shebang", .{})).permissions.toMode() & S.IXUSR == 0);
2243 // expect executable bit set
2244 try std.testing.expect((try out.statFile(io, "hello", .{})).permissions.toMode() & S.IXUSR != 0);
2245 try std.testing.expect((try out.statFile(io, "script", .{})).permissions.toMode() & S.IXUSR != 0);
2246 try std.testing.expect((try out.statFile(io, "script_with_shebang_without_exec_bit", .{})).permissions.toMode() & S.IXUSR != 0);
2247 try std.testing.expect((try out.statFile(io, "hello_ln", .{})).permissions.toMode() & S.IXUSR != 0);
2248
2249 //
2250 // $ ls -al zig-cache/tmp/OCz9ovUcstDjTC_U/zig-global-cache/p/1220fecb4c06a9da8673c87fe8810e15785f1699212f01728eadce094d21effeeef3
2251 // -rw-rw-r-- 1 0 Apr file1
2252 // -rwxrwxr-x 1 170 Apr hello
2253 // lrwxrwxrwx 1 5 Apr hello_ln -> hello
2254 // -rwxrwxr-x 1 17 Apr script
2255 // -rw-rw-r-- 1 7 Apr script_without_shebang
2256 // -rwxrwxr-x 1 17 Apr script_with_shebang_without_exec_bit
2257}
2258
2259fn saveEmbedFile(io: Io, comptime tarball_name: []const u8, dir: Io.Dir) !void {
2260 //const tarball_name = "duplicate_paths_excluded.tar.gz";
2261 const tarball_content = @embedFile("Fetch/testdata/" ++ tarball_name);
2262 var tmp_file = try dir.createFile(io, tarball_name, .{});
2263 defer tmp_file.close(io);
2264 try tmp_file.writeStreamingAll(io, tarball_content);
2265}
2266
2267// Builds Fetch with required dependencies, clears dependencies on deinit().
2268const TestFetchBuilder = struct {
2269 http_client: std.http.Client,
2270 global_cache_directory: Cache.Directory,
2271 local_cache_path: Cache.Path,
2272 job_queue: Fetch.JobQueue,
2273 fetch: Fetch,
2274
2275 fn build(
2276 self: *TestFetchBuilder,
2277 allocator: std.mem.Allocator,
2278 io: Io,
2279 cache_parent_dir: std.Io.Dir,
2280 path_or_url: []const u8,
2281 ) !*Fetch {
2282 const global_cache_dir = try cache_parent_dir.createDirPathOpen(io, "zig-global-cache", .{});
2283 const package_root_dir = try cache_parent_dir.createDirPathOpen(io, "local-project-root", .{});
2284
2285 self.http_client = .{ .allocator = allocator, .io = io };
2286 self.global_cache_directory = .{ .handle = global_cache_dir, .path = "zig-global-cache" };
2287 self.local_cache_path = .{
2288 .root_dir = .{ .handle = package_root_dir, .path = "local-project-root" },
2289 .sub_path = ".zig-cache",
2290 };
2291
2292 self.job_queue = .{
2293 .io = io,
2294 .http_client = &self.http_client,
2295 .global_cache = self.global_cache_directory,
2296 .local_cache = self.local_cache_path,
2297 .root_pkg_path = .{
2298 .root_dir = .{ .handle = package_root_dir, .path = "local-project-root" },
2299 .sub_path = "zig-pkg",
2300 },
2301 .recursive = false,
2302 .read_only = false,
2303 .debug_hash = false,
2304 .mode = .needed,
2305 .prog_node = std.Progress.Node.none,
2306 };
2307
2308 self.fetch = .{
2309 .arena = std.heap.ArenaAllocator.init(allocator),
2310 .location = .{ .path_or_url = path_or_url },
2311 .location_tok = 0,
2312 .hash_tok = .none,
2313 .name_tok = 0,
2314 .lazy_status = .eager,
2315 .parent_package_root = .{ .root_dir = .{ .handle = package_root_dir, .path = null } },
2316 .parent_manifest_ast = null,
2317 .prog_node = std.Progress.Node.none,
2318 .job_queue = &self.job_queue,
2319 .omit_missing_hash_error = true,
2320 .allow_missing_paths_field = false,
2321 .use_latest_commit = true,
2322
2323 .package_root = undefined,
2324 .error_bundle = undefined,
2325 .manifest = null,
2326 .manifest_ast = undefined,
2327 .computed_hash = undefined,
2328 .has_build_zig = false,
2329 .oom_flag = false,
2330 .latest_commit = null,
2331
2332 .module = null,
2333 };
2334 return &self.fetch;
2335 }
2336
2337 fn deinit(self: *TestFetchBuilder) void {
2338 const io = self.job_queue.io;
2339 self.fetch.deinit();
2340 self.job_queue.deinit();
2341 self.fetch.prog_node.end();
2342 self.global_cache_directory.handle.close(io);
2343 self.http_client.deinit();
2344 }
2345
2346 fn packageDir(self: *TestFetchBuilder) !Io.Dir {
2347 const io = self.job_queue.io;
2348 const root = self.fetch.package_root;
2349 return try root.root_dir.handle.openDir(io, root.sub_path, .{ .iterate = true });
2350 }
2351
2352 // Test helper, asserts thet package dir constains expected_files.
2353 // expected_files must be sorted.
2354 fn expectPackageFiles(self: *TestFetchBuilder, expected_files: []const []const u8) !void {
2355 const io = self.job_queue.io;
2356 const gpa = std.testing.allocator;
2357
2358 var package_dir = try self.packageDir();
2359 defer package_dir.close(io);
2360
2361 var actual_files: std.ArrayList([]u8) = .empty;
2362 defer actual_files.deinit(gpa);
2363 defer for (actual_files.items) |file| gpa.free(file);
2364 var walker = try package_dir.walk(gpa);
2365 defer walker.deinit();
2366 while (try walker.next(io)) |entry| {
2367 if (entry.kind != .file) continue;
2368 const path = try gpa.dupe(u8, entry.path);
2369 errdefer gpa.free(path);
2370 std.mem.replaceScalar(u8, path, std.fs.path.sep, '/');
2371 try actual_files.append(gpa, path);
2372 }
2373 std.mem.sortUnstable([]u8, actual_files.items, {}, struct {
2374 fn lessThan(_: void, a: []u8, b: []u8) bool {
2375 return std.mem.lessThan(u8, a, b);
2376 }
2377 }.lessThan);
2378
2379 try std.testing.expectEqual(expected_files.len, actual_files.items.len);
2380 for (expected_files, 0..) |file_name, i| {
2381 try std.testing.expectEqualStrings(file_name, actual_files.items[i]);
2382 }
2383 try std.testing.expectEqualDeep(expected_files, actual_files.items);
2384 }
2385
2386 // Test helper, asserts that fetch has failed with `msg` error message.
2387 fn expectFetchErrors(self: *TestFetchBuilder, notes_len: usize, msg: []const u8) !void {
2388 const gpa = std.testing.allocator;
2389
2390 var errors = try self.fetch.error_bundle.toOwnedBundle("");
2391 defer errors.deinit(gpa);
2392
2393 const em = errors.getErrorMessage(errors.getMessages()[0]);
2394 try std.testing.expectEqual(1, em.count);
2395 if (notes_len > 0) {
2396 try std.testing.expectEqual(notes_len, em.notes_len);
2397 }
2398 var aw: Io.Writer.Allocating = .init(gpa);
2399 defer aw.deinit();
2400 try errors.renderToWriter(.{}, &aw.writer);
2401 try std.testing.expectEqualStrings(msg, aw.written());
2402 }
2403};
2404
24052232test {
24062233 _ = Filter;
24072234 _ = FileType;
src/Package/Fetch/testdata/executables.tar.gz deleted
Binary files a/src/Package/Fetch/testdata/executables.tar.gz and /dev/null differ
src/Package/Manifest.zig+47-5
......@@ -1,10 +1,13 @@
11const Manifest = @This();
2
23const std = @import("std");
4const Io = std.Io;
35const mem = std.mem;
46const Allocator = std.mem.Allocator;
57const assert = std.debug.assert;
68const Ast = std.zig.Ast;
79const testing = std.testing;
10
811const Package = @import("../Package.zig");
912
1013pub const max_bytes = 10 * 1024 * 1024;
......@@ -53,7 +56,7 @@ pub const ParseOptions = struct {
5356
5457pub const Error = Allocator.Error;
5558
56pub fn parse(gpa: Allocator, ast: Ast, rng: std.Random, options: ParseOptions) Error!Manifest {
59pub fn parse(gpa: Allocator, ast: *const Ast, rng: std.Random, options: ParseOptions) Error!Manifest {
5760 const main_node_index = ast.nodeData(.root).node;
5861
5962 var arena_instance = std.heap.ArenaAllocator.init(gpa);
......@@ -61,7 +64,7 @@ pub fn parse(gpa: Allocator, ast: Ast, rng: std.Random, options: ParseOptions) E
6164
6265 var p: Parse = .{
6366 .gpa = gpa,
64 .ast = ast,
67 .ast = ast.*,
6568 .arena = arena_instance.allocator(),
6669 .errors = .{},
6770
......@@ -578,6 +581,45 @@ const Parse = struct {
578581 }
579582};
580583
584pub fn load(
585 io: Io,
586 arena: Allocator,
587 manifest_path: std.Build.Cache.Path,
588 ast: *std.zig.Ast,
589 error_bundle: *std.zig.ErrorBundle.Wip,
590 manifest: *Manifest,
591 allow_missing_paths_field: bool,
592) !void {
593 const manifest_bytes = try manifest_path.root_dir.handle.readFileAllocOptions(
594 io,
595 manifest_path.sub_path,
596 arena,
597 .limited(max_bytes),
598 .@"1",
599 0,
600 );
601
602 ast.* = try std.zig.Ast.parse(arena, manifest_bytes, .zon);
603
604 if (ast.errors.len > 0) {
605 const file_path = try manifest_path.joinString(arena, "");
606 try std.zig.putAstErrorsIntoBundle(arena, ast.*, file_path, error_bundle);
607 return error.ErrorsBundled;
608 }
609
610 const rng: std.Random.IoSource = .{ .io = io };
611
612 manifest.* = try parse(arena, ast, rng.interface(), .{
613 .allow_missing_paths_field = allow_missing_paths_field,
614 });
615
616 if (manifest.errors.len > 0) {
617 const src_path = try error_bundle.printString("{f}", .{manifest_path});
618 try manifest.copyErrorsIntoBundle(ast.*, src_path, error_bundle);
619 return error.ErrorsBundled;
620 }
621}
622
581623test "basic" {
582624 const gpa = testing.allocator;
583625
......@@ -603,7 +645,7 @@ test "basic" {
603645
604646 var rng = std.Random.DefaultPrng.init(0);
605647
606 var manifest = try Manifest.parse(gpa, ast, rng.random(), .{});
648 var manifest = try Manifest.parse(gpa, &ast, rng.random(), .{});
607649 defer manifest.deinit(gpa);
608650
609651 try testing.expect(manifest.errors.len == 0);
......@@ -649,7 +691,7 @@ test "minimum_zig_version" {
649691
650692 var rng = std.Random.DefaultPrng.init(0);
651693
652 var manifest = try Manifest.parse(gpa, ast, rng.random(), .{});
694 var manifest = try Manifest.parse(gpa, &ast, rng.random(), .{});
653695 defer manifest.deinit(gpa);
654696
655697 try testing.expect(manifest.errors.len == 0);
......@@ -684,7 +726,7 @@ test "minimum_zig_version - invalid version" {
684726
685727 var rng = std.Random.DefaultPrng.init(0);
686728
687 var manifest = try Manifest.parse(gpa, ast, rng.random(), .{});
729 var manifest = try Manifest.parse(gpa, &ast, rng.random(), .{});
688730 defer manifest.deinit(gpa);
689731
690732 try testing.expect(manifest.errors.len == 1);
src/main.zig+141-20
......@@ -4896,7 +4896,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
48964896 var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map);
48974897 var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(environ_map);
48984898 var override_build_runner: ?[]const u8 = EnvVar.ZIG_BUILD_RUNNER.get(environ_map);
4899 var child_argv = std.array_list.Managed([]const u8).init(arena);
4899 var child_argv: std.ArrayList([]const u8) = .empty;
4900 var forks: std.ArrayList(Fork) = .empty;
49004901 var reference_trace: ?u32 = null;
49014902 var debug_compile_errors = false;
49024903 var verbose_link = (native_os != .wasi or builtin.link_libc) and
......@@ -4917,24 +4918,24 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
49174918 var debug_libc_paths_file: ?[]const u8 = null;
49184919
49194920 const argv_index_exe = child_argv.items.len;
4920 _ = try child_argv.addOne();
4921 _ = try child_argv.addOne(arena);
49214922
49224923 const self_exe_path = try process.executablePathAlloc(io, arena);
4923 try child_argv.append(self_exe_path);
4924 try child_argv.append(arena, self_exe_path);
49244925
49254926 const argv_index_zig_lib_dir = child_argv.items.len;
4926 _ = try child_argv.addOne();
4927 _ = try child_argv.addOne(arena);
49274928
49284929 const argv_index_build_file = child_argv.items.len;
4929 _ = try child_argv.addOne();
4930 _ = try child_argv.addOne(arena);
49304931
49314932 const argv_index_cache_dir = child_argv.items.len;
4932 _ = try child_argv.addOne();
4933 _ = try child_argv.addOne(arena);
49334934
49344935 const argv_index_global_cache_dir = child_argv.items.len;
4935 _ = try child_argv.addOne();
4936 _ = try child_argv.addOne(arena);
49364937
4937 try child_argv.appendSlice(&.{
4938 try child_argv.appendSlice(arena, &.{
49384939 "--seed",
49394940 try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)}),
49404941 });
......@@ -4955,7 +4956,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
49554956 // read this file in the parent to obtain the results, in the case the child
49564957 // exits with code 3.
49574958 const results_tmp_file_nonce = std.fmt.hex(randInt(io, u64));
4958 try child_argv.append("-Z" ++ results_tmp_file_nonce);
4959 try child_argv.append(arena, "-Z" ++ results_tmp_file_nonce);
49594960
49604961 var color: Color = .auto;
49614962 var n_jobs: ?u32 = null;
......@@ -5000,11 +5001,24 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
50005001 fatal("expected [needed|all] after '--fetch=', found '{s}'", .{
50015002 sub_arg,
50025003 });
5004 } else if (mem.cutPrefix(u8, arg, "--fork=")) |sub_arg| {
5005 try forks.append(arena, .{
5006 .manifest_ast = undefined,
5007 .manifest = undefined,
5008 .error_bundle = undefined,
5009 .arena_allocator = undefined,
5010 .path = .{
5011 .root_dir = .cwd(),
5012 .sub_path = sub_arg,
5013 },
5014 .failed = false,
5015 });
5016 continue;
50035017 } else if (mem.eql(u8, arg, "--system")) {
50045018 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
50055019 i += 1;
50065020 system_pkg_dir_path = args[i];
5007 try child_argv.append("--system");
5021 try child_argv.append(arena, "--system");
50085022 continue;
50095023 } else if (mem.cutPrefix(u8, arg, "-freference-trace=")) |num| {
50105024 reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
......@@ -5014,7 +5028,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
50145028 reference_trace = null;
50155029 } else if (mem.eql(u8, arg, "--debug-log")) {
50165030 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
5017 try child_argv.appendSlice(args[i .. i + 2]);
5031 try child_argv.appendSlice(arena, args[i .. i + 2]);
50185032 i += 1;
50195033 if (!build_options.enable_logging) {
50205034 warn("Zig was compiled without logging enabled (-Dlog). --debug-log has no effect.", .{});
......@@ -5070,7 +5084,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
50705084 color = std.meta.stringToEnum(Color, args[i]) orelse {
50715085 fatal("expected [auto|on|off] after {s}, found '{s}'", .{ arg, args[i] });
50725086 };
5073 try child_argv.appendSlice(&.{ arg, args[i] });
5087 try child_argv.appendSlice(arena, &.{ arg, args[i] });
50745088 continue;
50755089 } else if (mem.cutPrefix(u8, arg, "-j")) |str| {
50765090 const num = std.fmt.parseUnsigned(u32, str, 10) catch |err| {
......@@ -5090,11 +5104,11 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
50905104 } else if (mem.eql(u8, arg, "--")) {
50915105 // The rest of the args are supposed to get passed onto
50925106 // build runner's `build.args`
5093 try child_argv.appendSlice(args[i..]);
5107 try child_argv.appendSlice(arena, args[i..]);
50945108 break;
50955109 }
50965110 }
5097 try child_argv.append(arg);
5111 try child_argv.append(arena, arg);
50985112 }
50995113 }
51005114
......@@ -5182,6 +5196,29 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
51825196 defer http_client.deinit();
51835197
51845198 var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{};
5199 var fork_set: Package.Fetch.JobQueue.ForkSet = .{};
5200
5201 {
5202 // Populate fork_set.
5203 var group: Io.Group = .init;
5204 defer group.cancel(io);
5205
5206 for (forks.items) |*fork|
5207 group.async(io, Fork.load, .{ io, gpa, fork, color });
5208
5209 try group.await(io);
5210
5211 for (forks.items) |*fork| {
5212 if (fork.failed) process.exit(1);
5213 try fork_set.put(arena, .{
5214 .path = fork.path,
5215 .manifest_ast = fork.manifest_ast,
5216 .manifest = fork.manifest,
5217 .uses = 0,
5218 }, {});
5219 }
5220 }
5221 defer Fork.deinitList(forks.items);
51855222
51865223 // This loop is re-evaluated when the build script exits with an indication that it
51875224 // could not continue due to missing lazy dependencies.
......@@ -5235,6 +5272,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
52355272 const fetch_prog_node = root_prog_node.start("Fetch Packages", 0);
52365273 defer fetch_prog_node.end();
52375274
5275 // Reset fork match counts.
5276 for (fork_set.keys()) |*fork| fork.uses = 0;
5277
52385278 var job_queue: Package.Fetch.JobQueue = .{
52395279 .io = io,
52405280 .http_client = &http_client,
......@@ -5245,6 +5285,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
52455285 .recursive = true,
52465286 .debug_hash = false,
52475287 .unlazy_set = unlazy_set,
5288 .fork_set = fork_set,
52485289 .mode = fetch_mode,
52495290 .prog_node = fetch_prog_node,
52505291 };
......@@ -5290,8 +5331,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
52905331
52915332 .package_root = undefined,
52925333 .error_bundle = undefined,
5293 .manifest = null,
5334 .manifest = undefined,
52945335 .manifest_ast = undefined,
5336 .have_manifest = false,
52955337 .computed_hash = undefined,
52965338 .has_build_zig = true,
52975339 .oom_flag = false,
......@@ -5309,6 +5351,26 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
53095351 job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" });
53105352 try job_queue.group.await(io);
53115353
5354 {
5355 // Ensure that forks were actually used. This is done
5356 // before printing manifest errors because using a fork can
5357 // prevent them.
5358 var any_unused = false;
5359 for (fork_set.keys()) |*fork| {
5360 if (fork.uses == 0) {
5361 std.log.err("fork {f} matched no {s} packages", .{
5362 fork.path, fork.manifest.name,
5363 });
5364 any_unused = true;
5365 } else {
5366 std.log.info("fork {f} matched {d} {s} packages", .{
5367 fork.path, fork.uses, fork.manifest.name,
5368 });
5369 }
5370 }
5371 if (any_unused) process.exit(1);
5372 }
5373
53125374 try job_queue.consolidateErrors();
53135375
53145376 if (fetch.error_bundle.root_list.items.len > 0) {
......@@ -5369,7 +5431,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
53695431 // dependencies' build.zig modules by name.
53705432 for (fetches) |f| {
53715433 const mod = f.module orelse continue;
5372 const man = f.manifest orelse continue;
5434 if (!f.have_manifest) continue;
5435 const man = &f.manifest;
53735436 const dep_names = man.dependencies.keys();
53745437 try mod.deps.ensureUnusedCapacity(arena, @intCast(dep_names.len));
53755438 for (dep_names, man.dependencies.values()) |name, dep| {
......@@ -5518,6 +5581,63 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
55185581 }
55195582}
55205583
5584const Fork = struct {
5585 path: Path,
5586 manifest_ast: std.zig.Ast,
5587 manifest: Package.Manifest,
5588 error_bundle: std.zig.ErrorBundle.Wip,
5589 failed: bool,
5590 arena_allocator: std.heap.ArenaAllocator,
5591
5592 fn load(io: Io, gpa: Allocator, fork: *Fork, color: Color) Io.Cancelable!void {
5593 loadFallible(io, gpa, fork, color) catch |err| switch (err) {
5594 error.Canceled => |e| return e,
5595 error.AlreadyReported => fork.failed = true,
5596 else => |e| {
5597 std.log.err("failed to load fork at {f}: {t}", .{ fork.path, e });
5598 fork.failed = true;
5599 },
5600 };
5601 }
5602
5603 fn loadFallible(io: Io, gpa: Allocator, fork: *Fork, color: Color) !void {
5604 fork.arena_allocator = .init(gpa);
5605 const arena = fork.arena_allocator.allocator();
5606
5607 var error_bundle: std.zig.ErrorBundle.Wip = undefined;
5608 try error_bundle.init(gpa);
5609 defer error_bundle.deinit();
5610
5611 const manifest_path = try fork.path.join(arena, Package.Manifest.basename);
5612
5613 Package.Manifest.load(
5614 io,
5615 arena,
5616 manifest_path,
5617 &fork.manifest_ast,
5618 &error_bundle,
5619 &fork.manifest,
5620 true,
5621 ) catch |err| switch (err) {
5622 error.Canceled => |e| return e,
5623 error.ErrorsBundled => {
5624 assert(error_bundle.root_list.items.len > 0);
5625 var errors = try error_bundle.toOwnedBundle("");
5626 errors.renderToStderr(io, .{}, color) catch {};
5627 return error.AlreadyReported;
5628 },
5629 else => |e| {
5630 std.log.err("failed to load package manifest {f}: {t}", .{ manifest_path, e });
5631 return error.AlreadyReported;
5632 },
5633 };
5634 }
5635
5636 fn deinitList(forks: []Fork) void {
5637 for (forks) |*fork| fork.arena_allocator.deinit();
5638 }
5639};
5640
55215641const JitCmdOptions = struct {
55225642 cmd_name: []const u8,
55235643 root_src_path: []const u8,
......@@ -7048,8 +7168,9 @@ fn cmdFetch(
70487168
70497169 .package_root = undefined,
70507170 .error_bundle = undefined,
7051 .manifest = null,
7171 .manifest = undefined,
70527172 .manifest_ast = undefined,
7173 .have_manifest = false,
70537174 .computed_hash = undefined,
70547175 .has_build_zig = false,
70557176 .oom_flag = false,
......@@ -7085,9 +7206,9 @@ fn cmdFetch(
70857206 },
70867207 .yes, .exact => |name| name: {
70877208 if (name) |n| break :name n;
7088 const fetched_manifest = fetch.manifest orelse
7209 if (!fetch.have_manifest)
70897210 fatal("unable to determine name; fetched package has no build.zig.zon file", .{});
7090 break :name fetched_manifest.name;
7211 break :name fetch.manifest.name;
70917212 },
70927213 };
70937214
......@@ -7410,7 +7531,7 @@ fn loadManifest(
74107531 process.exit(2);
74117532 }
74127533
7413 var manifest = try Package.Manifest.parse(gpa, ast, rng.interface(), .{});
7534 var manifest = try Package.Manifest.parse(gpa, &ast, rng.interface(), .{});
74147535 errdefer manifest.deinit(gpa);
74157536
74167537 if (manifest.errors.len > 0) {