summaryrefslogtreecommitdiff
path: root/build/gen_agent_embed.zig
diff options
context:
space:
mode:
authorT <t@tjp.lol>2026-05-27 16:34:32 -0600
committerT <t@tjp.lol>2026-05-27 16:35:09 -0600
commitf72b5793a5c7e7891e4904be73c0bc6bc038fa18 (patch)
tree5201aa7c6648d96b401dcbcb5343d4510b1c34e5 /build/gen_agent_embed.zig
parent6545cdfd8f2bc865aa06a2b5515056daf58ba111 (diff)
system agent definition scaffolding
Diffstat (limited to 'build/gen_agent_embed.zig')
-rw-r--r--build/gen_agent_embed.zig126
1 files changed, 126 insertions, 0 deletions
diff --git a/build/gen_agent_embed.zig b/build/gen_agent_embed.zig
new file mode 100644
index 0000000..b705863
--- /dev/null
+++ b/build/gen_agent_embed.zig
@@ -0,0 +1,126 @@
+//! Build-time codegen: walk the in-repo `agent/` tree and emit a Zig
+//! module that exposes every file as an `@embedFile` entry.
+//!
+//! Invoked from `build.zig`:
+//!
+//! gen-agent-embed <embed-prefix> <src-dir> <out-file>
+//!
+//! `<src-dir>` is the repo's `agent/` directory. `<embed-prefix>` is
+//! the subpath the generated zig file's neighboring `addCopyDirectory`
+//! stages the tree under; every `@embedFile` reference uses this
+//! prefix so Zig can resolve it relative to the generated module's
+//! directory.
+//!
+//! The generated file looks like:
+//!
+//! pub const Entry = struct {
+//! /// Path relative to the agent tree root (e.g. "tools/read.lua").
+//! path: []const u8,
+//! /// File contents.
+//! contents: []const u8,
+//! };
+//! pub const files: []const Entry = &.{
+//! .{ .path = "tools/read.lua", .contents = @embedFile("...") },
+//! ...
+//! };
+//!
+//! Bootstrap walks `files` and stages each entry at
+//! `$PANTO_HOME/agent/<path>` using the same `writeIfDifferent`
+//! discipline as `stageLuaHeaders`.
+
+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;
+
+ var entries: std.array_list.Managed(Entry) = .init(arena);
+ try collect(arena, io, src_dir_arg, embed_prefix, &entries);
+
+ // Sort for deterministic output across builds.
+ std.mem.sort(Entry, entries.items, {}, Entry.lessThan);
+
+ 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_agent_embed.zig.
+ \\
+ \\pub const Entry = struct {
+ \\ /// Path relative to the agent tree root (e.g. "tools/read.lua").
+ \\ path: []const u8,
+ \\ /// File contents (raw bytes).
+ \\ contents: []const u8,
+ \\};
+ \\
+ \\pub const files: []const Entry = &.{
+ \\
+ );
+
+ for (entries.items) |e| {
+ try w.print(
+ " .{{ .path = \"{s}\", .contents = @embedFile(\"{s}\") }},\n",
+ .{ e.rel_path, e.embed_path },
+ );
+ }
+
+ try w.writeAll("};\n");
+ try w.flush();
+}
+
+const Entry = struct {
+ /// Path inside the agent tree, forward-slash separated.
+ rel_path: []const u8,
+ /// Path the generated `@embedFile` references (under embed prefix).
+ embed_path: []const u8,
+
+ fn lessThan(_: void, a: Entry, b: Entry) bool {
+ return std.mem.lessThan(u8, a.rel_path, b.rel_path);
+ }
+};
+
+/// Walk `<root>` recursively, collecting every file. Dotfiles
+/// (entries whose basename starts with `.`) are skipped at every
+/// level so we don't accidentally embed editor backups, `.DS_Store`,
+/// or VCS metadata.
+fn collect(
+ arena: std.mem.Allocator,
+ io: std.Io,
+ root_abs: []const u8,
+ embed_prefix: []const u8,
+ out: *std.array_list.Managed(Entry),
+) !void {
+ var dir = std.Io.Dir.cwd().openDir(io, root_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 (entry.basename.len == 0 or entry.basename[0] == '.') continue;
+ // Skip anything along the path that looks like a dotfile dir.
+ if (std.mem.indexOf(u8, entry.path, "/.") != null) continue;
+
+ const rel_path = try arena.dupe(u8, entry.path);
+ const embed_path = try std.fs.path.join(arena, &.{ embed_prefix, entry.path });
+
+ try out.append(.{
+ .rel_path = rel_path,
+ .embed_path = embed_path,
+ });
+ }
+}