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....@@ -71,6 +71,9 @@ Struct.
7171
72Each dependency must either provide a `url` and `hash`, or a `path`.72Each 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
74#### `url`77#### `url`
7578
76String. 79String.
lib/compiler/Maker.zig+10
...@@ -1364,6 +1364,11 @@ fn configure(graph: *Graph, options: ConfigureOptions) ConfigureError!ScannedCon...@@ -1364,6 +1364,11 @@ fn configure(graph: *Graph, options: ConfigureOptions) ConfigureError!ScannedCon
1364 .location_tok = 0,1364 .location_tok = 0,
1365 .hash_tok = .none,1365 .hash_tok = .none,
1366 .name_tok = 0,1366 .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,
1367 .lazy_status = .eager,1372 .lazy_status = .eager,
1368 .remote_package_root = phantom_package_root,1373 .remote_package_root = phantom_package_root,
1369 .parent_package_root = phantom_package_root,1374 .parent_package_root = phantom_package_root,
...@@ -2096,6 +2101,11 @@ fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {...@@ -2096,6 +2101,11 @@ fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
2096 .location_tok = 0,2101 .location_tok = 0,
2097 .hash_tok = .none,2102 .hash_tok = .none,
2098 .name_tok = 0,2103 .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,
2099 .lazy_status = .eager,2109 .lazy_status = .eager,
2100 .remote_package_root = undefined,2110 .remote_package_root = undefined,
2101 .parent_package_root = if (build_root_initialized) .{ .root_dir = build_root.directory } else .cwd(),2111 .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,...@@ -56,6 +56,11 @@ location: Location,
56location_tok: std.zig.Ast.TokenIndex,56location_tok: std.zig.Ast.TokenIndex,
57hash_tok: std.zig.Ast.OptionalTokenIndex,57hash_tok: std.zig.Ast.OptionalTokenIndex,
58name_tok: std.zig.Ast.TokenIndex,58name_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,
59lazy_status: LazyStatus,64lazy_status: LazyStatus,
60/// Same as `parent_packge_root` except it is unchanged when recursing into65/// Same as `parent_packge_root` except it is unchanged when recursing into
61/// relative file paths (as opposed to URL).66/// relative file paths (as opposed to URL).
...@@ -632,7 +637,7 @@ pub fn run(f: *Fetch) RunError!void {...@@ -632,7 +637,7 @@ pub fn run(f: *Fetch) RunError!void {
632 log.debug("FileNotFound: {f}", .{package_root});637 log.debug("FileNotFound: {f}", .{package_root});
633 if (job_queue.read_only and f.lazy_status == .eager) return f.fail(638 if (job_queue.read_only and f.lazy_status == .eager) return f.fail(
634 f.name_tok,639 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}),
636 );641 );
637 },642 },
638 error.Canceled => |e| return e,643 error.Canceled => |e| return e,
...@@ -762,7 +767,7 @@ fn runResource(...@@ -762,7 +767,7 @@ fn runResource(
762 try loadManifest(f, pkg_path);767 try loadManifest(f, pkg_path);
763768
764 const filter: Filter = .{769 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,
766 };771 };
767772
768 // Ignore errors that were excluded by manifest, such as failure to773 // Ignore errors that were excluded by manifest, such as failure to
...@@ -856,9 +861,15 @@ pub fn computedPackageHash(f: *const Fetch) Package.Hash {...@@ -856,9 +861,15 @@ pub fn computedPackageHash(f: *const Fetch) Package.Hash {
856 const version: []const u8 = std.mem.print(&version_buffer, "{f}", .{man.version}) catch &version_buffer;861 const version: []const u8 = std.mem.print(&version_buffer, "{f}", .{man.version}) catch &version_buffer;
857 return .init(f.computed_hash.digest, man.name, version, man.id, saturated_size);862 return .init(f.computed_hash.digest, man.name, version, man.id, saturated_size);
858 }863 }
859 // In the future build.zig.zon fields will be added to allow overriding these values864 // Previous version of Zig did not use f.name to affect the package hash, so
860 // for naked tarballs.865 // allow the user to opt in by only doing that if they specify a version.
861 return .init(f.computed_hash.digest, "N", "V", 0xffff, saturated_size);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 }
862}873}
863874
864/// `computeHash` gets a free check for the existence of `build.zig`, but when875/// `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 {...@@ -902,12 +913,24 @@ fn loadManifest(f: *Fetch, pkg_root: Path) RunError!void {
902 error.ErrorsBundled => return error.FetchFailed,913 error.ErrorsBundled => return error.FetchFailed,
903 else => |e| {914 else => |e| {
904 try eb.addRootErrorMessage(.{915 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 }),
906 });917 });
907 return error.FetchFailed;918 return error.FetchFailed;
908 },919 },
909 };920 };
910 f.have_manifest = true;921 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 }
911}934}
912935
913fn queueJobsForDeps(f: *Fetch) RunError!void {936fn queueJobsForDeps(f: *Fetch) RunError!void {
...@@ -1008,6 +1031,11 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {...@@ -1008,6 +1031,11 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {
1008 .location_tok = dep.location_tok,1031 .location_tok = dep.location_tok,
1009 .hash_tok = dep.hash_tok,1032 .hash_tok = dep.hash_tok,
1010 .name_tok = dep.name_tok,1033 .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,
1011 .lazy_status = switch (f.job_queue.mode) {1039 .lazy_status = switch (f.job_queue.mode) {
1012 .needed => if (dep.lazy) .available else .eager,1040 .needed => if (dep.lazy) .available else .eager,
1013 .all => .eager,1041 .all => .eager,
...@@ -1753,7 +1781,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash...@@ -1753,7 +1781,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
17531781
1754 while (walker.next(io) catch |err| {1782 while (walker.next(io) catch |err| {
1755 try eb.addRootErrorMessage(.{ .msg = try eb.printString(1783 try eb.addRootErrorMessage(.{ .msg = try eb.printString(
1756 "unable to walk temporary directory '{f}': {t}",1784 "unable to walk temporary directory {qf}: {t}",
1757 .{ pkg_path, err },1785 .{ pkg_path, err },
1758 ) });1786 ) });
1759 return error.FetchFailed;1787 return error.FetchFailed;
...@@ -1784,7 +1812,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash...@@ -1784,7 +1812,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
1784 .file => .file,1812 .file => .file,
1785 .sym_link => .link,1813 .sym_link => .link,
1786 else => return f.fail(f.location_tok, try eb.printString(1814 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}",
1788 .{ entry.path, entry.kind },1816 .{ entry.path, entry.kind },
1789 )),1817 )),
1790 };1818 };
...@@ -1826,10 +1854,9 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash...@@ -1826,10 +1854,9 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
1826 error.DirNotEmpty => continue,1854 error.DirNotEmpty => continue,
1827 error.FileNotFound => continue,1855 error.FileNotFound => continue,
1828 else => |e| {1856 else => |e| {
1829 try eb.addRootErrorMessage(.{ .msg = try eb.printString(1857 try eb.addRootErrorMessage(.{
1830 "unable to delete empty directory '{s}': {s}",1858 .msg = try eb.printString("unable to delete empty directory {q}: {t}", .{ sus_dir, e }),
1831 .{ sus_dir, @errorName(e) },1859 });
1832 ) });
1833 return error.FetchFailed;1860 return error.FetchFailed;
1834 },1861 },
1835 };1862 };
...@@ -1847,9 +1874,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash...@@ -1847,9 +1874,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
1847 hashed_file.failure catch |err| {1874 hashed_file.failure catch |err| {
1848 any_failures = true;1875 any_failures = true;
1849 try eb.addRootErrorMessage(.{1876 try eb.addRootErrorMessage(.{
1850 .msg = try eb.printString("unable to hash '{s}': {s}", .{1877 .msg = try eb.printString("unable to hash {q}: {t}", .{ hashed_file.fs_path, err }),
1851 hashed_file.fs_path, @errorName(err),
1852 }),
1853 });1878 });
1854 };1879 };
1855 hasher.update(&hashed_file.hash);1880 hasher.update(&hashed_file.hash);
...@@ -1859,8 +1884,8 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash...@@ -1859,8 +1884,8 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
1859 deleted_file.failure catch |err| {1884 deleted_file.failure catch |err| {
1860 any_failures = true;1885 any_failures = true;
1861 try eb.addRootErrorMessage(.{1886 try eb.addRootErrorMessage(.{
1862 .msg = try eb.printString("failed to delete excluded path '{s}' from package: {s}", .{1887 .msg = try eb.printString("failed to delete excluded path {q} from package: {t}", .{
1863 deleted_file.fs_path, @errorName(err),1888 deleted_file.fs_path, err,
1864 }),1889 }),
1865 });1890 });
1866 };1891 };
...@@ -2216,21 +2241,21 @@ const UnpackResult = struct {...@@ -2216,21 +2241,21 @@ const UnpackResult = struct {
2216 switch (item) {2241 switch (item) {
2217 .unable_to_create_sym_link => |info| {2242 .unable_to_create_sym_link => |info| {
2218 eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{2243 eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{
2219 .msg = try eb.printString("unable to create symlink from '{s}' to '{s}': {s}", .{2244 .msg = try eb.printString("unable to create symlink from {q} to {q}: {t}", .{
2220 info.file_name, info.link_name, @errorName(info.code),2245 info.file_name, info.link_name, info.code,
2221 }),2246 }),
2222 }));2247 }));
2223 },2248 },
2224 .unable_to_create_file => |info| {2249 .unable_to_create_file => |info| {
2225 eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{2250 eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{
2226 .msg = try eb.printString("unable to create file '{s}': {s}", .{2251 .msg = try eb.printString("unable to create file {q}: {t}", .{
2227 info.file_name, @errorName(info.code),2252 info.file_name, info.code,
2228 }),2253 }),
2229 }));2254 }));
2230 },2255 },
2231 .unsupported_file_type => |info| {2256 .unsupported_file_type => |info| {
2232 eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{2257 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}'", .{
2234 info.file_name, info.file_type,2259 info.file_name, info.file_type,
2235 }),2260 }),
2236 }));2261 }));
lib/compiler/Maker/Package/Manifest.zig+47-11
...@@ -25,6 +25,10 @@ pub const Dependency = struct {...@@ -25,6 +25,10 @@ pub const Dependency = struct {
25 node: Ast.Node.Index,25 node: Ast.Node.Index,
26 name_tok: Ast.TokenIndex,26 name_tok: Ast.TokenIndex,
27 lazy: bool,27 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
29 pub const Location = union(enum) {33 pub const Location = union(enum) {
30 url: []const u8,34 url: []const u8,
...@@ -177,7 +181,7 @@ const Parse = struct {...@@ -177,7 +181,7 @@ const Parse = struct {
177 try parseDependencies(p, field_init);181 try parseDependencies(p, field_init);
178 } else if (mem.eql(u8, field_name, "paths")) {182 } else if (mem.eql(u8, field_name, "paths")) {
179 have_included_paths = true;183 have_included_paths = true;
180 try parseIncludedPaths(p, field_init);184 try parseIncludedPaths(p, &p.paths, field_init);
181 } else if (mem.eql(u8, field_name, "name")) {185 } else if (mem.eql(u8, field_name, "name")) {
182 p.name = try parseName(p, field_init);186 p.name = try parseName(p, field_init);
183 have_name = true;187 have_name = true;
...@@ -185,15 +189,13 @@ const Parse = struct {...@@ -185,15 +189,13 @@ const Parse = struct {
185 fingerprint = try parseFingerprint(p, field_init);189 fingerprint = try parseFingerprint(p, field_init);
186 } else if (mem.eql(u8, field_name, "version")) {190 } else if (mem.eql(u8, field_name, "version")) {
187 p.version_node = field_init;191 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 };
196 have_version = true;192 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 }
197 } else if (mem.eql(u8, field_name, "minimum_zig_version")) {199 } else if (mem.eql(u8, field_name, "minimum_zig_version")) {
198 const version_text = try parseString(p, field_init);200 const version_text = try parseString(p, field_init);
199 p.minimum_zig_version = std.SemanticVersion.parse(version_text) catch |err| v: {201 p.minimum_zig_version = std.SemanticVersion.parse(version_text) catch |err| v: {
...@@ -236,6 +238,21 @@ const Parse = struct {...@@ -236,6 +238,21 @@ const Parse = struct {
236 }238 }
237 }239 }
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
239 fn parseDependencies(p: *Parse, node: Ast.Node.Index) !void {256 fn parseDependencies(p: *Parse, node: Ast.Node.Index) !void {
240 const ast = p.ast;257 const ast = p.ast;
241258
...@@ -272,6 +289,10 @@ const Parse = struct {...@@ -272,6 +289,10 @@ const Parse = struct {
272 .node = node,289 .node = node,
273 .name_tok = undefined,290 .name_tok = undefined,
274 .lazy = false,291 .lazy = false,
292 .paths = .empty,
293 .paths_tok = .none,
294 .version = undefined,
295 .version_tok = .none,
275 };296 };
276 var has_location = false;297 var has_location = false;
277298
...@@ -320,6 +341,17 @@ const Parse = struct {...@@ -320,6 +341,17 @@ const Parse = struct {
320 error.ParseFailure => continue,341 error.ParseFailure => continue,
321 else => |e| return e,342 else => |e| return e,
322 };343 };
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 }
323 } else {355 } else {
324 // Ignore unknown fields so that we can add fields in future zig356 // Ignore unknown fields so that we can add fields in future zig
325 // versions without breaking older zig versions.357 // versions without breaking older zig versions.
...@@ -333,7 +365,7 @@ const Parse = struct {...@@ -333,7 +365,7 @@ const Parse = struct {
333 return dep;365 return dep;
334 }366 }
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 {
337 const ast = p.ast;369 const ast = p.ast;
338370
339 var buf: [2]Ast.Node.Index = undefined;371 var buf: [2]Ast.Node.Index = undefined;
...@@ -347,7 +379,11 @@ const Parse = struct {...@@ -347,7 +379,11 @@ const Parse = struct {
347 // This is normalized so that it can be used in string comparisons379 // This is normalized so that it can be used in string comparisons
348 // against file system paths.380 // against file system paths.
349 const normalized = try std.fs.path.resolve(p.arena, &.{path_string});381 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 }
351 }387 }
352 }388 }
353389