summaryrefslogtreecommitdiff
path: root/build/gen_luarocks_embed.zig
diff options
context:
space:
mode:
Diffstat (limited to 'build/gen_luarocks_embed.zig')
-rw-r--r--build/gen_luarocks_embed.zig157
1 files changed, 157 insertions, 0 deletions
diff --git a/build/gen_luarocks_embed.zig b/build/gen_luarocks_embed.zig
new file mode 100644
index 0000000..3f85177
--- /dev/null
+++ b/build/gen_luarocks_embed.zig
@@ -0,0 +1,157 @@
+//! Build-time codegen: walk the luarocks `src/` tree and emit a Zig
+//! module that exposes every Lua file as an `@embedFile` entry, with a
+//! comptime table mapping `require`-style module names to contents.
+//!
+//! Invoked from `build.zig`:
+//!
+//! gen-luarocks-embed <src-dir> <out-file>
+//!
+//! `<src-dir>` is `luarocks_src/src` (contains `luarocks/`, `compat53/`,
+//! and `bin/`). `<out-file>` is the Zig source to write.
+//!
+//! The generated file looks like:
+//!
+//! pub const Entry = struct { name: []const u8, contents: []const u8 };
+//! pub const files: []const Entry = &.{
+//! .{ .name = "luarocks.cmd", .contents = @embedFile("...") },
+//! ...
+//! };
+//! pub const luarocks_main: []const u8 = @embedFile("...");
+//! pub const luarocks_admin_main: []const u8 = @embedFile("...");
+//!
+//! The `@embedFile` paths are absolute paths into the Zig cache. That's
+//! safe because the codegen step depends on the luarocks dependency, so
+//! the cache entry exists by the time the generated source is compiled.
+
+const std = @import("std");
+
+pub fn main(init: std.process.Init) !void {
+ const arena = init.arena.allocator();
+ const io = init.io;
+
+ var args = init.minimal.args.iterate();
+ defer args.deinit();
+ _ = args.next(); // skip program name
+ const embed_prefix = args.next() orelse return error.MissingEmbedPrefix;
+ const src_dir_arg = args.next() orelse return error.MissingSrcDir;
+ const out_path_arg = args.next() orelse return error.MissingOutPath;
+
+ // Walk src/luarocks and src/compat53 collecting *.lua files; pick
+ // up src/bin/luarocks and src/bin/luarocks-admin separately.
+ var entries: std.array_list.Managed(Entry) = .init(arena);
+ try collect(arena, io, src_dir_arg, embed_prefix, "luarocks", &entries);
+ try collect(arena, io, src_dir_arg, embed_prefix, "compat53", &entries);
+
+ // Sort for deterministic output.
+ std.mem.sort(Entry, entries.items, {}, Entry.lessThan);
+
+ const bin_luarocks = try std.fs.path.join(arena, &.{ embed_prefix, "bin/luarocks" });
+ const bin_luarocks_admin = try std.fs.path.join(arena, &.{ embed_prefix, "bin/luarocks-admin" });
+
+ var out_file = try std.Io.Dir.cwd().createFile(io, out_path_arg, .{});
+ defer out_file.close(io);
+ var buf: [4096]u8 = undefined;
+ var writer = out_file.writer(io, &buf);
+ const w = &writer.interface;
+
+ try w.writeAll(
+ \\//! Auto-generated. Do not edit. See build/gen_luarocks_embed.zig.
+ \\
+ \\pub const Entry = struct {
+ \\ /// Lua `require` path, e.g. "luarocks.core.cfg".
+ \\ name: []const u8,
+ \\ /// File contents (Lua source).
+ \\ contents: []const u8,
+ \\};
+ \\
+ \\pub const files: []const Entry = &.{
+ \\
+ );
+
+ for (entries.items) |e| {
+ try w.print(
+ " .{{ .name = \"{s}\", .contents = @embedFile(\"{s}\") }},\n",
+ .{ e.require_name, e.embed_path },
+ );
+ }
+
+ try w.writeAll("};\n\n");
+ try w.print(
+ "pub const luarocks_main: []const u8 = @embedFile(\"{s}\");\n",
+ .{bin_luarocks},
+ );
+ try w.print(
+ "pub const luarocks_admin_main: []const u8 = @embedFile(\"{s}\");\n",
+ .{bin_luarocks_admin},
+ );
+
+ try w.flush();
+}
+
+const Entry = struct {
+ require_name: []const u8,
+ embed_path: []const u8,
+
+ fn lessThan(_: void, a: Entry, b: Entry) bool {
+ return std.mem.lessThan(u8, a.require_name, b.require_name);
+ }
+};
+
+/// Walk `<root>/<subdir>` recursively, collecting every `.lua` file. The
+/// `require` name is computed as the path relative to `<root>` with `/`
+/// replaced by `.` and `.lua` stripped, then `/init.lua` collapsed to
+/// the parent directory.
+fn collect(
+ arena: std.mem.Allocator,
+ io: std.Io,
+ root_abs: []const u8,
+ embed_prefix: []const u8,
+ subdir: []const u8,
+ out: *std.array_list.Managed(Entry),
+) !void {
+ const start_abs = try std.fs.path.join(arena, &.{ root_abs, subdir });
+ var dir = std.Io.Dir.cwd().openDir(io, start_abs, .{ .iterate = true }) catch |err| switch (err) {
+ error.FileNotFound => return,
+ else => return err,
+ };
+ defer dir.close(io);
+
+ var walker = try dir.walk(arena);
+ defer walker.deinit();
+
+ while (try walker.next(io)) |entry| {
+ if (entry.kind != .file) continue;
+ if (!std.mem.endsWith(u8, entry.basename, ".lua")) continue;
+
+ // `embed_path` lives under the staged tree (`<embed_prefix>/...`)
+ // so `@embedFile` can resolve it relative to the generated zig.
+ const embed_path = try std.fs.path.join(
+ arena,
+ &.{ embed_prefix, subdir, entry.path },
+ );
+ const rel = try std.mem.concat(arena, u8, &.{ subdir, "/", entry.path });
+
+ // Compute `require` name: drop ".lua", convert `/` to `.`.
+ //
+ // We deliberately do NOT collapse `/init.lua` into the parent
+ // module name. Lua's stock path searcher checks both
+ // `foo.lua` and `foo/init.lua` when you `require("foo")`, so
+ // the on-disk layout would resolve fine without our help —
+ // and luarocks's own tree contains both `luarocks/cmd.lua`
+ // (the package) and `luarocks/cmd/init.lua` (the subcommand
+ // module named `luarocks.cmd.init`). Collapsing would alias
+ // them onto one name. The embedded searcher serves whichever
+ // name appears literally in the source tree; the standard
+ // searcher behavior emerges from cmd.lua being a sibling.
+ const stem = rel[0 .. rel.len - ".lua".len];
+ const require_name = try arena.alloc(u8, stem.len);
+ for (stem, 0..) |ch, i| {
+ require_name[i] = if (ch == '/') '.' else ch;
+ }
+
+ try out.append(.{
+ .require_name = require_name,
+ .embed_path = embed_path,
+ });
+ }
+}