1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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,
});
}
}
|