From 4fc40330a4f59f57cfb93e09e56291def147d20d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 19 Oct 2026 17:34:42 -0700 Subject: [PATCH 1/2] 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. --- doc/build.zig.zon.md | 3 ++ lib/compiler/Maker.zig | 10 ++++ lib/compiler/Maker/Fetch.zig | 66 ++++++++++++++++--------- lib/compiler/Maker/Package/Manifest.zig | 58 +++++++++++++++++----- 4 files changed, 104 insertions(+), 33 deletions(-) diff --git a/doc/build.zig.zon.md b/doc/build.zig.zon.md index 690905a400afa683365dae704e6d18a0abaded2e..c43282a94885a17d7259a9ffb18044373305cac9 100644 --- a/doc/build.zig.zon.md +++ b/doc/build.zig.zon.md @@ -71,6 +71,9 @@ Struct. Each dependency must either provide a `url` and `hash`, or a `path`. +May also include `paths` and `version` which provides those values in +the case that the package lacks a `build.zig.zon` file. + #### `url` String. diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index 66ba521c040d736e679af5bf58773bfcecfc55d2..28b966e3e13fa89d7a9a14cafdc7e98e8abc5d9f 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -1342,6 +1342,11 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { .location_tok = 0, .hash_tok = .none, .name_tok = 0, + .name = "", + .parent_specified_paths = .empty, + .parent_specified_paths_tok = .none, + .parent_specified_version = undefined, + .parent_specified_version_tok = .none, .lazy_status = .eager, .remote_package_root = phantom_package_root, .parent_package_root = phantom_package_root, @@ -2054,6 +2059,11 @@ fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void { .location_tok = 0, .hash_tok = .none, .name_tok = 0, + .name = "", + .parent_specified_paths = .empty, + .parent_specified_paths_tok = .none, + .parent_specified_version = undefined, + .parent_specified_version_tok = .none, .lazy_status = .eager, .remote_package_root = undefined, .parent_package_root = if (build_root_initialized) .{ .root_dir = build_root.directory } else .cwd(), diff --git a/lib/compiler/Maker/Fetch.zig b/lib/compiler/Maker/Fetch.zig index 761457d6e0a4ccc404608535b7e02f13a56b1b69..a90085d0b49943e9946378dc8eff5220e396ca79 100644 --- a/lib/compiler/Maker/Fetch.zig +++ b/lib/compiler/Maker/Fetch.zig @@ -56,6 +56,11 @@ location: Location, location_tok: std.zig.Ast.TokenIndex, hash_tok: std.zig.Ast.OptionalTokenIndex, name_tok: std.zig.Ast.TokenIndex, +name: []const u8, +parent_specified_paths: std.array_hash_map.String(void), +parent_specified_paths_tok: std.zig.Ast.OptionalTokenIndex, +parent_specified_version: std.SemanticVersion, +parent_specified_version_tok: std.zig.Ast.OptionalTokenIndex, lazy_status: LazyStatus, /// Same as `parent_packge_root` except it is unchanged when recursing into /// relative file paths (as opposed to URL). @@ -632,7 +637,7 @@ pub fn run(f: *Fetch) RunError!void { log.debug("FileNotFound: {f}", .{package_root}); if (job_queue.read_only and f.lazy_status == .eager) return f.fail( f.name_tok, - try eb.printString("package not found at '{f}'", .{package_root}), + try eb.printString("package not found at {qf}", .{package_root}), ); }, error.Canceled => |e| return e, @@ -762,7 +767,7 @@ fn runResource( try loadManifest(f, pkg_path); const filter: Filter = .{ - .include_paths = if (f.have_manifest) f.manifest.paths else .{}, + .include_paths = if (f.have_manifest) f.manifest.paths else f.parent_specified_paths, }; // Ignore errors that were excluded by manifest, such as failure to @@ -856,9 +861,12 @@ pub fn computedPackageHash(f: *const Fetch) Package.Hash { const version: []const u8 = std.mem.print(&version_buffer, "{f}", .{man.version}) catch &version_buffer; return .init(f.computed_hash.digest, man.name, version, man.id, saturated_size); } - // In the future build.zig.zon fields will be added to allow overriding these values - // for naked tarballs. - return .init(f.computed_hash.digest, "N", "V", 0xffff, saturated_size); + var version_buffer: [Manifest.max_version_len]u8 = undefined; + const v: []const u8 = if (f.parent_specified_version_tok != .none) + std.mem.print(&version_buffer, "{f}", .{f.parent_specified_version}) catch unreachable + else + "V"; + return .init(f.computed_hash.digest, f.name, v, 0xffff, saturated_size); } /// `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 { error.ErrorsBundled => return error.FetchFailed, else => |e| { try eb.addRootErrorMessage(.{ - .msg = try eb.printString("unable to load package manifest '{f}': {t}", .{ manifest_path, e }), + .msg = try eb.printString("unable to load package manifest {qf}: {t}", .{ manifest_path, e }), }); return error.FetchFailed; }, }; f.have_manifest = true; + + if (f.parent_specified_paths_tok.unwrap()) |tok| { + return f.fail(tok, try eb.printString("paths provided for package containing {s}", .{ + Manifest.basename, + })); + } + + if (f.parent_specified_version_tok.unwrap()) |tok| { + return f.fail(tok, try eb.printString("version provided for package containing {s}", .{ + Manifest.basename, + })); + } } fn queueJobsForDeps(f: *Fetch) RunError!void { @@ -1008,6 +1028,11 @@ fn queueJobsForDeps(f: *Fetch) RunError!void { .location_tok = dep.location_tok, .hash_tok = dep.hash_tok, .name_tok = dep.name_tok, + .name = dep_name, + .parent_specified_paths = dep.paths, + .parent_specified_paths_tok = dep.paths_tok, + .parent_specified_version = dep.version, + .parent_specified_version_tok = dep.version_tok, .lazy_status = switch (f.job_queue.mode) { .needed => if (dep.lazy) .available else .eager, .all => .eager, @@ -1753,7 +1778,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash while (walker.next(io) catch |err| { try eb.addRootErrorMessage(.{ .msg = try eb.printString( - "unable to walk temporary directory '{f}': {t}", + "unable to walk temporary directory {qf}: {t}", .{ pkg_path, err }, ) }); return error.FetchFailed; @@ -1784,7 +1809,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash .file => .file, .sym_link => .link, else => return f.fail(f.location_tok, try eb.printString( - "package contains '{s}' which has illegal file type '{t}'", + "package contains {q} which has illegal file type {t}", .{ entry.path, entry.kind }, )), }; @@ -1826,10 +1851,9 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash error.DirNotEmpty => continue, error.FileNotFound => continue, else => |e| { - try eb.addRootErrorMessage(.{ .msg = try eb.printString( - "unable to delete empty directory '{s}': {s}", - .{ sus_dir, @errorName(e) }, - ) }); + try eb.addRootErrorMessage(.{ + .msg = try eb.printString("unable to delete empty directory {q}: {t}", .{ sus_dir, e }), + }); return error.FetchFailed; }, }; @@ -1847,9 +1871,7 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash hashed_file.failure catch |err| { any_failures = true; try eb.addRootErrorMessage(.{ - .msg = try eb.printString("unable to hash '{s}': {s}", .{ - hashed_file.fs_path, @errorName(err), - }), + .msg = try eb.printString("unable to hash {q}: {t}", .{ hashed_file.fs_path, err }), }); }; hasher.update(&hashed_file.hash); @@ -1859,8 +1881,8 @@ fn computeHash(f: *Fetch, pkg_path: Path, filter: Filter) RunError!ComputedHash deleted_file.failure catch |err| { any_failures = true; try eb.addRootErrorMessage(.{ - .msg = try eb.printString("failed to delete excluded path '{s}' from package: {s}", .{ - deleted_file.fs_path, @errorName(err), + .msg = try eb.printString("failed to delete excluded path {q} from package: {t}", .{ + deleted_file.fs_path, err, }), }); }; @@ -2216,21 +2238,21 @@ const UnpackResult = struct { switch (item) { .unable_to_create_sym_link => |info| { eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{ - .msg = try eb.printString("unable to create symlink from '{s}' to '{s}': {s}", .{ - info.file_name, info.link_name, @errorName(info.code), + .msg = try eb.printString("unable to create symlink from {q} to {q}: {t}", .{ + info.file_name, info.link_name, info.code, }), })); }, .unable_to_create_file => |info| { eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{ - .msg = try eb.printString("unable to create file '{s}': {s}", .{ - info.file_name, @errorName(info.code), + .msg = try eb.printString("unable to create file {q}: {t}", .{ + info.file_name, info.code, }), })); }, .unsupported_file_type => |info| { eb.extra.items[note_i] = @backingInt(try eb.addErrorMessage(.{ - .msg = try eb.printString("file '{s}' has unsupported type '{c}'", .{ + .msg = try eb.printString("file {q} has unsupported type '{c}'", .{ info.file_name, info.file_type, }), })); diff --git a/lib/compiler/Maker/Package/Manifest.zig b/lib/compiler/Maker/Package/Manifest.zig index e9ce31626294a26bea9642c34827e5ae0b142cd3..517c5fd3239077e2f5cf324d4667648e5417ebc3 100644 --- a/lib/compiler/Maker/Package/Manifest.zig +++ b/lib/compiler/Maker/Package/Manifest.zig @@ -25,6 +25,10 @@ pub const Dependency = struct { node: Ast.Node.Index, name_tok: Ast.TokenIndex, lazy: bool, + paths: std.array_hash_map.String(void), + paths_tok: Ast.OptionalTokenIndex, + version: std.SemanticVersion, + version_tok: Ast.OptionalTokenIndex, pub const Location = union(enum) { url: []const u8, @@ -177,7 +181,7 @@ const Parse = struct { try parseDependencies(p, field_init); } else if (mem.eql(u8, field_name, "paths")) { have_included_paths = true; - try parseIncludedPaths(p, field_init); + try parseIncludedPaths(p, &p.paths, field_init); } else if (mem.eql(u8, field_name, "name")) { p.name = try parseName(p, field_init); have_name = true; @@ -185,15 +189,13 @@ const Parse = struct { fingerprint = try parseFingerprint(p, field_init); } else if (mem.eql(u8, field_name, "version")) { p.version_node = field_init; - const version_text = try parseString(p, field_init); - if (version_text.len > max_version_len) { - try appendError(p, ast.nodeMainToken(field_init), "version string length {d} exceeds maximum of {d}", .{ version_text.len, max_version_len }); - } - p.version = std.SemanticVersion.parse(version_text) catch |err| v: { - try appendError(p, ast.nodeMainToken(field_init), "unable to parse semantic version: {s}", .{@errorName(err)}); - break :v undefined; - }; have_version = true; + if (parseVersion(p, field_init)) |v| { + p.version = v; + } else |err| switch (err) { + else => |e| return e, + error.ParseFailure => {}, + } } else if (mem.eql(u8, field_name, "minimum_zig_version")) { const version_text = try parseString(p, field_init); p.minimum_zig_version = std.SemanticVersion.parse(version_text) catch |err| v: { @@ -236,6 +238,21 @@ const Parse = struct { } } + fn parseVersion(p: *Parse, node: Ast.Node.Index) InnerError!std.SemanticVersion { + const ast = p.ast; + const version_text = try parseString(p, node); + if (version_text.len > max_version_len) { + try appendError(p, ast.nodeMainToken(node), "version string length {d} exceeds maximum of {d}", .{ + version_text.len, max_version_len, + }); + } + return std.SemanticVersion.parse(version_text) catch |err| { + return fail(p, ast.nodeMainToken(node), "parsing semantic version {q} failed: {t}", .{ + version_text, err, + }); + }; + } + fn parseDependencies(p: *Parse, node: Ast.Node.Index) !void { const ast = p.ast; @@ -272,6 +289,10 @@ const Parse = struct { .node = node, .name_tok = undefined, .lazy = false, + .paths = .empty, + .paths_tok = .none, + .version = undefined, + .version_tok = .none, }; var has_location = false; @@ -320,6 +341,17 @@ const Parse = struct { error.ParseFailure => continue, else => |e| return e, }; + } else if (mem.eql(u8, field_name, "paths")) { + dep.paths_tok = .fromToken(name_token); + try parseIncludedPaths(p, &dep.paths, field_init); + } else if (mem.eql(u8, field_name, "version")) { + if (parseVersion(p, field_init)) |v| { + dep.version = v; + dep.version_tok = .fromToken(name_token); + } else |err| switch (err) { + else => |e| return e, + error.ParseFailure => {}, + } } else { // Ignore unknown fields so that we can add fields in future zig // versions without breaking older zig versions. @@ -333,7 +365,7 @@ const Parse = struct { return dep; } - fn parseIncludedPaths(p: *Parse, node: Ast.Node.Index) !void { + fn parseIncludedPaths(p: *Parse, paths: *std.array_hash_map.String(void), node: Ast.Node.Index) !void { const ast = p.ast; var buf: [2]Ast.Node.Index = undefined; @@ -347,7 +379,11 @@ const Parse = struct { // This is normalized so that it can be used in string comparisons // against file system paths. const normalized = try std.fs.path.resolve(p.arena, &.{path_string}); - try p.paths.put(p.gpa, normalized, {}); + if (try paths.fetchPut(p.gpa, normalized, {})) |kv| { + return fail(p, ast.nodeMainToken(elem_node), "path {q} already included as {q}", .{ + normalized, kv.key, + }); + } } } -- 2.54.0 From ffa20b6deb0aa9f573be3c85538b86d0d4f0cdf4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 19 Oct 2026 17:46:04 -0700 Subject: [PATCH 2/2] Fetch: only use name for naked packages if version specified --- lib/compiler/Maker/Fetch.zig | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/compiler/Maker/Fetch.zig b/lib/compiler/Maker/Fetch.zig index a90085d0b49943e9946378dc8eff5220e396ca79..c1bc153056854864050ef8a0656255b2541b57a5 100644 --- a/lib/compiler/Maker/Fetch.zig +++ b/lib/compiler/Maker/Fetch.zig @@ -861,12 +861,15 @@ pub fn computedPackageHash(f: *const Fetch) Package.Hash { const version: []const u8 = std.mem.print(&version_buffer, "{f}", .{man.version}) catch &version_buffer; return .init(f.computed_hash.digest, man.name, version, man.id, saturated_size); } + // Previous version of Zig did not use f.name to affect the package hash, so + // allow the user to opt in by only doing that if they specify a version. var version_buffer: [Manifest.max_version_len]u8 = undefined; - const v: []const u8 = if (f.parent_specified_version_tok != .none) - std.mem.print(&version_buffer, "{f}", .{f.parent_specified_version}) catch unreachable - else - "V"; - return .init(f.computed_hash.digest, f.name, v, 0xffff, saturated_size); + if (f.parent_specified_version_tok != .none) { + const v = std.mem.print(&version_buffer, "{f}", .{f.parent_specified_version}) catch unreachable; + return .init(f.computed_hash.digest, f.name, v, 0xffff, saturated_size); + } else { + return .init(f.computed_hash.digest, "N", "V", 0xffff, saturated_size); + } } /// `computeHash` gets a free check for the existence of `build.zig`, but when -- 2.54.0