authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-06-01 19:41:24 -07:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-06-01 19:41:24 -07:00
logfbd6a16088d32e3f8c674e8cf8f266a02fca270c
treedddd97aa7d0233033e3644821870a7bb2f777581
parent3082f4766486f3726c9431cc0b2f3d86456769bb
signaturebadge-check Signed by SSH key SHA256:4hHJbtBRU58AYXwjL7fkz2fnQHdiye8x1QpTCQ0sHNw

cmd/install: make it multi-version, add 0.14 support


3 files changed, 447 insertions(+), 3 deletions(-)

docs/commands/install.md-3
...@@ -16,9 +16,6 @@ zigmod install [git|hg|http] [url]...@@ -16,9 +16,6 @@ zigmod install [git|hg|http] [url]
1616
17`[url]` may be the link to any remote repository that contains a Zig project with a `zigmod.yml` manifest. If your project currently does not have one, you may create one using [`zigmod init`](./init.md).17`[url]` may be the link to any remote repository that contains a Zig project with a `zigmod.yml` manifest. If your project currently does not have one, you may create one using [`zigmod init`](./init.md).
1818
19> Note: It is known this this command will currently work best when the repository is compatible with the version of Zig that your version of Zigmod is built for.
20> At time of writing Zigmod is not currently capable of writing multiple versions of `deps.zig` but this may change as a result of the introduction of this command.
21
22```19```
23$ zigmod install git https://github.com/nektro/zigmod20$ zigmod install git https://github.com/nektro/zigmod
24debug: modpath: /home/me/.cache/zigmod/deps/git/github.com/nektro/zigmod21debug: modpath: /home/me/.cache/zigmod/deps/git/github.com/nektro/zigmod
src/cmd/fetch.0.14.zig created+428
...@@ -0,0 +1,428 @@
1// Adapted from https://github.com/nektro/zigmod/blob/r102/src/cmd/fetch.zig
2
3const std = @import("std");
4const string = []const u8;
5const ansi = @import("ansi");
6const extras = @import("extras");
7const nfs = @import("nfs");
8
9const zigmod = @import("../lib.zig");
10const u = @import("./../util/funcs.zig");
11const common = @import("./../common.zig");
12const license = @import("./license.zig");
13
14pub fn create_depszig(alloc: std.mem.Allocator, cachepath: string, dir: nfs.Dir, top_module: zigmod.Module, list: *std.array_list.Managed(zigmod.Module)) !void {
15 const f = try dir.createFile("deps.zig", .{});
16 defer f.close();
17
18 const w = f;
19 try w.writeAll("// zig fmt: off\n");
20 try w.writeAll("const std = @import(\"std\");\n");
21 try w.writeAll("const builtin = @import(\"builtin\");\n");
22 try w.writeAll("const string = []const u8;\n");
23 try w.writeAll("\n");
24 try w.print("pub const cache = \"{}\";\n", .{u.altStringEscape(cachepath)});
25 try w.writeAll("pub var skip_libc = false;\n");
26 try w.writeAll("\n");
27 try w.writeAll(
28 \\pub fn addAllTo(exe: *std.Build.Step.Compile) void {
29 \\ checkMinZig(builtin.zig_version, exe);
30 \\ @setEvalBranchQuota(1_000_000);
31 \\ for (packages) |pkg| {
32 \\ const module = pkg.module(exe);
33 \\ exe.root_module.addImport(pkg.import.?[0], module);
34 \\ }
35 \\ for (package_data._root.system_libs) |libname| {
36 \\ exe.linkSystemLibrary(libname);
37 \\ exe.linkLibC();
38 \\ }
39 \\ // clear module memo cache so addAllTo can be called more than once in the same build.zig
40 \\ module_memo.clearAndFree(exe.step.owner.allocator);
41 \\}
42 \\
43 \\var link_lib_c = false;
44 \\var module_memo: std.StringArrayHashMapUnmanaged(*std.Build.Module) = .empty;
45 \\pub const Package = struct {
46 \\ id: string,
47 \\ directory: string,
48 \\ import: ?struct { string, std.Build.LazyPath } = null,
49 \\ dependencies: []const *const Package,
50 \\ c_include_dirs: []const string = &.{},
51 \\ c_source_files: []const string = &.{},
52 \\ c_source_flags: []const string = &.{},
53 \\ system_libs: []const string = &.{},
54 \\ frameworks: []const string = &.{},
55 \\
56 \\ pub fn module(self: *const Package, exe: *std.Build.Step.Compile) *std.Build.Module {
57 \\ if (module_memo.get(self.id)) |cached| {
58 \\ return cached;
59 \\ }
60 \\ const b = exe.step.owner;
61 \\ const result = b.createModule(.{
62 \\ .target = exe.root_module.resolved_target,
63 \\ });
64 \\ const target = result.resolved_target.?.result;
65 \\ if (self.import) |capture| {
66 \\ result.root_source_file = capture[1];
67 \\ }
68 \\ for (self.dependencies) |item| {
69 \\ const module_dep = item.module(exe);
70 \\ if (module_dep.root_source_file != null) {
71 \\ result.addImport(item.import.?[0], module_dep);
72 \\ }
73 \\ for (module_dep.include_dirs.items) |jtem| {
74 \\ switch (jtem) {
75 \\ .path => result.addIncludePath(jtem.path),
76 \\ .path_system, .path_after, .framework_path, .framework_path_system, .other_step, .config_header_step => {},
77 \\ }
78 \\ }
79 \\ }
80 \\ for (self.c_include_dirs) |item| {
81 \\ result.addIncludePath(.{ .cwd_relative = (b.fmt("{s}/{s}", .{ self.directory, item })) });
82 \\ exe.addIncludePath(.{ .cwd_relative = (b.fmt("{s}/{s}", .{ self.directory, item })) });
83 \\ link_lib_c = true;
84 \\ }
85 \\ for (self.c_source_files) |item| {
86 \\ exe.addCSourceFile(.{ .file = .{ .cwd_relative = (b.fmt("{s}/{s}", .{ self.directory, item })) }, .flags = self.c_source_flags });
87 \\ link_lib_c = true;
88 \\ }
89 \\ for (self.system_libs) |item| {
90 \\ if (skip_libc and std.zig.target.isLibCLibName(target, item)) continue;
91 \\ result.linkSystemLibrary(item, .{});
92 \\ exe.linkSystemLibrary(item);
93 \\ link_lib_c = true;
94 \\ }
95 \\ for (self.frameworks) |item| {
96 \\ result.linkFramework(item, .{});
97 \\ exe.linkFramework(item);
98 \\ link_lib_c = true;
99 \\ }
100 \\ if (link_lib_c and !skip_libc) {
101 \\ result.link_libc = true;
102 \\ exe.linkLibC();
103 \\ }
104 \\ module_memo.putNoClobber(b.allocator, self.id, result) catch @panic("OOM");
105 \\ return result;
106 \\ }
107 \\};
108 \\
109 \\
110 );
111
112 try w.print(
113 \\fn checkMinZig(current: std.SemanticVersion, exe: *std.Build.Step.Compile) void {{
114 \\ const min = std.SemanticVersion.parse("{?}") catch return;
115 \\ if (current.order(min).compare(.lt)) @panic(exe.step.owner.fmt("Your Zig version v{{}} does not meet the minimum build requirement of v{{}}", .{{current, min}}));
116 \\}}
117 \\
118 \\
119 , .{if (top_module.minZigVersion()) |sv| u.altSemanticVersion(sv) else null});
120
121 try w.writeAll("pub const dirs = struct {\n");
122 try print_dirs(w, list.items, alloc);
123 try w.writeAll("};\n\n");
124
125 try w.writeAll("pub const package_data = struct {\n");
126 var duped = std.array_list.Managed(zigmod.Module).init(alloc);
127 var done = std.array_list.Managed(zigmod.Module).init(alloc);
128 for (list.items) |mod| {
129 if (mod.type == .system_lib or mod.type == .framework) {
130 continue;
131 }
132 try duped.append(mod);
133 }
134 try print_pkg_data_to(w, &duped, &done);
135 try w.writeAll("};\n\n");
136
137 try w.writeAll("pub const packages = ");
138 try print_deps(w, top_module);
139 try w.writeAll(";\n\n");
140
141 try w.writeAll("pub const pkgs = ");
142 try print_pkgs(alloc, w, top_module);
143 try w.writeAll(";\n\n");
144
145 try w.writeAll("pub const imports = struct {\n");
146 try print_imports(alloc, w, top_module, cachepath);
147 try w.writeAll("};\n");
148}
149
150fn create_lockfile(alloc: std.mem.Allocator, list: *std.array_list.Managed(zigmod.Module), path: string, dir: nfs.Dir) !void {
151 const fl = try dir.createFile("zigmod.lock", .{});
152 defer fl.close();
153
154 std.mem.sort(zigmod.Module, list.items, {}, zigmod.Module.lessThan);
155
156 const wl = fl;
157 try wl.writeAll("2\n");
158 for (list.items) |m| {
159 if (m.dep) |md| {
160 if (md.type.isLocal()) continue;
161 const mpath = try std.fs.path.joinZ(alloc, &.{ path, m.clean_path });
162 const version = try md.exact_version(alloc, mpath);
163 try wl.print("{s} {s} {s}\n", .{ @tagName(md.type), md.path, version });
164 }
165 }
166}
167
168const DiffChange = struct {
169 from: string,
170 to: string,
171};
172
173fn diff_lockfile(alloc: std.mem.Allocator) !void {
174 const max = std.math.maxInt(usize);
175
176 if (try nfs.cwd().existsDir(".git")) {
177 const result = try u.run_cmd_raw(alloc, null, &.{ "git", "diff", "zigmod.lock" });
178 var stdout = std.io.fixedBufferStream(result.stdout);
179 const r = stdout.reader();
180 while (try r.readUntilDelimiterOrEofAlloc(alloc, '\n', max)) |line| {
181 if (std.mem.startsWith(u8, line, "@@")) break;
182 }
183
184 var rems = std.array_list.Managed(string).init(alloc);
185 var adds = std.array_list.Managed(string).init(alloc);
186 while (try r.readUntilDelimiterOrEofAlloc(alloc, '\n', max)) |line_full| {
187 const line = line_full[0 .. line_full.len - 1];
188 if (line[0] == ' ') continue;
189 if (line[0] == '-') try rems.append(line[1..]);
190 if (line[0] == '+') if (line[1] == '2') break else try adds.append(line[1..]);
191 }
192
193 var changes = std.StringHashMap(DiffChange).init(alloc);
194
195 var didbreak = false;
196 var i: usize = 0;
197 while (i < rems.items.len) {
198 const it = rems.items[i];
199 const sni = u.indexOfN(it, ' ', 2).?;
200
201 var j: usize = 0;
202 while (j < adds.items.len) {
203 const jt = adds.items[j];
204 const snj = u.indexOfN(jt, ' ', 2).?;
205
206 if (std.mem.eql(u8, it[0..sni], jt[0..snj])) {
207 try changes.put(it[0..sni], .{
208 .from = it[u.indexOfAfter(it, '-', sni).? + 1 .. it.len],
209 .to = jt[u.indexOfAfter(jt, '-', snj).? + 1 .. jt.len],
210 });
211 _ = rems.orderedRemove(i);
212 _ = adds.orderedRemove(j);
213 didbreak = true;
214 break;
215 }
216 if (!didbreak) j += 1;
217 }
218 if (!didbreak) i += 1;
219 if (didbreak) didbreak = false;
220 }
221
222 if (adds.items.len > 0) {
223 std.debug.print(comptime ansi.color.Faint("Newly added packages:\n"), .{});
224 defer std.debug.print("\n", .{});
225
226 for (adds.items) |it| {
227 std.debug.print("- {s}\n", .{it});
228 }
229 }
230
231 if (rems.items.len > 0) {
232 std.debug.print(comptime ansi.color.Faint("Removed packages:\n"), .{});
233 defer std.debug.print("\n", .{});
234
235 for (rems.items) |it| {
236 std.debug.print("- {s}\n", .{it});
237 }
238 }
239
240 if (changes.unmanaged.size > 0) std.debug.print(comptime ansi.color.Faint("Updated packages:\n"), .{});
241 var iter = changes.iterator();
242 while (iter.next()) |it| {
243 if (diff_printchange("git https://github.com", "- {s}/compare/{s}...{s}\n", it)) continue;
244 if (diff_printchange("git https://gitlab.com", "- {s}/-/compare/{s}...{s}\n", it)) continue;
245 if (diff_printchange("git https://gitea.com", "- {s}/compare/{s}...{s}\n", it)) continue;
246
247 std.debug.print("- {s}\n", .{it.key_ptr.*});
248 std.debug.print(" - {s} ... {s}\n", .{ it.value_ptr.from, it.value_ptr.to });
249 }
250 }
251}
252
253fn diff_printchange(comptime testt: string, comptime replacement: string, item: std.StringHashMap(DiffChange).Entry) bool {
254 if (std.mem.startsWith(u8, item.key_ptr.*, testt)) {
255 if (std.mem.eql(u8, item.value_ptr.from, item.value_ptr.to)) return true;
256 std.debug.print(replacement, .{ item.key_ptr.*[4..], item.value_ptr.from, item.value_ptr.to });
257 return true;
258 }
259 return false;
260}
261
262fn print_dirs(w: nfs.File, list: []const zigmod.Module, alloc: std.mem.Allocator) !void {
263 for (list) |mod| {
264 if (mod.type == .system_lib or mod.type == .framework) continue;
265 if (std.mem.eql(u8, &mod.id, &zigmod.Module.ROOT)) {
266 try w.writeAll(" pub const _root = \"\";\n");
267 continue;
268 }
269 if (std.mem.eql(u8, mod.clean_path, "../..")) {
270 const cwd_realpath = try nfs.cwd().realpathAlloc(alloc, ".");
271 try w.print(" pub const _{s} = \"{}\";\n", .{ mod.short_id(), u.altStringEscape(cwd_realpath) });
272 continue;
273 }
274 try w.print(" pub const _{s} = cache ++ \"/{}\";\n", .{ mod.short_id(), u.altStringEscape(mod.clean_path) });
275 }
276}
277
278fn print_deps(w: nfs.File, m: zigmod.Module) !void {
279 try w.writeAll("&[_]*const Package{\n");
280 for (m.deps) |d| {
281 if (d.main.len == 0) {
282 continue;
283 }
284 if (d.for_build) {
285 continue;
286 }
287 try w.print(" &package_data._{s},\n", .{d.id[0..12]});
288 }
289 try w.writeAll("}");
290}
291
292fn print_pkg_data_to(w: nfs.File, notdone: *std.array_list.Managed(zigmod.Module), done: *std.array_list.Managed(zigmod.Module)) !void {
293 var len: usize = notdone.items.len;
294 while (notdone.items.len > 0) {
295 for (notdone.items, 0..) |mod, i| {
296 if (contains_all(mod.deps, done.items)) {
297 try w.print(
298 \\ pub const _{s} = Package{{
299 \\ .id = "{s}",
300 \\ .directory = dirs._{s},
301 \\
302 , .{
303 mod.short_id(),
304 mod.short_id(),
305 mod.short_id(),
306 });
307 if (mod.main.len > 0 and !std.mem.eql(u8, &mod.id, &zigmod.Module.ROOT)) {
308 try w.print(
309 \\ .import = .{{ "{s}", .{{ .cwd_relative = dirs._{s} ++ "/{s}" }} }},
310 \\
311 , .{
312 mod.name,
313 mod.short_id(),
314 mod.main,
315 });
316 }
317 {
318 try w.writeAll(" .dependencies =");
319 try w.writeAll(" &.{");
320 for (mod.deps, 0..) |moddep, j| {
321 if (moddep.type == .system_lib) continue;
322 if (moddep.type == .framework) continue;
323 try w.print(" &_{s}", .{moddep.id[0..12]});
324 if (j != mod.deps.len - 1) try w.writeAll(",");
325 }
326 try w.writeAll(" },\n");
327 }
328 if (mod.c_include_dirs.len > 0) {
329 try w.writeAll(" .c_include_dirs = &.{");
330 for (mod.c_include_dirs, 0..) |item, j| {
331 try w.print(" \"{}\"", .{u.altStringEscape(item)});
332 if (j != mod.c_include_dirs.len - 1) try w.writeAll(",");
333 }
334 try w.writeAll(" },\n");
335 }
336 if (mod.c_source_files.len > 0) {
337 try w.writeAll(" .c_source_files = &.{");
338 for (mod.c_source_files, 0..) |item, j| {
339 try w.print(" \"{}\"", .{u.altStringEscape(item)});
340 if (j != mod.c_source_files.len - 1) try w.writeAll(",");
341 }
342 try w.writeAll(" },\n");
343 }
344 if (mod.c_source_flags.len > 0) {
345 try w.writeAll(" .c_source_flags = &.{");
346 for (mod.c_source_flags, 0..) |item, j| {
347 try w.print(" \"{}\"", .{u.altStringEscape(item)});
348 if (j != mod.c_source_flags.len - 1) try w.writeAll(",");
349 }
350 try w.writeAll(" },\n");
351 }
352 if (mod.has_syslib_deps()) {
353 try w.writeAll(" .system_libs = &.{");
354 for (mod.deps, 0..) |item, j| {
355 if (!(item.type == .system_lib)) continue;
356 try w.print(" \"{}\"", .{u.altStringEscape(item.name)});
357 if (j != mod.deps.len - 1) try w.writeAll(",");
358 }
359 try w.writeAll(" },\n");
360 }
361 if (mod.has_framework_deps()) {
362 try w.writeAll(" .frameworks = &.{");
363 for (mod.deps, 0..) |item, j| {
364 if (!(item.type == .system_lib)) continue;
365 try w.print(" \"{}\"", .{u.altStringEscape(item.name)});
366 if (j != mod.deps.len - 1) try w.writeAll(",");
367 }
368 try w.writeAll(" },\n");
369 }
370 try w.writeAll(" };\n");
371
372 try done.append(mod);
373 _ = notdone.orderedRemove(i);
374 break;
375 }
376 }
377 if (notdone.items.len == len) {
378 u.fail("notdone still has {d} items", .{len});
379 }
380 len = notdone.items.len;
381 }
382}
383
384/// returns if all of the zig modules in needles are in haystack
385fn contains_all(needles: []zigmod.Module, haystack: []const zigmod.Module) bool {
386 for (needles) |item| {
387 if (item.main.len > 0 and !extras.containsAggregate(zigmod.Module, haystack, item)) {
388 return false;
389 }
390 }
391 return true;
392}
393
394fn print_pkgs(alloc: std.mem.Allocator, w: nfs.File, m: zigmod.Module) !void {
395 try w.writeAll("struct {\n");
396 for (m.deps) |d| {
397 if (d.main.len == 0) {
398 continue;
399 }
400 if (d.for_build) {
401 continue;
402 }
403 const ident = try zig_name_from_pkg_name(alloc, d.name);
404 try w.print(" pub const {s} = &package_data._{s};\n", .{ ident, d.id[0..12] });
405 }
406 try w.writeAll("}");
407}
408
409fn print_imports(alloc: std.mem.Allocator, w: nfs.File, m: zigmod.Module, path: string) !void {
410 for (m.deps) |d| {
411 if (d.main.len == 0) {
412 continue;
413 }
414 if (!d.for_build) {
415 continue;
416 }
417 const ident = try zig_name_from_pkg_name(alloc, d.name);
418 try w.print(" pub const {s} = @import(\"{}/{}/{s}\");\n", .{ ident, u.altStringEscape(path), u.altStringEscape(d.clean_path), d.main });
419 }
420}
421
422fn zig_name_from_pkg_name(alloc: std.mem.Allocator, name: string) !string {
423 var legal = name;
424 legal = try std.mem.replaceOwned(u8, alloc, legal, "-", "_");
425 legal = try std.mem.replaceOwned(u8, alloc, legal, "/", "_");
426 legal = try std.mem.replaceOwned(u8, alloc, legal, ".", "_");
427 return legal;
428}
src/cmd/install.zig+19
...@@ -68,6 +68,25 @@ pub fn execute(self_name: []const u8, args: [][:0]u8) !void {...@@ -68,6 +68,25 @@ pub fn execute(self_name: []const u8, args: [][:0]u8) !void {
68 const zigversion = try nio.fmt.allocPrint(gpa, "{s}", .{u.altSemanticVersion(zigversion_sv)});68 const zigversion = try nio.fmt.allocPrint(gpa, "{s}", .{u.altSemanticVersion(zigversion_sv)});
69 const zigpath = try std.fs.path.joinZ(gpa, &.{ datapath, "zig", zigversion, "zig" });69 const zigpath = try std.fs.path.joinZ(gpa, &.{ datapath, "zig", zigversion, "zig" });
7070
71 // regen deps.zig with the repo's zig version
72 var fetch_options = common.CollectOptions{
73 .log = true,
74 .update = false,
75 .lock = try common.parse_lockfile(gpa, moddir),
76 .alloc = gpa,
77 };
78 const fetch_top_module = try common.collect_deps_deep(cachepath, moddir, &fetch_options);
79 var fetch_list = std.array_list.Managed(zigmod.Module).init(gpa);
80 try common.collect_pkgs(fetch_top_module, &fetch_list);
81 const V = struct { major: u32, minor: u32 };
82 const version_sct: V = .{ .major = @intCast(zigversion_sv.major), .minor = @intCast(zigversion_sv.minor) };
83 const version_int: u32 = (version_sct.major << 2) | version_sct.minor;
84 switch (version_int) {
85 0x0000_000e => try @import("./fetch.0.14.zig").create_depszig(gpa, cachepath, moddir, fetch_top_module, &fetch_list),
86 0x0000_000f => {}, // that's us, zigmod is already 0.15.2
87 else => u.fail("zig {d}.{d} unimplemented", .{ version_sct.major, version_sct.minor }),
88 }
89
71 // zig build90 // zig build
72 const argv: []const string = &.{91 const argv: []const string = &.{
73 zigpath, "build",92 zigpath, "build",