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