authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-20 19:10:07+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-20 19:10:07+02:00
logc8bb44618a9c3c7bfff4a9f672a451fdeda1bb02
tree9b9c5035dff55ac3833f14ce69ce605977c798ba
parent67f773beaeb299024676d21d7f15342a100690d5
parentffa20b6deb0aa9f573be3c85538b86d0d4f0cdf4

Merge pull request 'naked package metadata' (#37340) from naked-package-labeling into master

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

4 files changed, 107 insertions(+), 33 deletions(-)

doc/build.zig.zon.md+3
......@@ -71,6 +71,9 @@ Struct.
7171
7272Each dependency must either provide a `url` and `hash`, or a `path`.
7373
74May also include `paths` and `version` which provides those values in
75the case that the package lacks a `build.zig.zon` file.
76
7477#### `url`
7578
7679String.
lib/compiler/Maker.zig+10
......@@ -1364,6 +1364,11 @@ fn configure(graph: *Graph, options: ConfigureOptions) ConfigureError!ScannedCon
13641364 .location_tok = 0,
13651365 .hash_tok = .none,
13661366 .name_tok = 0,
1367 .name = "",
1368 .parent_specified_paths = .empty,
1369 .parent_specified_paths_tok = .none,
1370 .parent_specified_version = undefined,
1371 .parent_specified_version_tok = .none,
13671372 .lazy_status = .eager,
13681373 .remote_package_root = phantom_package_root,
13691374 .parent_package_root = phantom_package_root,
......@@ -2096,6 +2101,11 @@ fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
20962101 .location_tok = 0,
20972102 .hash_tok = .none,
20982103 .name_tok = 0,
2104 .name = "",
2105 .parent_specified_paths = .empty,
2106 .parent_specified_paths_tok = .none,
2107 .parent_specified_version = undefined,
2108 .parent_specified_version_tok = .none,
20992109 .lazy_status = .eager,
21002110 .remote_package_root = undefined,
21012111 .parent_package_root = if (build_root_initialized) .{ .root_dir = build_root.directory } else .cwd(),
lib/compiler/Maker/Fetch.zig+47-22
......@@ -56,6 +56,11 @@ location: Location,
5656location_tok: std.zig.Ast.TokenIndex,
5757hash_tok: std.zig.Ast.OptionalTokenIndex,
5858name_tok: std.zig.Ast.TokenIndex,
59name: []const u8,
60parent_specified_paths: std.array_hash_map.String(void),
61parent_specified_paths_tok: std.zig.Ast.OptionalTokenIndex,
62parent_specified_version: std.SemanticVersion,
63parent_specified_version_tok: std.zig.Ast.OptionalTokenIndex,
5964lazy_status: LazyStatus,
6065/// Same as `parent_packge_root` except it is unchanged when recursing into
6166/// relative file paths (as opposed to URL).
......@@ -632,7 +637,7 @@ pub fn run(f: *Fetch) RunError!void {
632637 log.debug("FileNotFound: {f}", .{package_root});
633638 if (job_queue.read_only and f.lazy_status == .eager) return f.fail(
634639 f.name_tok,
635 try eb.printString("package not found at '{f}'", .{package_root}),
640 try eb.printString("package not found at {qf}", .{package_root}),
636641 );
637642 },
638643 error.Canceled => |e| return e,
......@@ -762,7 +767,7 @@ fn runResource(
762767 try loadManifest(f, pkg_path);
763768
764769 const filter: Filter = .{
765 .include_paths = if (f.have_manifest) f.manifest.paths else .{},
770 .include_paths = if (f.have_manifest) f.manifest.paths else f.parent_specified_paths,
766771 };
767772
768773 // Ignore errors that were excluded by manifest, such as failure to
......@@ -856,9 +861,15 @@ pub fn computedPackageHash(f: *const Fetch) Package.Hash {
856861 const version: []const u8 = std.mem.print(&version_buffer, "{f}", .{man.version}) catch &version_buffer;
857862 return .init(f.computed_hash.digest, man.name, version, man.id, saturated_size);
858863 }
859 // In the future build.zig.zon fields will be added to allow overriding these values
860 // for naked tarballs.
861 return .init(f.computed_hash.digest, "N", "V", 0xffff, saturated_size);
864 // Previous version of Zig did not use f.name to affect the package hash, so
865 // allow the user to opt in by only doing that if they specify a version.
866 var version_buffer: [Manifest.max_version_len]u8 = undefined;
867 if (f.parent_specified_version_tok != .none) {
868 const v = std.mem.print(&version_buffer, "{f}", .{f.parent_specified_version}) catch unreachable;
869 return .init(f.computed_hash.digest, f.name, v, 0xffff, saturated_size);
870 } else {
871 return .init(f.computed_hash.digest, "N", "V", 0xffff, saturated_size);
872 }
862873}
863874
864875/// `computeHash` gets a free check for the existence of `build.zig`, but when
......@@ -902,12 +913,24 @@ fn loadManifest(f: *Fetch, pkg_root: Path) RunError!void {
902913 error.ErrorsBundled => return error.FetchFailed,
903914 else => |e| {
904915 try eb.addRootErrorMessage(.{
905 .msg = try eb.printString("unable to load package manifest '{f}': {t}", .{ manifest_path, e }),
916 .msg = try eb.printString("unable to load package manifest {qf}: {t}", .{ manifest_path, e }),
906917 });
907918 return error.FetchFailed;
908919 },
909920 };
910921 f.have_manifest = true;
922
923 if (f.parent_specified_paths_tok.unwrap()) |tok| {
924 return f.fail(tok, try eb.printString("paths provided for package containing {s}", .{
925 Manifest.basename,
926 }));
927 }
928
929 if (f.parent_specified_version_tok.unwrap()) |tok| {
930 return f.fail(tok, try eb.printString("version provided for package containing {s}", .{
931 Manifest.basename,
932 }));
933 }
911934}
912935
913936fn queueJobsForDeps(f: *Fetch) RunError!void {
......@@ -1008,6 +1031,11 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {
10081031 .location_tok = dep.location_tok,
10091032 .hash_tok = dep.hash_tok,
10101033 .name_tok = dep.name_tok,
1034 .name = dep_name,
1035 .parent_specified_paths = dep.paths,
1036 .parent_specified_paths_tok = dep.paths_tok,
1037 .parent_specified_version = dep.version,
1038 .parent_specified_version_tok = dep.version_tok,
10111039 .lazy_status = switch (f.job_queue.mode) {
10121040 .needed => if (dep.lazy) .available else .eager,
10131041 .all => .eager,
......@@ -1753,7 +1781,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
17531781
17541782 while (walker.next(io) catch |err| {
17551783 try eb.addRootErrorMessage(.{ .msg = try eb.printString(
1756 "unable to walk temporary directory '{f}': {t}",
1784 "unable to walk temporary directory {qf}: {t}",
17571785 .{ pkg_path, err },
17581786 ) });
17591787 return error.FetchFailed;
......@@ -1784,7 +1812,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
17841812 .file => .file,
17851813 .sym_link => .link,
17861814 else => return f.fail(f.location_tok, try eb.printString(
1787 "package contains '{s}' which has illegal file type '{t}'",
1815 "package contains {q} which has illegal file type {t}",
17881816 .{ entry.path, entry.kind },
17891817 )),
17901818 };
......@@ -1826,10 +1854,9 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
18261854 error.DirNotEmpty => continue,
18271855 error.FileNotFound => continue,
18281856 else => |e| {
1829 try eb.addRootErrorMessage(.{ .msg = try eb.printString(
1830 "unable to delete empty directory '{s}': {s}",
1831 .{ sus_dir, @errorName(e) },
1832 ) });
1857 try eb.addRootErrorMessage(.{
1858 .msg = try eb.printString("unable to delete empty directory {q}: {t}", .{ sus_dir, e }),
1859 });
18331860 return error.FetchFailed;
18341861 },
18351862 };
......@@ -1847,9 +1874,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
18471874 hashed_file.failure catch |err| {
18481875 any_failures = true;
18491876 try eb.addRootErrorMessage(.{
1850 .msg = try eb.printString("unable to hash '{s}': {s}", .{
1851 hashed_file.fs_path, @errorName(err),
1852 }),
1877 .msg = try eb.printString("unable to hash {q}: {t}", .{ hashed_file.fs_path, err }),
18531878 });
18541879 };
18551880 hasher.update(&hashed_file.hash);
......@@ -1859,8 +1884,8 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
18591884 deleted_file.failure catch |err| {
18601885 any_failures = true;
18611886 try eb.addRootErrorMessage(.{
1862 .msg = try eb.printString("failed to delete excluded path '{s}' from package: {s}", .{
1863 deleted_file.fs_path, @errorName(err),
1887 .msg = try eb.printString("failed to delete excluded path {q} from package: {t}", .{
1888 deleted_file.fs_path, err,
18641889 }),
18651890 });
18661891 };
......@@ -2216,21 +2241,21 @@ const UnpackResult = struct {
22162241 switch (item) {
22172242 .unable_to_create_sym_link => |info| {
22182243 eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{
2219 .msg = try eb.printString("unable to create symlink from '{s}' to '{s}': {s}", .{
2220 info.file_name, info.link_name, @errorName(info.code),
2244 .msg = try eb.printString("unable to create symlink from {q} to {q}: {t}", .{
2245 info.file_name, info.link_name, info.code,
22212246 }),
22222247 }));
22232248 },
22242249 .unable_to_create_file => |info| {
22252250 eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{
2226 .msg = try eb.printString("unable to create file '{s}': {s}", .{
2227 info.file_name, @errorName(info.code),
2251 .msg = try eb.printString("unable to create file {q}: {t}", .{
2252 info.file_name, info.code,
22282253 }),
22292254 }));
22302255 },
22312256 .unsupported_file_type => |info| {
22322257 eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{
2233 .msg = try eb.printString("file '{s}' has unsupported type '{c}'", .{
2258 .msg = try eb.printString("file {q} has unsupported type '{c}'", .{
22342259 info.file_name, info.file_type,
22352260 }),
22362261 }));
lib/compiler/Maker/Package/Manifest.zig+47-11
......@@ -25,6 +25,10 @@ pub const Dependency = struct {
2525 node: Ast.Node.Index,
2626 name_tok: Ast.TokenIndex,
2727 lazy: bool,
28 paths: std.array_hash_map.String(void),
29 paths_tok: Ast.OptionalTokenIndex,
30 version: std.SemanticVersion,
31 version_tok: Ast.OptionalTokenIndex,
2832
2933 pub const Location = union(enum) {
3034 url: []const u8,
......@@ -177,7 +181,7 @@ const Parse = struct {
177181 try parseDependencies(p, field_init);
178182 } else if (mem.eql(u8, field_name, "paths")) {
179183 have_included_paths = true;
180 try parseIncludedPaths(p, field_init);
184 try parseIncludedPaths(p, &p.paths, field_init);
181185 } else if (mem.eql(u8, field_name, "name")) {
182186 p.name = try parseName(p, field_init);
183187 have_name = true;
......@@ -185,15 +189,13 @@ const Parse = struct {
185189 fingerprint = try parseFingerprint(p, field_init);
186190 } else if (mem.eql(u8, field_name, "version")) {
187191 p.version_node = field_init;
188 const version_text = try parseString(p, field_init);
189 if (version_text.len > max_version_len) {
190 try appendError(p, ast.nodeMainToken(field_init), "version string length {d} exceeds maximum of {d}", .{ version_text.len, max_version_len });
191 }
192 p.version = std.SemanticVersion.parse(version_text) catch |err| v: {
193 try appendError(p, ast.nodeMainToken(field_init), "unable to parse semantic version: {s}", .{@errorName(err)});
194 break :v undefined;
195 };
196192 have_version = true;
193 if (parseVersion(p, field_init)) |v| {
194 p.version = v;
195 } else |err| switch (err) {
196 else => |e| return e,
197 error.ParseFailure => {},
198 }
197199 } else if (mem.eql(u8, field_name, "minimum_zig_version")) {
198200 const version_text = try parseString(p, field_init);
199201 p.minimum_zig_version = std.SemanticVersion.parse(version_text) catch |err| v: {
......@@ -236,6 +238,21 @@ const Parse = struct {
236238 }
237239 }
238240
241 fn parseVersion(p: *Parse, node: Ast.Node.Index) InnerError!std.SemanticVersion {
242 const ast = p.ast;
243 const version_text = try parseString(p, node);
244 if (version_text.len > max_version_len) {
245 try appendError(p, ast.nodeMainToken(node), "version string length {d} exceeds maximum of {d}", .{
246 version_text.len, max_version_len,
247 });
248 }
249 return std.SemanticVersion.parse(version_text) catch |err| {
250 return fail(p, ast.nodeMainToken(node), "parsing semantic version {q} failed: {t}", .{
251 version_text, err,
252 });
253 };
254 }
255
239256 fn parseDependencies(p: *Parse, node: Ast.Node.Index) !void {
240257 const ast = p.ast;
241258
......@@ -272,6 +289,10 @@ const Parse = struct {
272289 .node = node,
273290 .name_tok = undefined,
274291 .lazy = false,
292 .paths = .empty,
293 .paths_tok = .none,
294 .version = undefined,
295 .version_tok = .none,
275296 };
276297 var has_location = false;
277298
......@@ -320,6 +341,17 @@ const Parse = struct {
320341 error.ParseFailure => continue,
321342 else => |e| return e,
322343 };
344 } else if (mem.eql(u8, field_name, "paths")) {
345 dep.paths_tok = .fromToken(name_token);
346 try parseIncludedPaths(p, &dep.paths, field_init);
347 } else if (mem.eql(u8, field_name, "version")) {
348 if (parseVersion(p, field_init)) |v| {
349 dep.version = v;
350 dep.version_tok = .fromToken(name_token);
351 } else |err| switch (err) {
352 else => |e| return e,
353 error.ParseFailure => {},
354 }
323355 } else {
324356 // Ignore unknown fields so that we can add fields in future zig
325357 // versions without breaking older zig versions.
......@@ -333,7 +365,7 @@ const Parse = struct {
333365 return dep;
334366 }
335367
336 fn parseIncludedPaths(p: *Parse, node: Ast.Node.Index) !void {
368 fn parseIncludedPaths(p: *Parse, paths: *std.array_hash_map.String(void), node: Ast.Node.Index) !void {
337369 const ast = p.ast;
338370
339371 var buf: [2]Ast.Node.Index = undefined;
......@@ -347,7 +379,11 @@ const Parse = struct {
347379 // This is normalized so that it can be used in string comparisons
348380 // against file system paths.
349381 const normalized = try std.fs.path.resolve(p.arena, &.{path_string});
350 try p.paths.put(p.gpa, normalized, {});
382 if (try paths.fetchPut(p.gpa, normalized, {})) |kv| {
383 return fail(p, ast.nodeMainToken(elem_node), "path {q} already included as {q}", .{
384 normalized, kv.key,
385 });
386 }
351387 }
352388 }
353389