authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-19 17:34:42-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-19 17:34:42-07:00
log4fc40330a4f59f57cfb93e09e56291def147d20d
tree26e796120098699b8c3ea12b9eb26c101be9dd17
parent64caa0e324d522915769dd5cc8a11374f919f01f

ability to label naked packages

dependencies inside build.zig.zon now support `paths` and `version` fields which can be used to affect the filtered files of the package as well as the hash.

4 files changed, 104 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
...@@ -1342,6 +1342,11 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {...@@ -1342,6 +1342,11 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
1342 .location_tok = 0,1342 .location_tok = 0,
1343 .hash_tok = .none,1343 .hash_tok = .none,
1344 .name_tok = 0,1344 .name_tok = 0,
1345 .name = "",
1346 .parent_specified_paths = .empty,
1347 .parent_specified_paths_tok = .none,
1348 .parent_specified_version = undefined,
1349 .parent_specified_version_tok = .none,
1345 .lazy_status = .eager,1350 .lazy_status = .eager,
1346 .remote_package_root = phantom_package_root,1351 .remote_package_root = phantom_package_root,
1347 .parent_package_root = phantom_package_root,1352 .parent_package_root = phantom_package_root,
...@@ -2054,6 +2059,11 @@ fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {...@@ -2054,6 +2059,11 @@ fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
2054 .location_tok = 0,2059 .location_tok = 0,
2055 .hash_tok = .none,2060 .hash_tok = .none,
2056 .name_tok = 0,2061 .name_tok = 0,
2062 .name = "",
2063 .parent_specified_paths = .empty,
2064 .parent_specified_paths_tok = .none,
2065 .parent_specified_version = undefined,
2066 .parent_specified_version_tok = .none,
2057 .lazy_status = .eager,2067 .lazy_status = .eager,
2058 .remote_package_root = undefined,2068 .remote_package_root = undefined,
2059 .parent_package_root = if (build_root_initialized) .{ .root_dir = build_root.directory } else .cwd(),2069 .parent_package_root = if (build_root_initialized) .{ .root_dir = build_root.directory } else .cwd(),
lib/compiler/Maker/Fetch.zig+44-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,12 @@ pub fn computedPackageHash(f: *const Fetch) Package.Hash {...@@ -856,9 +861,12 @@ 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 var version_buffer: [Manifest.max_version_len]u8 = undefined;
860 // for naked tarballs.865 const v: []const u8 = if (f.parent_specified_version_tok != .none)
861 return .init(f.computed_hash.digest, "N", "V", 0xffff, saturated_size);866 std.mem.print(&version_buffer, "{f}", .{f.parent_specified_version}) catch unreachable
867 else
868 "V";
869 return .init(f.computed_hash.digest, f.name, v, 0xffff, saturated_size);
862}870}
863871
864/// `computeHash` gets a free check for the existence of `build.zig`, but when872/// `computeHash` gets a free check for the existence of `build.zig`, but when
...@@ -902,12 +910,24 @@ fn loadManifest(f: *Fetch, pkg_root: Path) RunError!void {...@@ -902,12 +910,24 @@ fn loadManifest(f: *Fetch, pkg_root: Path) RunError!void {
902 error.ErrorsBundled => return error.FetchFailed,910 error.ErrorsBundled => return error.FetchFailed,
903 else => |e| {911 else => |e| {
904 try eb.addRootErrorMessage(.{912 try eb.addRootErrorMessage(.{
905 .msg = try eb.printString("unable to load package manifest '{f}': {t}", .{ manifest_path, e }),913 .msg = try eb.printString("unable to load package manifest {qf}: {t}", .{ manifest_path, e }),
906 });914 });
907 return error.FetchFailed;915 return error.FetchFailed;
908 },916 },
909 };917 };
910 f.have_manifest = true;918 f.have_manifest = true;
919
920 if (f.parent_specified_paths_tok.unwrap()) |tok| {
921 return f.fail(tok, try eb.printString("paths provided for package containing {s}", .{
922 Manifest.basename,
923 }));
924 }
925
926 if (f.parent_specified_version_tok.unwrap()) |tok| {
927 return f.fail(tok, try eb.printString("version provided for package containing {s}", .{
928 Manifest.basename,
929 }));
930 }
911}931}
912932
913fn queueJobsForDeps(f: *Fetch) RunError!void {933fn queueJobsForDeps(f: *Fetch) RunError!void {
...@@ -1008,6 +1028,11 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {...@@ -1008,6 +1028,11 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {
1008 .location_tok = dep.location_tok,1028 .location_tok = dep.location_tok,
1009 .hash_tok = dep.hash_tok,1029 .hash_tok = dep.hash_tok,
1010 .name_tok = dep.name_tok,1030 .name_tok = dep.name_tok,
1031 .name = dep_name,
1032 .parent_specified_paths = dep.paths,
1033 .parent_specified_paths_tok = dep.paths_tok,
1034 .parent_specified_version = dep.version,
1035 .parent_specified_version_tok = dep.version_tok,
1011 .lazy_status = switch (f.job_queue.mode) {1036 .lazy_status = switch (f.job_queue.mode) {
1012 .needed => if (dep.lazy) .available else .eager,1037 .needed => if (dep.lazy) .available else .eager,
1013 .all => .eager,1038 .all => .eager,
...@@ -1753,7 +1778,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash...@@ -1753,7 +1778,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
17531778
1754 while (walker.next(io) catch |err| {1779 while (walker.next(io) catch |err| {
1755 try eb.addRootErrorMessage(.{ .msg = try eb.printString(1780 try eb.addRootErrorMessage(.{ .msg = try eb.printString(
1756 "unable to walk temporary directory '{f}': {t}",1781 "unable to walk temporary directory {qf}: {t}",
1757 .{ pkg_path, err },1782 .{ pkg_path, err },
1758 ) });1783 ) });
1759 return error.FetchFailed;1784 return error.FetchFailed;
...@@ -1784,7 +1809,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash...@@ -1784,7 +1809,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
1784 .file => .file,1809 .file => .file,
1785 .sym_link => .link,1810 .sym_link => .link,
1786 else => return f.fail(f.location_tok, try eb.printString(1811 else => return f.fail(f.location_tok, try eb.printString(
1787 "package contains '{s}' which has illegal file type '{t}'",1812 "package contains {q} which has illegal file type {t}",
1788 .{ entry.path, entry.kind },1813 .{ entry.path, entry.kind },
1789 )),1814 )),
1790 };1815 };
...@@ -1826,10 +1851,9 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash...@@ -1826,10 +1851,9 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
1826 error.DirNotEmpty => continue,1851 error.DirNotEmpty => continue,
1827 error.FileNotFound => continue,1852 error.FileNotFound => continue,
1828 else => |e| {1853 else => |e| {
1829 try eb.addRootErrorMessage(.{ .msg = try eb.printString(1854 try eb.addRootErrorMessage(.{
1830 "unable to delete empty directory '{s}': {s}",1855 .msg = try eb.printString("unable to delete empty directory {q}: {t}", .{ sus_dir, e }),
1831 .{ sus_dir, @errorName(e) },1856 });
1832 ) });
1833 return error.FetchFailed;1857 return error.FetchFailed;
1834 },1858 },
1835 };1859 };
...@@ -1847,9 +1871,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash...@@ -1847,9 +1871,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
1847 hashed_file.failure catch |err| {1871 hashed_file.failure catch |err| {
1848 any_failures = true;1872 any_failures = true;
1849 try eb.addRootErrorMessage(.{1873 try eb.addRootErrorMessage(.{
1850 .msg = try eb.printString("unable to hash '{s}': {s}", .{1874 .msg = try eb.printString("unable to hash {q}: {t}", .{ hashed_file.fs_path, err }),
1851 hashed_file.fs_path, @errorName(err),
1852 }),
1853 });1875 });
1854 };1876 };
1855 hasher.update(&hashed_file.hash);1877 hasher.update(&hashed_file.hash);
...@@ -1859,8 +1881,8 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash...@@ -1859,8 +1881,8 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
1859 deleted_file.failure catch |err| {1881 deleted_file.failure catch |err| {
1860 any_failures = true;1882 any_failures = true;
1861 try eb.addRootErrorMessage(.{1883 try eb.addRootErrorMessage(.{
1862 .msg = try eb.printString("failed to delete excluded path '{s}' from package: {s}", .{1884 .msg = try eb.printString("failed to delete excluded path {q} from package: {t}", .{
1863 deleted_file.fs_path, @errorName(err),1885 deleted_file.fs_path, err,
1864 }),1886 }),
1865 });1887 });
1866 };1888 };
...@@ -2216,21 +2238,21 @@ const UnpackResult = struct {...@@ -2216,21 +2238,21 @@ const UnpackResult = struct {
2216 switch (item) {2238 switch (item) {
2217 .unable_to_create_sym_link => |info| {2239 .unable_to_create_sym_link => |info| {
2218 eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{2240 eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{
2219 .msg = try eb.printString("unable to create symlink from '{s}' to '{s}': {s}", .{2241 .msg = try eb.printString("unable to create symlink from {q} to {q}: {t}", .{
2220 info.file_name, info.link_name, @errorName(info.code),2242 info.file_name, info.link_name, info.code,
2221 }),2243 }),
2222 }));2244 }));
2223 },2245 },
2224 .unable_to_create_file => |info| {2246 .unable_to_create_file => |info| {
2225 eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{2247 eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{
2226 .msg = try eb.printString("unable to create file '{s}': {s}", .{2248 .msg = try eb.printString("unable to create file {q}: {t}", .{
2227 info.file_name, @errorName(info.code),2249 info.file_name, info.code,
2228 }),2250 }),
2229 }));2251 }));
2230 },2252 },
2231 .unsupported_file_type => |info| {2253 .unsupported_file_type => |info| {
2232 eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{2254 eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{
2233 .msg = try eb.printString("file '{s}' has unsupported type '{c}'", .{2255 .msg = try eb.printString("file {q} has unsupported type '{c}'", .{
2234 info.file_name, info.file_type,2256 info.file_name, info.file_type,
2235 }),2257 }),
2236 }));2258 }));
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