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.
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
......@@ -1342,6 +1342,11 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
13421342 .location_tok = 0,
13431343 .hash_tok = .none,
13441344 .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,
13451350 .lazy_status = .eager,
13461351 .remote_package_root = phantom_package_root,
13471352 .parent_package_root = phantom_package_root,
......@@ -2054,6 +2059,11 @@ fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
20542059 .location_tok = 0,
20552060 .hash_tok = .none,
20562061 .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,
20572067 .lazy_status = .eager,
20582068 .remote_package_root = undefined,
20592069 .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,
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,12 @@ 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 var version_buffer: [Manifest.max_version_len]u8 = undefined;
865 const v: []const u8 = if (f.parent_specified_version_tok != .none)
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);
862870}
863871
864872/// `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 {
902910 error.ErrorsBundled => return error.FetchFailed,
903911 else => |e| {
904912 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 }),
906914 });
907915 return error.FetchFailed;
908916 },
909917 };
910918 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 }
911931}
912932
913933fn queueJobsForDeps(f: *Fetch) RunError!void {
......@@ -1008,6 +1028,11 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {
10081028 .location_tok = dep.location_tok,
10091029 .hash_tok = dep.hash_tok,
10101030 .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,
10111036 .lazy_status = switch (f.job_queue.mode) {
10121037 .needed => if (dep.lazy) .available else .eager,
10131038 .all => .eager,
......@@ -1753,7 +1778,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
17531778
17541779 while (walker.next(io) catch |err| {
17551780 try eb.addRootErrorMessage(.{ .msg = try eb.printString(
1756 "unable to walk temporary directory '{f}': {t}",
1781 "unable to walk temporary directory {qf}: {t}",
17571782 .{ pkg_path, err },
17581783 ) });
17591784 return error.FetchFailed;
......@@ -1784,7 +1809,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
17841809 .file => .file,
17851810 .sym_link => .link,
17861811 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}",
17881813 .{ entry.path, entry.kind },
17891814 )),
17901815 };
......@@ -1826,10 +1851,9 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
18261851 error.DirNotEmpty => continue,
18271852 error.FileNotFound => continue,
18281853 else => |e| {
1829 try eb.addRootErrorMessage(.{ .msg = try eb.printString(
1830 "unable to delete empty directory '{s}': {s}",
1831 .{ sus_dir, @errorName(e) },
1832 ) });
1854 try eb.addRootErrorMessage(.{
1855 .msg = try eb.printString("unable to delete empty directory {q}: {t}", .{ sus_dir, e }),
1856 });
18331857 return error.FetchFailed;
18341858 },
18351859 };
......@@ -1847,9 +1871,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
18471871 hashed_file.failure catch |err| {
18481872 any_failures = true;
18491873 try eb.addRootErrorMessage(.{
1850 .msg = try eb.printString("unable to hash '{s}': {s}", .{
1851 hashed_file.fs_path, @errorName(err),
1852 }),
1874 .msg = try eb.printString("unable to hash {q}: {t}", .{ hashed_file.fs_path, err }),
18531875 });
18541876 };
18551877 hasher.update(&hashed_file.hash);
......@@ -1859,8 +1881,8 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash
18591881 deleted_file.failure catch |err| {
18601882 any_failures = true;
18611883 try eb.addRootErrorMessage(.{
1862 .msg = try eb.printString("failed to delete excluded path '{s}' from package: {s}", .{
1863 deleted_file.fs_path, @errorName(err),
1884 .msg = try eb.printString("failed to delete excluded path {q} from package: {t}", .{
1885 deleted_file.fs_path, err,
18641886 }),
18651887 });
18661888 };
......@@ -2216,21 +2238,21 @@ const UnpackResult = struct {
22162238 switch (item) {
22172239 .unable_to_create_sym_link => |info| {
22182240 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),
2241 .msg = try eb.printString("unable to create symlink from {q} to {q}: {t}", .{
2242 info.file_name, info.link_name, info.code,
22212243 }),
22222244 }));
22232245 },
22242246 .unable_to_create_file => |info| {
22252247 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),
2248 .msg = try eb.printString("unable to create file {q}: {t}", .{
2249 info.file_name, info.code,
22282250 }),
22292251 }));
22302252 },
22312253 .unsupported_file_type => |info| {
22322254 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}'", .{
22342256 info.file_name, info.file_type,
22352257 }),
22362258 }));
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