authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2022-09-17 13:13:34 -07:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2022-09-17 13:13:34 -07:00
logae6cf5fbe23993d09f9e99ed80fc6717f25f6317
tree958ad54e0bd3d963ead7fab220d47c1dec749de0
parentf12542b93daa5ece650a79b08766594dd25692a0

cmd: add the `generate` command


5 files changed, 435 insertions(+), 0 deletions(-)

docs/commands/generate.md created+33
...@@ -0,0 +1,33 @@
1## `generate` command
2```
3zigmod generate
4```
5
6- This command takes no parameters and will generate a `deps.zig` in the root of your project. This is the file that you will then import into your `build.zig` to automatically add all the necessary packages and (any) C code that may be in your dependencies.
7- This behavior is similar to [`zigmod fetch`](./fetch.md) but it does the fetching of dependencies in `deps.zig` itself to enable your users to only need `zig build` (and optionally `git`).
8- You as an program author will still need `zigmod` installed during development, but consumers of your app can get started with only a `git clone` and `zig build`!
9- To this end, a `deps.zig` made by running `zigmod generate` *will* want to be checked into source control. Don't forget to remove it from `.gitignore`.
10- The version of dependencies fetched by `deps.zig` from this command is based on your `zigmod.lock` so no surprise breaks from users.
11
12For a full reference on the fields available in `deps.zig` you can check [here](../deps.zig.md).
13
14### Adding `deps.zig` to your `build.zig`
15```diff
16 const std = @import("std");
17+const deps = @import("./deps.zig");
18
19 pub fn build(b: *std.build.Builder) void {
20 const target = b.standardTargetOptions(.{});
21
22 const mode = b.standardReleaseOptions();
23
24 const exe = b.addExecutable("hello", "src/main.zig");
25 exe.setTarget(target);
26 exe.setBuildMode(mode);
27+ deps.addAllTo(exe);
28 exe.install();
29```
30
31If you don't want Zigmod to handle adding packages to your project and only do resource fetching then `deps.fetch(exe)` may be called independently of `deps.addAllTo(exe)`.
32
33`deps.addAllTo(exe)` will call `deps.fetch(exe)` inside of it, so as to be compatible with `deps.zig` generated by `zigmod fetch`.
docs/deps.zig.md+6
...@@ -4,6 +4,12 @@ This file is generated by [`zigmod fetch`](commands/fetch.md) or [`zigmod ci`](c...@@ -4,6 +4,12 @@ This file is generated by [`zigmod fetch`](commands/fetch.md) or [`zigmod ci`](c
4### `cache`4### `cache`
5- Type: `[]const u8`5- Type: `[]const u8`
6The relative path root to which all dependency sources are saved. Currently always `.zigmod/deps`.6The relative path root to which all dependency sources are saved. Currently always `.zigmod/deps`.
7> Note: only available when made with `zigmod fetch`.
8
9### `fetch`
10- Type: `pub fn (exe: *std.build.LibExeObjStep) void`
11A helper function to automatically pull in dependencies, purely from the `zig build` system without the need to have `zigmod` installed.
12> Note: only available when made with `zigmod generate`.
713
8### `addAllTo`14### `addAllTo`
9- Type: `pub fn (exe: *std.build.LibExeObjStep) void`15- Type: `pub fn (exe: *std.build.LibExeObjStep) void`
src/cmd/generate.zig created+375
...@@ -0,0 +1,375 @@
1const std = @import("std");
2const string = []const u8;
3
4const zigmod = @import("../lib.zig");
5const u = @import("./../util/index.zig");
6const common = @import("./../common.zig");
7
8//
9//
10
11pub fn execute(args: [][]u8) !void {
12 _ = args;
13
14 //
15 const gpa = std.heap.c_allocator;
16 const cachepath = try std.fs.path.join(gpa, &.{ ".zigmod", "deps" });
17 const dir = std.fs.cwd();
18
19 var options = common.CollectOptions{
20 .log = false,
21 .update = false,
22 .alloc = gpa,
23 };
24 const top_module = try common.collect_deps_deep(cachepath, dir, &options);
25
26 var list = std.ArrayList(zigmod.Module).init(gpa);
27 try common.collect_pkgs(top_module, &list);
28
29 std.sort.sort(zigmod.Module, list.items, {}, zigmod.Module.lessThan);
30
31 try create_depszig(gpa, cachepath, dir, top_module, list.items);
32}
33
34pub fn create_depszig(alloc: std.mem.Allocator, cachepath: string, dir: std.fs.Dir, top_module: zigmod.Module, list: []const zigmod.Module) !void {
35 const f = try dir.createFile("deps.zig", .{});
36 defer f.close();
37
38 const w = f.writer();
39 try w.writeAll("// zig fmt: off\n");
40 try w.writeAll("const std = @import(\"std\");\n");
41 try w.writeAll("const builtin = @import(\"builtin\");\n");
42 try w.writeAll("const Pkg = std.build.Pkg;\n");
43 try w.writeAll("const string = []const u8;\n");
44 try w.writeAll("\n");
45 try w.writeAll(
46 \\pub const GitExactStep = struct {
47 \\ step: std.build.Step,
48 \\ builder: *std.build.Builder,
49 \\ url: string,
50 \\ commit: string,
51 \\
52 \\ pub fn create(b: *std.build.Builder, url: string, commit: string) *GitExactStep {
53 \\ var result = b.allocator.create(GitExactStep) catch @panic("memory");
54 \\ result.* = GitExactStep{
55 \\ .step = std.build.Step.init(.custom, b.fmt("git clone {s} @ {s}", .{ url, commit }), b.allocator, make),
56 \\ .builder = b,
57 \\ .url = url,
58 \\ .commit = commit,
59 \\ };
60 \\
61 \\ var urlpath = url;
62 \\ urlpath = trimPrefix(u8, urlpath, "https://");
63 \\ urlpath = trimPrefix(u8, urlpath, "git://");
64 \\ const repopath = b.fmt("{s}/zigmod/deps/git/{s}/{s}", .{ b.cache_root, urlpath, commit });
65 \\ flip(std.fs.cwd().access(repopath, .{})) catch return result;
66 \\
67 \\ var clonestep = std.build.RunStep.create(b, "clone");
68 \\ clonestep.addArgs(&.{ "git", "clone", "-q", "--progress", url, repopath });
69 \\ result.step.dependOn(&clonestep.step);
70 \\
71 \\ var checkoutstep = std.build.RunStep.create(b, "checkout");
72 \\ checkoutstep.addArgs(&.{ "git", "-C", repopath, "checkout", "-q", commit });
73 \\ result.step.dependOn(&checkoutstep.step);
74 \\
75 \\ return result;
76 \\ }
77 \\
78 \\ fn make(step: *std.build.Step) !void {
79 \\ _ = step;
80 \\ }
81 \\};
82 \\
83 \\pub fn fetch(exe: *std.build.LibExeObjStep) void {
84 \\ const b = exe.builder;
85 \\ inline for (comptime std.meta.declarations(package_data)) |decl| {
86 \\ const path = &@field(package_data, decl.name).entry;
87 \\ const root = if (@field(package_data, decl.name).store) |_| b.cache_root else ".";
88 \\ if (path.* != null) path.* = b.fmt("{s}/zigmod/deps{s}", .{ root, path.*.? });
89 \\ }
90 \\
91 );
92 for (list) |module| {
93 switch (module.type) {
94 .local => {},
95 .system_lib => {},
96 .framework => {},
97 .git => try w.print(" exe.step.dependOn(&GitExactStep.create(b, \"{s}\", \"{s}\").step);\n", .{ module.dep.?.path, try module.pin(alloc, cachepath) }),
98 .hg => @panic("TODO"),
99 .http => @panic("TODO"),
100 }
101 }
102 try w.writeAll(
103 \\}
104 \\
105 \\fn trimPrefix(comptime T: type, haystack: []const T, needle: []const T) []const T {
106 \\ if (std.mem.startsWith(T, haystack, needle)) {
107 \\ return haystack[needle.len .. haystack.len];
108 \\ }
109 \\ return haystack;
110 \\}
111 \\
112 \\fn flip(foo: anytype) !void {
113 \\ _ = foo catch return;
114 \\ return error.ExpectedError;
115 \\}
116 \\
117 \\pub fn addAllTo(exe: *std.build.LibExeObjStep) void {
118 \\ checkMinZig(builtin.zig_version, exe);
119 \\ fetch(exe);
120 \\ const b = exe.builder;
121 \\ @setEvalBranchQuota(1_000_000);
122 \\ for (packages) |pkg| {
123 \\ exe.addPackage(pkg.zp(b));
124 \\ }
125 \\ var llc = false;
126 \\ var vcpkg = false;
127 \\ inline for (comptime std.meta.declarations(package_data)) |decl| {
128 \\ const pkg = @as(Package, @field(package_data, decl.name));
129 \\ const root = if (pkg.store) |st| b.fmt("{s}/zigmod/deps/{s}", .{ b.cache_root, st }) else ".";
130 \\ for (pkg.system_libs) |item| {
131 \\ exe.linkSystemLibrary(item);
132 \\ llc = true;
133 \\ }
134 \\ for (pkg.frameworks) |item| {
135 \\ if (!builtin.target.isDarwin()) @panic(exe.builder.fmt("a dependency is attempting to link to the framework {s}, which is only possible under Darwin", .{item}));
136 \\ exe.linkFramework(item);
137 \\ llc = true;
138 \\ }
139 \\ for (pkg.c_include_dirs) |item| {
140 \\ exe.addIncludeDir(b.fmt("{s}/{s}", .{ root, item }));
141 \\ llc = true;
142 \\ }
143 \\ for (pkg.c_source_files) |item| {
144 \\ exe.addCSourceFile(b.fmt("{s}/{s}", .{ root, item }), pkg.c_source_flags);
145 \\ llc = true;
146 \\ }
147 \\ vcpkg = vcpkg or pkg.vcpkg;
148 \\ }
149 \\ if (llc) exe.linkLibC();
150 \\ if (builtin.os.tag == .windows and vcpkg) exe.addVcpkgPaths(.static) catch |err| @panic(@errorName(err));
151 \\}
152 \\
153 \\pub const Package = struct {
154 \\ name: string = "",
155 \\ entry: ?string = null,
156 \\ store: ?string = null,
157 \\ deps: []const *Package = &.{},
158 \\ c_include_dirs: []const string = &.{},
159 \\ c_source_files: []const string = &.{},
160 \\ c_source_flags: []const string = &.{},
161 \\ system_libs: []const string = &.{},
162 \\ frameworks: []const string = &.{},
163 \\ vcpkg: bool = false,
164 \\
165 \\ pub fn zp(self: *const Package, b: *std.build.Builder) Pkg {
166 \\ var temp: [100]Pkg = undefined;
167 \\ for (self.deps) |item, i| {
168 \\ temp[i] = item.zp(b);
169 \\ }
170 \\ return .{
171 \\ .name = self.name,
172 \\ .source = .{ .path = self.entry.? },
173 \\ .dependencies = b.allocator.dupe(Pkg, temp[0..self.deps.len]) catch @panic("oom"),
174 \\ };
175 \\ }
176 \\};
177 \\
178 \\
179 );
180
181 try w.print(
182 \\fn checkMinZig(current: std.SemanticVersion, exe: *std.build.LibExeObjStep) void {{
183 \\ const min = std.SemanticVersion.parse("{?}") catch return;
184 \\ if (current.order(min).compare(.lt)) @panic(exe.builder.fmt("Your Zig version v{{}} does not meet the minimum build requirement of v{{}}", .{{current, min}}));
185 \\}}
186 \\
187 \\
188 , .{top_module.minZigVersion()});
189
190 try w.writeAll("pub const package_data = struct {\n");
191 var duped = std.ArrayList(zigmod.Module).init(alloc);
192 for (list) |mod| {
193 if (mod.type == .system_lib or mod.type == .framework) {
194 continue;
195 }
196 try duped.append(mod);
197 }
198 try print_pkg_data_to(w, alloc, cachepath, &duped, &std.ArrayList(zigmod.Module).init(alloc));
199 try w.writeAll("};\n\n");
200
201 try w.writeAll("pub const packages = ");
202 try print_deps(w, top_module);
203 try w.writeAll(";\n\n");
204
205 try w.writeAll("pub const pkgs = ");
206 try print_pkgs(alloc, w, top_module);
207 try w.writeAll(";\n\n");
208
209 try w.writeAll("pub const imports = struct {\n");
210 try print_imports(alloc, w, top_module, cachepath);
211 try w.writeAll("};\n");
212}
213
214fn print_dirs(w: std.fs.File.Writer, list: []const zigmod.Module) !void {
215 for (list) |mod| {
216 if (mod.type == .system_lib or mod.type == .framework) continue;
217 if (std.mem.eql(u8, mod.id, "root")) {
218 try w.writeAll(" pub const _root = \"\";\n");
219 continue;
220 }
221 try w.print(" pub const _{s} = cache ++ \"/{}\";\n", .{ mod.short_id(), std.zig.fmtEscapes(mod.clean_path) });
222 }
223}
224
225fn print_deps(w: std.fs.File.Writer, m: zigmod.Module) !void {
226 try w.writeAll("[_]*const Package{\n");
227 for (m.deps) |d| {
228 if (d.main.len == 0) {
229 continue;
230 }
231 if (d.for_build) {
232 continue;
233 }
234 try w.print(" &package_data._{s},\n", .{d.id[0..12]});
235 }
236 try w.writeAll("}");
237}
238
239fn print_pkg_data_to(w: std.fs.File.Writer, alloc: std.mem.Allocator, cachepath: string, notdone: *std.ArrayList(zigmod.Module), done: *std.ArrayList(zigmod.Module)) !void {
240 var len: usize = notdone.items.len;
241 while (notdone.items.len > 0) {
242 for (notdone.items) |mod, i| {
243 if (contains_all(mod.deps, done.items)) {
244 try w.print(
245 \\ pub var _{s} = Package{{
246 \\
247 , .{
248 mod.short_id(),
249 });
250 var 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;
251 switch (mod.type) {
252 .system_lib, .framework => {},
253 .local => {},
254 .git => try w.print(" .store = \"/{}/{s}\",\n", .{ std.zig.fmtEscapes(fixed_path), try mod.pin(alloc, cachepath) }),
255 .hg => @panic("TODO"),
256 .http => @panic("TODO"),
257 }
258 if (mod.main.len > 0 and !std.mem.eql(u8, mod.id, "root")) {
259 try w.print(" .name = \"{s}\",\n", .{mod.name});
260 try w.print(" .entry = \"/{}/{s}/{s}\",\n", .{ std.zig.fmtEscapes(fixed_path), try mod.pin(alloc, cachepath), mod.main });
261
262 if (!mod.has_no_zig_deps()) {
263 try w.writeAll(" .deps = &[_]*Package{");
264 for (mod.deps) |moddep, j| {
265 if (moddep.main.len == 0) continue;
266 try w.print(" &_{s}", .{moddep.id[0..12]});
267 if (j != mod.deps.len - 1) try w.writeAll(",");
268 }
269 try w.writeAll(" },\n");
270 }
271 }
272 if (mod.c_include_dirs.len > 0) {
273 try w.writeAll(" .c_include_dirs = &.{");
274 for (mod.c_include_dirs) |item, j| {
275 try w.print(" \"{}\"", .{std.zig.fmtEscapes(item)});
276 if (j != mod.c_include_dirs.len - 1) try w.writeAll(",");
277 }
278 try w.writeAll(" },\n");
279 }
280 if (mod.c_source_files.len > 0) {
281 try w.writeAll(" .c_source_files = &.{");
282 for (mod.c_source_files) |item, j| {
283 try w.print(" \"{}\"", .{std.zig.fmtEscapes(item)});
284 if (j != mod.c_source_files.len - 1) try w.writeAll(",");
285 }
286 try w.writeAll(" },\n");
287 }
288 if (mod.c_source_flags.len > 0) {
289 try w.writeAll(" .c_source_flags = &.{");
290 for (mod.c_source_flags) |item, j| {
291 try w.print(" \"{}\"", .{std.zig.fmtEscapes(item)});
292 if (j != mod.c_source_flags.len - 1) try w.writeAll(",");
293 }
294 try w.writeAll(" },\n");
295 }
296 if (mod.has_syslib_deps()) {
297 try w.writeAll(" .system_libs = &.{");
298 for (mod.deps) |item, j| {
299 if (!(item.type == .system_lib)) continue;
300 try w.print(" \"{}\"", .{std.zig.fmtEscapes(item.name)});
301 if (j != mod.deps.len - 1) try w.writeAll(",");
302 }
303 try w.writeAll(" },\n");
304 }
305 if (mod.has_framework_deps()) {
306 try w.writeAll(" .frameworks = &.{");
307 for (mod.deps) |item, j| {
308 if (!(item.type == .system_lib)) continue;
309 try w.print(" \"{}\"", .{std.zig.fmtEscapes(item.name)});
310 if (j != mod.deps.len - 1) try w.writeAll(",");
311 }
312 try w.writeAll(" },\n");
313 }
314 if (mod.vcpkg) {
315 try w.writeAll(" .vcpkg = true,\n");
316 }
317 try w.writeAll(" };\n");
318
319 try done.append(mod);
320 _ = notdone.orderedRemove(i);
321 break;
322 }
323 }
324 if (notdone.items.len == len) {
325 u.fail("notdone still has {d} items", .{len});
326 }
327 len = notdone.items.len;
328 }
329}
330
331/// returns if all of the zig modules in needles are in haystack
332fn contains_all(needles: []zigmod.Module, haystack: []const zigmod.Module) bool {
333 for (needles) |item| {
334 if (item.main.len > 0 and !u.list_contains_gen(zigmod.Module, haystack, item)) {
335 return false;
336 }
337 }
338 return true;
339}
340
341fn print_pkgs(alloc: std.mem.Allocator, w: std.fs.File.Writer, m: zigmod.Module) !void {
342 try w.writeAll("struct {\n");
343 for (m.deps) |d| {
344 if (d.main.len == 0) {
345 continue;
346 }
347 if (d.for_build) {
348 continue;
349 }
350 const ident = try zig_name_from_pkg_name(alloc, d.name);
351 try w.print(" pub const {s} = &package_data._{s};\n", .{ ident, d.id[0..12] });
352 }
353 try w.writeAll("}");
354}
355
356fn print_imports(alloc: std.mem.Allocator, w: std.fs.File.Writer, m: zigmod.Module, path: string) !void {
357 for (m.deps) |d| {
358 if (d.main.len == 0) {
359 continue;
360 }
361 if (!d.for_build) {
362 continue;
363 }
364 const ident = try zig_name_from_pkg_name(alloc, d.name);
365 try w.print(" pub const {s} = @import(\"{}/{}/{s}\");\n", .{ ident, std.zig.fmtEscapes(path), std.zig.fmtEscapes(d.clean_path), d.main });
366 }
367}
368
369fn zig_name_from_pkg_name(alloc: std.mem.Allocator, name: string) !string {
370 var legal = name;
371 legal = try std.mem.replaceOwned(u8, alloc, legal, "-", "_");
372 legal = try std.mem.replaceOwned(u8, alloc, legal, "/", "_");
373 legal = try std.mem.replaceOwned(u8, alloc, legal, ".", "_");
374 return legal;
375}
src/lib.zig+1
...@@ -13,6 +13,7 @@ pub const commands = struct {...@@ -13,6 +13,7 @@ pub const commands = struct {
13 pub const zpm = @import("./cmd/zpm.zig");13 pub const zpm = @import("./cmd/zpm.zig");
14 pub const license = @import("./cmd/license.zig");14 pub const license = @import("./cmd/license.zig");
15 pub const aq = @import("./cmd/aq.zig");15 pub const aq = @import("./cmd/aq.zig");
16 pub const generate = @import("./cmd/generate.zig");
16};17};
1718
18pub fn init() !void {19pub fn init() !void {
src/util/module.zig+20
...@@ -157,4 +157,24 @@ pub const Module = struct {...@@ -157,4 +157,24 @@ pub const Module = struct {
157 }157 }
158 return false;158 return false;
159 }159 }
160
161 pub fn pin(self: Module, alloc: std.mem.Allocator, cachepath: string) !string {
162 return switch (self.type) {
163 .local => "",
164 .system_lib => "",
165 .framework => "",
166 else => |sub| {
167 var cdir = try std.fs.cwd().openDir(cachepath, .{});
168 defer cdir.close();
169 var mdir = try cdir.openDir(self.clean_path, .{});
170 defer mdir.close();
171 return switch (sub) {
172 .local, .system_lib, .framework => unreachable,
173 .git => try u.git_rev_HEAD(alloc, mdir),
174 .hg => @panic("TODO"),
175 .http => @panic("TODO"),
176 };
177 },
178 };
179 }
160};180};