1const std = @import("std");
2const string = []const u8;
3const builtin = @import("builtin");
4const ansi = @import("ansi");
5const extras = @import("extras");
6const nio = @import("nio");
7const nfs = @import("nfs");
8
9const zigmod = @import("./lib.zig");
10const u = @import("./util/funcs.zig");
11
12const gpa = std.heap.c_allocator;
13
14//
15//
16
17pub const CollectOptions = struct {
18 log: bool,
19 update: bool,
20 lock: ?[]const [4]string = null,
21 alloc: std.mem.Allocator,
22 already_fetched: *std.array_list.Managed(string) = undefined,
23
24 pub fn init(self: *CollectOptions) !void {
25 self.already_fetched = try self.alloc.create(std.array_list.Managed(string));
26 self.already_fetched.* = std.array_list.Managed(string).init(self.alloc);
27 }
28};
29
30pub fn collect_deps_deep(cachepath: [:0]const u8, mdir: nfs.Dir, options: *CollectOptions) !zigmod.Module {
31 try nfs.cwd().makePath(cachepath);
32
33 const m = try zigmod.ModFile.from_dir(options.alloc, mdir, ".");
34 try options.init();
35 var moduledeps = std.array_list.Managed(zigmod.Module).init(options.alloc);
36 errdefer moduledeps.deinit();
37 if (m.root_files.len > 0) {
38 try gen_files_package(options.alloc, cachepath, mdir, m.root_files);
39 }
40 try moduledeps.append(try collect_deps(cachepath, mdir, ".", .local, options));
41 for (m.rootdeps) |*d| {
42 if (try get_module_from_dep(d, cachepath, options)) |founddep| {
43 try moduledeps.append(founddep);
44 }
45 }
46 for (m.builddeps) |*d| {
47 if (try get_module_from_dep(d, cachepath, options)) |founddep| {
48 try moduledeps.append(founddep);
49 }
50 }
51 return zigmod.Module{
52 .type = .local,
53 .id = zigmod.Module.ROOT,
54 .name = "root",
55 .main = m.main,
56 .deps = try moduledeps.toOwnedSlice(),
57 .clean_path = "",
58 .yaml = m.yaml,
59 .dep = null,
60 .min_zig_version = m.min_zig_version,
61 };
62}
63
64pub fn collect_deps(cachepath: [:0]const u8, mdir: nfs.Dir, mdir_path: string, dtype: zigmod.Dep.Type, options: *CollectOptions) anyerror!zigmod.Module {
65 try nfs.cwd().makePath(cachepath);
66
67 const m = try zigmod.ModFile.from_dir(options.alloc, mdir, mdir_path);
68 var moduledeps = std.array_list.Managed(zigmod.Module).init(options.alloc);
69 errdefer moduledeps.deinit();
70 if (m.files.len > 0) {
71 try gen_files_package(options.alloc, cachepath, mdir, m.files);
72 }
73 for (m.deps) |*d| {
74 if (try get_module_from_dep(d, cachepath, options)) |founddep| {
75 try moduledeps.append(founddep);
76 }
77 }
78 return zigmod.Module{
79 .type = dtype,
80 .id = m.id,
81 .name = m.name,
82 .main = m.main,
83 .c_include_dirs = m.c_include_dirs,
84 .c_source_flags = m.c_source_flags,
85 .c_source_files = m.c_source_files,
86 .deps = try moduledeps.toOwnedSlice(),
87 .clean_path = "../..",
88 .yaml = m.yaml,
89 .dep = null,
90 .min_zig_version = m.min_zig_version,
91 };
92}
93
94pub fn collect_pkgs(mod: zigmod.Module, list: *std.array_list.Managed(zigmod.Module)) anyerror!void {
95 if (extras.containsAggregate(zigmod.Module, list.items, mod)) {
96 return;
97 }
98 try list.append(mod);
99 for (mod.deps) |d| {
100 try collect_pkgs(d, list);
101 }
102}
103
104pub fn get_modpath(cachepath: string, d: zigmod.Dep, options: *CollectOptions) ![:0]const u8 {
105 const p = try std.fs.path.joinZ(options.alloc, &.{ cachepath, try d.clean_path(options.alloc) });
106 const pv = try std.fs.path.joinZ(options.alloc, &.{ cachepath, try d.clean_path_v(options.alloc) });
107
108 const nocache = d.type.isLocal();
109 if (!nocache and extras.containsString(options.already_fetched.items, p)) return p;
110 if (!nocache and extras.containsString(options.already_fetched.items, pv)) return pv;
111
112 if (options.log and d.type != .local) {
113 std.debug.print("fetch: {s}: {s}\n", .{ @tagName(d.type), d.path });
114 }
115 defer {
116 if (options.log and d.type != .local) {
117 std.debug.print("{s}", .{ansi.csi.CursorUp(1)});
118 std.debug.print("{s}", .{ansi.csi.EraseInLine(0)});
119 }
120 }
121 switch (d.type) {
122 .local => {
123 if (!std.mem.endsWith(u8, d.main, ".zig")) {
124 return gpa.dupeZ(u8, d.main);
125 }
126 return d.path;
127 },
128 .system_lib, .framework => {
129 // no op
130 return "";
131 },
132 .git => {
133 if (d.version.len > 0) {
134 const vers = u.parse_split(zigmod.Dep.Type.Version.Git, '-').do(d.version) catch |e| switch (e) {
135 error.IterEmpty => unreachable,
136 error.NoMemberFound => {
137 const vtype = d.version[0..std.mem.indexOfScalar(u8, d.version, '-').?];
138 u.fail("fetch: git: version type '{s}' is invalid.", .{vtype});
139 },
140 };
141 if (try nfs.cwd().existsDir(pv)) {
142 if (vers.id == .branch) {
143 if (options.update) {
144 try d.type.update(options.alloc, pv, d.path);
145 }
146 }
147 return pv;
148 }
149 try d.type.pull(options.alloc, d.path, pv);
150 if ((try u.run_cmd(options.alloc, pv, &.{ "git", "checkout", vers.string })) > 0) {
151 u.fail("fetch: git: {s}: {s} {s} does not exist", .{ d.path, @tagName(vers.id), vers.string });
152 }
153 if (builtin.os.tag != .windows and vers.id == .commit) {
154 var pvd = try nfs.cwd().openDir(pv, .{});
155 defer pvd.close();
156 try pvd.deleteTree(".git");
157 }
158 var pvd = try nfs.cwd().openDir(pv, .{});
159 defer pvd.close();
160 try setTreeReadOnly(pvd, options.alloc);
161 return pv;
162 }
163 if (!try nfs.cwd().existsDir(p)) {
164 try d.type.pull(options.alloc, d.path, p);
165 } else {
166 if (options.update) {
167 try d.type.update(options.alloc, p, d.path);
168 }
169 }
170 return p;
171 },
172 .hg => {
173 if (!try nfs.cwd().existsDir(p)) {
174 try d.type.pull(options.alloc, d.path, p);
175 } else {
176 if (options.update) {
177 try d.type.update(options.alloc, p, d.path);
178 }
179 }
180 return p;
181 },
182 .http => {
183 if (try nfs.cwd().existsDir(pv)) {
184 return pv;
185 }
186 const file_name = u.last(try u.split(options.alloc, d.path, '/')).?;
187 if (d.version.len > 0) {
188 if (try nfs.cwd().existsDir(pv)) {
189 return pv;
190 }
191 const file_path = try std.fs.path.joinZ(options.alloc, &.{ pv, file_name });
192 try d.type.pull(options.alloc, d.path, pv);
193 if (try u.validate_hash(options.alloc, d.version, file_path)) {
194 try nfs.cwd().deleteFile(file_path);
195 var pvd = try nfs.cwd().openDir(pv, .{});
196 defer pvd.close();
197 try setTreeReadOnly(pvd, options.alloc);
198 return pv;
199 }
200 try nfs.cwd().deleteTree(pv);
201 u.fail("{s} does not match hash {s}", .{ d.path, d.version });
202 return p;
203 }
204 if (try nfs.cwd().existsDir(p)) {
205 try nfs.cwd().deleteTree(p);
206 }
207 const file_path_s = try std.fs.path.resolve(options.alloc, &.{ p, file_name });
208 defer gpa.free(file_path_s);
209 const file_path = try gpa.dupeZ(u8, file_path_s);
210 try d.type.pull(options.alloc, d.path, p);
211 try nfs.cwd().deleteFile(file_path);
212 return p;
213 },
214 }
215}
216
217pub fn get_module_from_dep(d: *zigmod.Dep, cachepath: [:0]const u8, options: *CollectOptions) anyerror!?zigmod.Module {
218 if (options.lock) |lock| {
219 for (lock) |item| {
220 if (std.mem.eql(u8, item[0], try d.clean_path(options.alloc))) {
221 d.type = std.meta.stringToEnum(zigmod.Dep.Type, item[1]).?;
222 d.path = try gpa.dupeZ(u8, item[2]);
223 d.version = item[3];
224 break;
225 }
226 }
227 }
228 if (!d.is_for_this()) return null;
229 const modpath = try get_modpath(cachepath, d.*, options);
230 const moddir = if (modpath.len == 0) try nfs.cwd().openDir(cachepath, .{}) else try nfs.cwd().openDir(modpath, .{});
231
232 const nocache = d.type.isLocal();
233 if (!nocache) try options.already_fetched.append(modpath);
234
235 switch (d.type) {
236 .system_lib, .framework => {
237 return zigmod.Module{
238 .type = d.type,
239 .id = try u.do_hash(std.crypto.hash.sha3.Shake(96), d.path),
240 .name = d.path,
241 .only_os = d.only_os,
242 .except_os = d.except_os,
243 .main = "",
244 .deps = &[_]zigmod.Module{},
245 .clean_path = d.path,
246 .yaml = null,
247 .dep = d.*,
248 .for_build = d.for_build,
249 .min_zig_version = null,
250 };
251 },
252 else => {
253 var dd = collect_deps(cachepath, moddir, modpath, d.type, options) catch |e| switch (e) {
254 error.ManifestNotFound => {
255 if (d.main.len > 0 or d.c_include_dirs.len > 0 or d.c_source_files.len > 0 or d.keep) {
256 var mod_from = try zigmod.Module.from(options.alloc, d.*, cachepath, options);
257 if (d.type != .local) {
258 const new_clean_path = extras.trimPrefix(modpath, cachepath)[1..];
259 mod_from.clean_path = new_clean_path.ptr[0..new_clean_path.len :0];
260 }
261 if (mod_from.is_for_this()) return mod_from;
262 return null;
263 }
264 const moddirO = try nfs.cwd().openDir(modpath, .{});
265 const tryname = try u.detect_pkgname(options.alloc, d.name, modpath);
266 const trymain = u.detct_mainfile(options.alloc, d.main, moddirO, tryname) catch |err| switch (err) {
267 error.CantFindMain => null,
268 else => |ee| return ee,
269 };
270 if (trymain) |_| {
271 d.*.name = tryname;
272 d.*.main = trymain.?;
273 var mod_from = try zigmod.Module.from(options.alloc, d.*, cachepath, options);
274 if (d.type != .local) {
275 const new_clean_path = extras.trimPrefix(modpath, cachepath)[1..];
276 mod_from.clean_path = new_clean_path.ptr[0..new_clean_path.len :0];
277 }
278 if (mod_from.is_for_this()) return mod_from;
279 return null;
280 }
281 u.fail("no zig.mod or zigmod.yml found and no override props defined. unable to use add this dependency!", .{});
282 },
283 else => |ee| return ee,
284 };
285 dd.dep = d.*;
286 dd.for_build = d.for_build;
287 const save = dd;
288 if (d.type != .local) {
289 const new_clean_path = extras.trimPrefix(modpath, cachepath)[1..];
290 dd.clean_path = new_clean_path.ptr[0..new_clean_path.len :0];
291 }
292 if (std.mem.eql(u8, &dd.id, &zigmod.Dep.EMPTY)) dd.id = u.random_string(48);
293 if (d.name.len > 0) dd.name = d.name;
294 if (d.main.len > 0) dd.main = d.main;
295 if (d.c_include_dirs.len > 0) dd.c_include_dirs = d.c_include_dirs;
296 if (d.c_source_flags.len > 0) dd.c_source_flags = d.c_source_flags;
297 if (d.c_source_files.len > 0) dd.c_source_files = d.c_source_files;
298 if (d.only_os.len > 0) dd.only_os = d.only_os;
299 if (d.except_os.len > 0) dd.except_os = d.except_os;
300 if (d.type == .local) dd.main = try std.fs.path.join(options.alloc, &.{ d.main, save.main });
301 if (dd.is_for_this()) return dd;
302 return null;
303 },
304 }
305}
306
307pub fn gen_files_package(alloc: std.mem.Allocator, cachepath: string, mdir: nfs.Dir, dirs: []const string) !void {
308 var map = std.StringHashMap(string).init(alloc);
309 defer map.deinit();
310
311 for (dirs) |dir_path| {
312 const dir = try mdir.openDirC(dir_path, .{});
313 var walker = try dir.walk(alloc);
314 defer walker.deinit();
315 while (try walker.next()) |p| {
316 if (p.type == .DIR) {
317 continue;
318 }
319 if (p.type == .LNK) {
320 const parent = try dir.openDirC(std.fs.path.dirname(p.path).?, .{});
321 defer parent.close();
322 var buf: [1024]u8 = undefined;
323 const link_path = try dir.readlink(p.path, &buf);
324 const link_stat = try parent.statFile(link_path);
325 if (link_stat.kind() == .DIR) {
326 const link = try parent.openDir(link_path, .{});
327 var walker2 = try link.walk(alloc);
328 defer walker2.deinit();
329 while (try walker2.next()) |p2| {
330 if (p2.type == .DIR) {
331 continue;
332 }
333 const path = try nio.fmt.allocPrint(alloc, "{s}/{s}", .{ p.path, p2.path });
334 try map.put(path, try nio.fmt.allocPrint(alloc, "{s}/{s}", .{ dir_path, path }));
335 }
336 continue;
337 }
338 }
339 const path = try alloc.dupe(u8, p.path);
340 try map.put(path, try nio.fmt.allocPrint(alloc, "{s}/{s}", .{ dir_path, path }));
341 }
342 }
343
344 var dpath: string = try mdir.realpathAlloc(alloc, ".");
345 const is_not_root = std.mem.indexOf(u8, dpath, cachepath);
346 dpath = if (is_not_root) |idx| dpath[idx..] else "../..";
347
348 const fname = "files.zig";
349 const destdir = mdir;
350 const rff = try destdir.createFile(fname, .{});
351 defer rff.close();
352 var iter = map.iterator();
353 while (iter.next()) |item| {
354 try rff.print("pub const @\"/{}\" = @embedFile(\"{}\");\n", .{ u.altStringEscape(item.key_ptr.*), u.altStringEscape(item.value_ptr.*) });
355 }
356}
357
358pub fn parse_lockfile(alloc: std.mem.Allocator, dir: nfs.Dir) ![]const [4]string {
359 var list = std.array_list.Managed([4]string).init(alloc);
360 const max = std.math.maxInt(usize);
361 if (!try dir.exists("zigmod.lock")) return &[_][4]string{};
362 var f = try dir.openFile("zigmod.lock", .{});
363 defer f.close();
364 var r = nio.BufferedReader(4096, nfs.File).init(f);
365 var i: usize = 0;
366 var v: usize = 1;
367 while (try r.readUntilDelimiterOrEofAlloc(alloc, '\n', max)) |line_full| : (i += 1) {
368 const line = line_full[0 .. line_full.len - 1];
369 if (i == 0 and std.mem.eql(u8, line, "2")) {
370 v = 2;
371 continue;
372 }
373 switch (v) {
374 1 => {
375 var iter = std.mem.splitScalar(u8, line, ' ');
376 try list.append([4]string{
377 iter.next().?,
378 iter.next().?,
379 iter.next().?,
380 iter.next().?,
381 });
382 },
383 2 => {
384 var iter = std.mem.splitScalar(u8, line, ' ');
385 const asdep = zigmod.Dep{
386 .type = std.meta.stringToEnum(zigmod.Dep.Type, iter.next().?).?,
387 .path = try gpa.dupeZ(u8, iter.next().?),
388 .version = iter.next().?,
389 .id = zigmod.Dep.EMPTY,
390 .name = "",
391 .main = "",
392 .yaml = null,
393 .deps = &.{},
394 };
395 try list.append([4]string{
396 try asdep.clean_path(alloc),
397 @tagName(asdep.type),
398 asdep.path,
399 asdep.version,
400 });
401 },
402 else => {
403 u.fail("invalid zigmod.lock version: {d}", .{v});
404 },
405 }
406 }
407 return list.toOwnedSlice();
408}
409
410fn setTreeReadOnly(dir: nfs.Dir, alloc: std.mem.Allocator) !void {
411 var walker = try dir.walk(alloc);
412 defer walker.deinit();
413
414 while (try walker.next()) |entry| {
415 if (entry.type != .REG) continue;
416 var file = try dir.openFile(entry.path, .{});
417 defer file.close();
418 const stat = try file.stat();
419 var mode = stat.mode;
420 mode |= ~@as(nfs.File.Mode, 0o222);
421 try file.chmod(mode);
422 }
423}