From b6168b6da8718f32e416c5256ad4839603a71b37 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Mon, 11 Aug 2025 03:33:58 -0700 Subject: [PATCH] add min_zigmod_version manifest attribute --- docs/zig.mod.md | 5 +++++ src/cmd/aquila/add.zig | 8 ++++---- src/cmd/aquila/install.zig | 4 ++-- src/cmd/aquila/update.zig | 2 +- src/cmd/zpm/add.zig | 2 +- src/common.zig | 10 +++++----- src/lib.zig | 21 +++++++++++++++++++++ src/util/modfile.zig | 33 +++++++++++++++++++++++---------- zig.mod | 1 + 9 files changed, 63 insertions(+), 23 deletions(-) diff --git a/docs/zig.mod.md b/docs/zig.mod.md index 85eef2e4ad09cff8157bbd6fc2808e14ca8bbcc5..f8690cf10e6ae48ce171c92b8d5e2715bf17e3bb 100644 --- a/docs/zig.mod.md +++ b/docs/zig.mod.md @@ -58,6 +58,11 @@ Similar to `dependencies` but will only get added to the project if the current - Type: `string` Parsed as a `std.SemanticVersion`, this attribute refers to the minimum compatible Zig version for this package/application and will cause `zig build` to panic if violated. +### `min_zigmod_version` +- Type: `string` +This attribute refers to the minimum compatible Zigmod version for this package/application and will cause `zigmod fetch`, `zigmod ci`, and others to exit with an error. +While rare, this is most useful to provide a nice error message to the user when your `zigmod.yml` uses a new feature that may not be available in previous versions of Zigmod. + ---- ### Dep Object diff --git a/src/cmd/aquila/add.zig b/src/cmd/aquila/add.zig index c0d2f541f5082b120b72b6a5f7fc77ab1d602e08..1f01e1af203a69a6363a0fe52867e84e899f3a33 100644 --- a/src/cmd/aquila/add.zig +++ b/src/cmd/aquila/add.zig @@ -12,11 +12,11 @@ pub fn execute(self_name: []const u8, args: [][:0]u8) !void { _ = self_name; const pkg_id = args[0]; - _ = try do(std.fs.cwd(), pkg_id); + _ = try do(std.fs.cwd(), ".", pkg_id); std.log.info("Successfully added package {s}", .{pkg_id}); } -pub fn do(dir: std.fs.Dir, pkg_id: string) !string { +pub fn do(dir: std.fs.Dir, dir_path: string, pkg_id: string) !string { const url = try std.mem.join(gpa, "/", &.{ aq.server_root, pkg_id }); const doc = try aq.server_fetch(url); doc.acquire(); @@ -27,7 +27,7 @@ pub fn do(dir: std.fs.Dir, pkg_id: string) !string { doc.root.object().getO("package").?.getS("remote_name").?, }); - const m = try zigmod.ModFile.from_dir(gpa, dir); + const m = try zigmod.ModFile.from_dir(gpa, dir, dir_path); for (m.rootdeps) |d| { if (std.mem.eql(u8, d.path, pkg_url)) { return pkg_url; @@ -39,7 +39,7 @@ pub fn do(dir: std.fs.Dir, pkg_id: string) !string { } } - var file = try zigmod.ModFile.openFile(dir, .{ .mode = .read_write }); + _, var file = try zigmod.ModFile.openFile(dir, .{ .mode = .read_write }); defer file.close(); try file.seekTo(try file.getEndPos()); diff --git a/src/cmd/aquila/install.zig b/src/cmd/aquila/install.zig index 493df3f342cbfc96b7b0506289acc9219c094706..2b272672aafd10133f212f4730b68d9e07dfe8e2 100644 --- a/src/cmd/aquila/install.zig +++ b/src/cmd/aquila/install.zig @@ -25,13 +25,13 @@ pub fn execute(self_name: []const u8, args: [][:0]u8) !void { // add to ~/zigmod.yml for later const aqadd = @import("./add.zig"); - const pkgurl = aqadd.do(homedir, args[0]) catch |err| switch (err) { + const pkgurl = aqadd.do(homedir, homepath, args[0]) catch |err| switch (err) { error.AquilaBadResponse => return, else => |ee| return ee, }; // get modfile and dep - const m = try zigmod.ModFile.from_dir(gpa, homedir); + const m = try zigmod.ModFile.from_dir(gpa, homedir, homepath); var dep: zigmod.Dep = undefined; for (m.rootdeps) |d| { if (std.mem.eql(u8, d.path, pkgurl)) { diff --git a/src/cmd/aquila/update.zig b/src/cmd/aquila/update.zig index 0c708b6c75d079e17e69afa53629194b7b9563b8..80e2f830c223f5d51520c622db58470832a7453e 100644 --- a/src/cmd/aquila/update.zig +++ b/src/cmd/aquila/update.zig @@ -25,7 +25,7 @@ pub fn execute(self_name: []const u8, args: [][:0]u8) !void { u.assert(args.len == 0, "zigmod aq update accepts no parameters", .{}); // get modfile and dep - const m = try zigmod.ModFile.from_dir(gpa, homedir); + const m = try zigmod.ModFile.from_dir(gpa, homedir, homepath); for (m.rootdeps) |dep| { // const cache = try knownfolders.getPath(gpa, .cache); diff --git a/src/cmd/zpm/add.zig b/src/cmd/zpm/add.zig index 6e472a2d4aeab7cbb6df14e0ea14402dc02ba931..088fb3993bfea0cd48372ebdb060cd61ce53d9c6 100644 --- a/src/cmd/zpm/add.zig +++ b/src/cmd/zpm/add.zig @@ -57,7 +57,7 @@ pub fn execute(self_name: []const u8, args: [][:0]u8) !void { break :blk @intFromEnum(_req.status) == 200; }; - const file = try zigmod.ModFile.openFile(std.fs.cwd(), .{ .mode = .read_write }); + _, const file = try zigmod.ModFile.openFile(std.fs.cwd(), .{ .mode = .read_write }); defer file.close(); try file.seekTo(try file.getEndPos()); diff --git a/src/common.zig b/src/common.zig index 613f50c40f677d77cd2009aeac30741597f9afde..6f793cf52d70b0f2e4860a5c39b030d7f3d302d8 100644 --- a/src/common.zig +++ b/src/common.zig @@ -26,14 +26,14 @@ pub const CollectOptions = struct { pub fn collect_deps_deep(cachepath: string, mdir: std.fs.Dir, options: *CollectOptions) !zigmod.Module { try std.fs.cwd().makePath(cachepath); - const m = try zigmod.ModFile.from_dir(options.alloc, mdir); + const m = try zigmod.ModFile.from_dir(options.alloc, mdir, "."); try options.init(); var moduledeps = std.ArrayList(zigmod.Module).init(options.alloc); errdefer moduledeps.deinit(); if (m.root_files.len > 0) { try gen_files_package(options.alloc, cachepath, mdir, m.root_files); } - try moduledeps.append(try collect_deps(cachepath, mdir, .local, options)); + try moduledeps.append(try collect_deps(cachepath, mdir, ".", .local, options)); for (m.rootdeps) |*d| { if (try get_module_from_dep(d, cachepath, options)) |founddep| { try moduledeps.append(founddep); @@ -57,10 +57,10 @@ pub fn collect_deps_deep(cachepath: string, mdir: std.fs.Dir, options: *CollectO }; } -pub fn collect_deps(cachepath: string, mdir: std.fs.Dir, dtype: zigmod.Dep.Type, options: *CollectOptions) anyerror!zigmod.Module { +pub fn collect_deps(cachepath: string, mdir: std.fs.Dir, mdir_path: string, dtype: zigmod.Dep.Type, options: *CollectOptions) anyerror!zigmod.Module { try std.fs.cwd().makePath(cachepath); - const m = try zigmod.ModFile.from_dir(options.alloc, mdir); + const m = try zigmod.ModFile.from_dir(options.alloc, mdir, mdir_path); var moduledeps = std.ArrayList(zigmod.Module).init(options.alloc); errdefer moduledeps.deinit(); if (m.files.len > 0) { @@ -244,7 +244,7 @@ pub fn get_module_from_dep(d: *zigmod.Dep, cachepath: string, options: *CollectO }; }, else => { - var dd = collect_deps(cachepath, moddir, d.type, options) catch |e| switch (e) { + var dd = collect_deps(cachepath, moddir, modpath, d.type, options) catch |e| switch (e) { error.ManifestNotFound => { if (d.main.len > 0 or d.c_include_dirs.len > 0 or d.c_source_files.len > 0 or d.keep) { var mod_from = try zigmod.Module.from(options.alloc, d.*, cachepath, options); diff --git a/src/lib.zig b/src/lib.zig index 4bf717e31444ad3884c45bc74b71a73113754233..9fa01c32f4ff3fab5916ce1d7b247fa73e034328 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -1,4 +1,8 @@ +const std = @import("std"); const zfetch = @import("zfetch"); +const extras = @import("extras"); +const root = @import("root"); +const build_options = root.build_options; pub const commands = struct { pub const version = @import("./cmd/version.zig"); @@ -23,3 +27,20 @@ pub fn deinit() void { pub const Dep = @import("./util/dep.zig").Dep; pub const ModFile = @import("./util/modfile.zig").ModFile; pub const Module = @import("./util/module.zig").Module; + +pub const version: u16 = blk: { + var version_s = build_options.version; + version_s = extras.trimPrefixEnsure(version_s, "r").?; + version_s = version_s[0 .. std.mem.indexOfScalar(u8, version_s, '-') orelse version_s.len]; + var version_i = std.fmt.parseInt(u16, version_s, 10) catch unreachable; + if (std.mem.indexOfScalar(u8, version_s, '-')) |_| version_i += 1; + break :blk version_i; +}; + +pub fn meetsMinimumVersion(min_zigmod_version_raw: []const u8) ?bool { + var min_zigmod_version_s = min_zigmod_version_raw; + min_zigmod_version_s = extras.trimPrefixEnsure(min_zigmod_version_s, "r") orelse return null; + min_zigmod_version_s = min_zigmod_version_s[0 .. std.mem.indexOfScalar(u8, min_zigmod_version_s, '-') orelse min_zigmod_version_s.len]; + const min_zigmod_version = std.fmt.parseInt(u16, min_zigmod_version_s, 10) catch return null; + return min_zigmod_version <= version; +} diff --git a/src/util/modfile.zig b/src/util/modfile.zig index 923686f4e573fe97490def2e528d03fc24798aa6..c5efa2af33ac9cf2ea1f5ea2b3e288e3424e05fb 100644 --- a/src/util/modfile.zig +++ b/src/util/modfile.zig @@ -1,6 +1,9 @@ const std = @import("std"); const string = []const u8; const yaml = @import("yaml"); +const extras = @import("extras"); +const root = @import("root"); +const build_options = root.build_options; const zigmod = @import("../lib.zig"); const u = @import("funcs.zig"); @@ -29,29 +32,29 @@ pub const ModFile = struct { builddeps: []zigmod.Dep, min_zig_version: ?std.SemanticVersion, - pub fn openFile(dir: std.fs.Dir, ops: std.fs.File.OpenFlags) !std.fs.File { - return dir.openFile("zig.mod", ops) catch |err| switch (err) { - error.FileNotFound => dir.openFile("zigmod.yml", ops) catch |err2| switch (err2) { + pub fn openFile(dir: std.fs.Dir, ops: std.fs.File.OpenFlags) !struct { string, std.fs.File } { + return .{ "zig.mod", dir.openFile("zig.mod", ops) catch |err| switch (err) { + error.FileNotFound => return .{ "zigmod.yml", dir.openFile("zigmod.yml", ops) catch |err2| switch (err2) { error.FileNotFound => return error.ManifestNotFound, else => |e2| return e2, - }, + } }, else => |e| return e, - }; + } }; } pub fn init(alloc: std.mem.Allocator) !Self { - return try from_dir(alloc, std.fs.cwd()); + return try from_dir(alloc, std.fs.cwd(), "."); } - pub fn from_dir(alloc: std.mem.Allocator, dir: std.fs.Dir) !Self { - const file = try openFile(dir, .{}); + pub fn from_dir(alloc: std.mem.Allocator, dir: std.fs.Dir, dir_path: string) !Self { + const file_path, const file = try openFile(dir, .{}); defer file.close(); const input = try file.reader().readAllAlloc(alloc, mb); const doc = try yaml.parse(alloc, input); - return from_mapping(alloc, doc.mapping); + return from_mapping(alloc, doc.mapping, dir_path, file_path); } - pub fn from_mapping(alloc: std.mem.Allocator, mapping: yaml.Mapping) !Self { + pub fn from_mapping(alloc: std.mem.Allocator, mapping: yaml.Mapping, dir_path: string, file_path: string) !Self { var id = zigmod.Dep.EMPTY; std.mem.copyForwards(u8, &id, mapping.get_string("id") orelse &u.random_string(48)); @@ -62,6 +65,16 @@ pub const ModFile = struct { u.fail("name may not contain any '/'", .{}); } + if (mapping.get_string("min_zigmod_version")) |min_zigmod_version_raw| { + const meets = zigmod.meetsMinimumVersion(min_zigmod_version_raw); + if (meets == null) { + u.fail("invalid min_zigmod_version: {s}", .{min_zigmod_version_raw}); + } + if (meets.? == false) { + u.fail("Your Zigmod version {s} does not meet the minimum of {s} required by {s}{s}{s}", .{ build_options.version, min_zigmod_version_raw, dir_path, std.fs.path.sep_str, file_path }); + } + } + return Self{ .id = id, .name = name, diff --git a/zig.mod b/zig.mod index f361827fbb1a894321e7b45db2a09bf018d9fb6a..a48fb3e1d1271a8260bd92e9dae76c0214d1bc77 100644 --- a/zig.mod +++ b/zig.mod @@ -4,6 +4,7 @@ main: src/lib.zig license: MIT description: A package manager for the Zig programming language. min_zig_version: 0.14.0 +min_zigmod_version: r96 dependencies: - src: git https://github.com/nektro/zig-yaml - src: git https://github.com/nektro/zig-ansi -- 2.54.0