1const std = @import("std");
2const string = []const u8;
3const extras = @import("extras");
4const nfs = @import("nfs");
5
6const zigmod = @import("../lib.zig");
7const u = @import("./../util/funcs.zig");
8const common = @import("./../common.zig");
9
10//
11//
12
13pub fn execute(self_name: []const u8, args: []const [:0]const u8) !void {
14 _ = self_name;
15
16 //
17 const gpa = std.heap.c_allocator;
18 const cachepath = try u.find_cachepath();
19 const dir = nfs.cwd();
20 const should_lock = args.len >= 1 and std.mem.eql(u8, args[0], "--locked");
21
22 var options = common.CollectOptions{
23 .log = false,
24 .update = false,
25 .alloc = gpa,
26 .lock = if (should_lock) try common.parse_lockfile(gpa, dir) else null,
27 };
28 const top_module = try common.collect_deps_deep(cachepath, dir, &options);
29
30 var list = std.array_list.Managed(zigmod.Module).init(gpa);
31 try common.collect_pkgs(top_module, &list);
32
33 std.mem.sort(zigmod.Module, list.items, {}, zigmod.Module.lessThan);
34
35 try create_depszig(gpa, cachepath, dir, top_module, list.items, &options);
36}
37
38pub fn create_depszig(alloc: std.mem.Allocator, cachepath: [:0]const u8, dir: nfs.Dir, top_module: zigmod.Module, list: []const zigmod.Module, options: *common.CollectOptions) !void {
39 const f = try dir.createFile("deps.zig", .{});
40 defer f.close();
41
42 const w = f;
43 try w.writeAll("// zig fmt: off\n");
44 try w.writeAll("const std = @import(\"std\");\n");
45 try w.writeAll("const builtin = @import(\"builtin\");\n");
46 try w.writeAll("const string = []const u8;\n");
47 try w.writeAll("\n");
48 try w.writeAll(
49 \\pub const GitExactStep = struct {
50 \\ step: std.Build.Step,
51 \\ builder: *std.Build,
52 \\ url: string,
53 \\ commit: string,
54 \\
55 \\ pub fn create(b: *std.Build, url: string, commit: string) *GitExactStep {
56 \\ var result = b.allocator.create(GitExactStep) catch @panic("memory");
57 \\ result.* = GitExactStep{
58 \\ .step = std.Build.Step.init(.{
59 \\ .id = .custom,
60 \\ .name = b.fmt("git clone {s} @ {s}", .{ url, commit }),
61 \\ .owner = b,
62 \\ .makeFn = make,
63 \\ }),
64 \\ .builder = b,
65 \\ .url = url,
66 \\ .commit = commit,
67 \\ };
68 \\
69 \\ var urlpath = url;
70 \\ urlpath = trimPrefix(u8, urlpath, "https://");
71 \\ urlpath = trimPrefix(u8, urlpath, "git://");
72 \\ const repopath = b.fmt("{s}/zigmod/deps/git/{s}/{s}", .{ b.cache_root.path.?, urlpath, commit });
73 \\ flip(std.Io.Dir.cwd().access(std.Options.debug_io, repopath, .{})) catch return result;
74 \\
75 \\ var clonestep = std.Build.Step.Run.create(b, "clone");
76 \\ clonestep.addArgs(&.{ "git", "clone", "-q", "--progress", url, repopath });
77 \\
78 \\ var checkoutstep = std.Build.Step.Run.create(b, "checkout");
79 \\ checkoutstep.addArgs(&.{ "git", "-C", repopath, "checkout", "-q", commit });
80 \\ result.step.dependOn(&checkoutstep.step);
81 \\ checkoutstep.step.dependOn(&clonestep.step);
82 // TODO rm the .git folder
83 // TODO mark folder as read-only
84 \\
85 \\ return result;
86 \\ }
87 \\
88 \\ fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) !void {
89 \\ _ = step;
90 \\ _ = options;
91 \\ }
92 \\};
93 \\
94 \\pub fn fetch(exe: *std.Build.Step.Compile) *std.Build.Step {
95 \\ const b = exe.step.owner;
96 \\ const step = b.step("fetch", "");
97 \\ inline for (comptime std.meta.declarations(package_data)) |decl| {
98 \\ const path = &@field(package_data, decl.name).entry;
99 \\ const root = if (@field(package_data, decl.name).store) |_| b.cache_root.path.? else ".";
100 \\ if (path.* != null) path.* = b.fmt("{s}/zigmod/deps{s}", .{ root, path.*.? });
101 \\ }
102 \\
103 );
104 for (list) |module| {
105 switch (module.type) {
106 .local => {},
107 .system_lib => {},
108 .framework => {},
109 .git => try w.print(" step.dependOn(&GitExactStep.create(b, \"{s}\", \"{s}\").step);\n", .{ module.dep.?.path, try module.pin(alloc, cachepath, options) }),
110 .hg => @panic("TODO"),
111 .http => @panic("TODO"),
112 }
113 }
114 try w.writeAll(
115 \\ return step;
116 \\}
117 \\
118 \\fn trimPrefix(comptime T: type, haystack: []const T, needle: []const T) []const T {
119 \\ if (std.mem.startsWith(T, haystack, needle)) {
120 \\ return haystack[needle.len .. haystack.len];
121 \\ }
122 \\ return haystack;
123 \\}
124 \\
125 \\fn flip(foo: anytype) !void {
126 \\ _ = foo catch return;
127 \\ return error.ExpectedError;
128 \\}
129 \\
130 \\pub fn addAllTo(exe: *std.Build.Step.Compile) void {
131 \\ checkMinZig(builtin.zig_version, exe);
132 \\ const fetch_step = fetch(exe);
133 \\ @setEvalBranchQuota(1_000_000);
134 \\ for (packages) |pkg| {
135 \\ const module = pkg.module(exe, fetch_step);
136 \\ exe.root_module.addImport(pkg.name, module);
137 \\ }
138 \\ // clear module memo cache so addAllTo can be called more than once in the same build.zig
139 \\ inline for (comptime std.meta.declarations(package_data)) |decl| @field(package_data, decl.name).module_memo = null;
140 \\}
141 \\
142 \\var link_lib_c = false;
143 \\pub const Package = struct {
144 \\ name: string = "",
145 \\ entry: ?string = null,
146 \\ store: ?string = null,
147 \\ deps: []const *Package = &.{},
148 \\ c_include_dirs: []const string = &.{},
149 \\ c_source_files: []const string = &.{},
150 \\ c_source_flags: []const string = &.{},
151 \\ system_libs: []const string = &.{},
152 \\ frameworks: []const string = &.{},
153 \\ module_memo: ?*std.Build.Module = null,
154 \\
155 \\ pub fn module(self: *Package, exe: *std.Build.Step.Compile, fetch_step: *std.Build.Step) *std.Build.Module {
156 \\ if (self.module_memo) |cached| {
157 \\ return cached;
158 \\ }
159 \\ const b = exe.step.owner;
160 \\
161 \\ const result = b.createModule(.{
162 \\ .target = exe.root_module.resolved_target,
163 \\ });
164 \\ const target = result.resolved_target.?.result;
165 \\ const dummy_library = b.addLibrary(.{
166 \\ .linkage = .static,
167 \\ .name = b.fmt("dummy-{s}", .{self.name}),
168 \\ .root_module = b.createModule(.{
169 \\ .target = exe.root_module.resolved_target orelse b.graph.host,
170 \\ .optimize = exe.root_module.optimize.?,
171 \\ }),
172 \\ });
173 \\ dummy_library.step.dependOn(fetch_step);
174 \\ var links: u32 = 0;
175 \\ if (self.entry) |capture| {
176 \\ result.root_source_file = .{ .cwd_relative = capture };
177 \\ }
178 \\ for (self.deps) |item| {
179 \\ const module_dep = item.module(exe, fetch_step);
180 \\ if (module_dep.root_source_file != null) {
181 \\ result.addImport(item.name, module_dep);
182 \\ }
183 \\ for (module_dep.include_dirs.items) |jtem| {
184 \\ switch (jtem) {
185 \\ .path => result.addIncludePath(jtem.path),
186 \\ .path_system, .path_after, .framework_path, .framework_path_system, .other_step, .config_header_step => {},
187 \\ .embed_path => {},
188 \\ }
189 \\ }
190 \\ }
191 \\ for (self.c_include_dirs) |item| {
192 \\ result.addIncludePath(.{ .cwd_relative = b.fmt("{s}/zigmod/deps{s}/{s}", .{ b.cache_root.path.?, self.store.?, item }) });
193 \\ dummy_library.root_module.addIncludePath(.{ .cwd_relative = b.fmt("{s}/zigmod/deps{s}/{s}", .{ b.cache_root.path.?, self.store.?, item }) });
194 \\ link_lib_c = true;
195 \\ links += 1;
196 \\ }
197 \\ for (self.c_source_files) |item| {
198 \\ dummy_library.root_module.addCSourceFile(.{ .file = .{ .cwd_relative = b.fmt("{s}/zigmod/deps{s}/{s}", .{ b.cache_root.path.?, self.store.?, item }) }, .flags = self.c_source_flags });
199 \\ links += 1;
200 \\ }
201 \\ for (self.system_libs) |item| {
202 \\ if (std.zig.target.isLibCLibName(&target, item)) continue;
203 \\ dummy_library.root_module.linkSystemLibrary(item, .{});
204 \\ links += 1;
205 \\ }
206 \\ for (self.frameworks) |item| {
207 \\ dummy_library.root_module.linkFramework(item, .{});
208 \\ links += 1;
209 \\ }
210 \\ if (links > 0) {
211 \\ dummy_library.root_module.linkSystemLibrary("c", .{});
212 \\ exe.root_module.linkLibrary(dummy_library);
213 \\ link_lib_c = true;
214 \\ }
215 \\ if (link_lib_c) {
216 \\ result.link_libc = true;
217 \\ }
218 \\ self.module_memo = result;
219 \\ return result;
220 \\ }
221 \\};
222 \\
223 \\
224 );
225
226 try w.print(
227 \\fn checkMinZig(current: std.SemanticVersion, exe: *std.Build.Step.Compile) void {{
228 \\ const min = std.SemanticVersion.parse("{?}") catch return;
229 \\ 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}}));
230 \\}}
231 \\
232 \\
233 , .{if (top_module.minZigVersion()) |sv| u.altSemanticVersion(sv) else null});
234
235 try w.writeAll("pub const package_data = struct {\n");
236 var duped = std.array_list.Managed(zigmod.Module).init(alloc);
237 var done = std.array_list.Managed(zigmod.Module).init(alloc);
238 for (list) |mod| {
239 if (mod.type == .system_lib or mod.type == .framework) {
240 continue;
241 }
242 try duped.append(mod);
243 }
244 try print_pkg_data_to(w, alloc, cachepath, &duped, &done, options);
245 try w.writeAll("};\n\n");
246
247 try w.writeAll("pub const packages = ");
248 try print_deps(w, top_module);
249 try w.writeAll(";\n\n");
250
251 try w.writeAll("pub const pkgs = ");
252 try print_pkgs(alloc, w, top_module);
253 try w.writeAll(";\n\n");
254
255 try w.writeAll("pub const imports = struct {\n");
256 try print_imports(alloc, w, top_module, cachepath);
257 try w.writeAll("};\n");
258}
259
260fn print_dirs(w: nfs.File, list: []const zigmod.Module) !void {
261 for (list) |mod| {
262 if (mod.type == .system_lib or mod.type == .framework) continue;
263 if (std.mem.eql(u8, mod.id, "root")) {
264 try w.writeAll(" pub const _root = \"\";\n");
265 continue;
266 }
267 try w.print(" pub const _{s} = cache ++ \"/{}\";\n", .{ mod.short_id(), u.altStringEscape(mod.clean_path) });
268 }
269}
270
271fn print_deps(w: nfs.File, m: zigmod.Module) !void {
272 try w.writeAll("[_]*Package{\n");
273 for (m.deps) |d| {
274 if (d.main.len == 0) {
275 continue;
276 }
277 if (d.for_build) {
278 continue;
279 }
280 try w.print(" &package_data._{s},\n", .{d.id[0..12]});
281 }
282 try w.writeAll("}");
283}
284
285fn print_pkg_data_to(w: nfs.File, alloc: std.mem.Allocator, cachepath: [:0]const u8, notdone: *std.array_list.Managed(zigmod.Module), done: *std.array_list.Managed(zigmod.Module), options: *common.CollectOptions) !void {
286 var len: usize = notdone.items.len;
287 while (notdone.items.len > 0) {
288 for (notdone.items, 0..) |mod, i| {
289 if (contains_all(mod.deps, done.items)) {
290 try w.print(
291 \\ pub var _{s} = Package{{
292 \\
293 , .{
294 mod.short_id(),
295 });
296 const fixed_path = if (std.mem.startsWith(u8, mod.clean_path, "v/")) mod.clean_path[2..std.mem.lastIndexOfScalar(u8, mod.clean_path, '/').?] else mod.clean_path;
297 switch (mod.type) {
298 .system_lib, .framework => {},
299 .local => {},
300 .git => try w.print(" .store = \"/{}/{s}\",\n", .{ u.altStringEscape(fixed_path), try mod.pin(alloc, cachepath, options) }),
301 .hg => @panic("TODO"),
302 .http => @panic("TODO"),
303 }
304 if (mod.main.len > 0 and !std.mem.eql(u8, &mod.id, &zigmod.Module.ROOT)) {
305 try w.print(" .name = \"{s}\",\n", .{mod.name});
306 try w.print(" .entry = \"/{}/{s}/{s}\",\n", .{ u.altStringEscape(fixed_path), try mod.pin(alloc, cachepath, options), mod.main });
307
308 if (mod.deps.len != 0) {
309 try w.writeAll(" .deps = &[_]*Package{");
310 for (mod.deps, 0..) |moddep, j| {
311 if (moddep.type == .system_lib or moddep.type == .framework) continue;
312 try w.print(" &_{s}", .{moddep.id[0..12]});
313 if (j != mod.deps.len - 1) try w.writeAll(",");
314 }
315 try w.writeAll(" },\n");
316 }
317 }
318 if (mod.c_include_dirs.len > 0) {
319 try w.writeAll(" .c_include_dirs = &.{");
320 for (mod.c_include_dirs, 0..) |item, j| {
321 try w.print(" \"{}\"", .{u.altStringEscape(item)});
322 if (j != mod.c_include_dirs.len - 1) try w.writeAll(",");
323 }
324 try w.writeAll(" },\n");
325 }
326 if (mod.c_source_files.len > 0) {
327 try w.writeAll(" .c_source_files = &.{");
328 for (mod.c_source_files, 0..) |item, j| {
329 try w.print(" \"{}\"", .{u.altStringEscape(item)});
330 if (j != mod.c_source_files.len - 1) try w.writeAll(",");
331 }
332 try w.writeAll(" },\n");
333 }
334 if (mod.c_source_flags.len > 0) {
335 try w.writeAll(" .c_source_flags = &.{");
336 for (mod.c_source_flags, 0..) |item, j| {
337 try w.print(" \"{}\"", .{u.altStringEscape(item)});
338 if (j != mod.c_source_flags.len - 1) try w.writeAll(",");
339 }
340 try w.writeAll(" },\n");
341 }
342 if (mod.has_syslib_deps()) {
343 try w.writeAll(" .system_libs = &.{");
344 for (mod.deps, 0..) |item, j| {
345 if (!(item.type == .system_lib)) continue;
346 try w.print(" \"{}\"", .{u.altStringEscape(item.name)});
347 if (j != mod.deps.len - 1) try w.writeAll(",");
348 }
349 try w.writeAll(" },\n");
350 }
351 if (mod.has_framework_deps()) {
352 try w.writeAll(" .frameworks = &.{");
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 try w.writeAll(" };\n");
361
362 try done.append(mod);
363 _ = notdone.orderedRemove(i);
364 break;
365 }
366 }
367 if (notdone.items.len == len) {
368 u.fail("notdone still has {d} items", .{len});
369 }
370 len = notdone.items.len;
371 }
372}
373
374/// returns if all of the zig modules in needles are in haystack
375fn contains_all(needles: []zigmod.Module, haystack: []const zigmod.Module) bool {
376 for (needles) |item| {
377 if (item.main.len > 0 and !extras.containsAggregate(zigmod.Module, haystack, item)) {
378 return false;
379 }
380 }
381 return true;
382}
383
384fn print_pkgs(alloc: std.mem.Allocator, w: nfs.File, m: zigmod.Module) !void {
385 try w.writeAll("struct {\n");
386 for (m.deps) |d| {
387 if (d.main.len == 0) {
388 continue;
389 }
390 if (d.for_build) {
391 continue;
392 }
393 const ident = try zig_name_from_pkg_name(alloc, d.name);
394 try w.print(" pub const {s} = &package_data._{s};\n", .{ ident, d.id[0..12] });
395 }
396 try w.writeAll("}");
397}
398
399fn print_imports(alloc: std.mem.Allocator, w: nfs.File, m: zigmod.Module, path: string) !void {
400 for (m.deps) |d| {
401 if (d.main.len == 0) {
402 continue;
403 }
404 if (!d.for_build) {
405 continue;
406 }
407 const ident = try zig_name_from_pkg_name(alloc, d.name);
408 try w.print(" pub const {s} = @import(\"{}/{}/{s}\");\n", .{ ident, u.altStringEscape(path), u.altStringEscape(d.clean_path), d.main });
409 }
410}
411
412fn zig_name_from_pkg_name(alloc: std.mem.Allocator, name: string) !string {
413 var legal = name;
414 legal = try std.mem.replaceOwned(u8, alloc, legal, "-", "_");
415 legal = try std.mem.replaceOwned(u8, alloc, legal, "/", "_");
416 legal = try std.mem.replaceOwned(u8, alloc, legal, ".", "_");
417 return legal;
418}