summaryrefslogtreecommitdiff
path: root/build.zig
diff options
context:
space:
mode:
Diffstat (limited to 'build.zig')
-rw-r--r--build.zig251
1 files changed, 230 insertions, 21 deletions
diff --git a/build.zig b/build.zig
index e675859..5bf5d44 100644
--- a/build.zig
+++ b/build.zig
@@ -1,5 +1,9 @@
const std = @import("std");
+const lua_version = "5.4.7";
+const lua_short_version = "5.4";
+const luarocks_version = "3.13.0";
+
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
@@ -9,10 +13,31 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
});
- // Fetch upstream Lua 5.4.7 (source-only tarball from lua.org).
- // Reproducibility comes from the content-addressed hash in build.zig.zon.
+ // Fetch upstream Lua source (used both for our static library and
+ // staged at runtime as the `include/` headers under $PANTO_HOME).
+ // Reproducibility comes from the content-addressed hash in
+ // build.zig.zon.
const lua_src = b.dependency("lua_src", .{});
- const lua = buildLua(b, target, optimize, lua_src);
+ const lua_anchor_path = generateLuaAnchor(b, lua_src);
+
+ // Fetch luarocks source. We don't compile any of it — it's pure
+ // Lua. The codegen step below produces a `embedded_luarocks.zig`
+ // module that `@embedFile`s every script so the runtime can serve
+ // them as `require("luarocks.*")` results without disk I/O.
+ const luarocks_src = b.dependency("luarocks_src", .{});
+ const luarocks_embed_path = generateLuarocksEmbed(b, luarocks_src);
+
+ // Same trick for the Lua headers: bundle them so first-run
+ // bootstrap can stage them under $PANTO_HOME/rocks/lua-X.Y.Z/include/
+ // for luarocks to find when compiling C rocks.
+ const lua_headers_embed_path = generateLuaHeadersEmbed(b, lua_src);
+
+ // Constants module: makes Zig know the Lua and luarocks versions
+ // without duplicating literals everywhere.
+ const versions_mod = b.addOptions();
+ versions_mod.addOption([]const u8, "lua_version", lua_version);
+ versions_mod.addOption([]const u8, "lua_short_version", lua_short_version);
+ versions_mod.addOption([]const u8, "luarocks_version", luarocks_version);
// CLI executable
const exe_mod = b.createModule(.{
@@ -22,13 +47,42 @@ pub fn build(b: *std.Build) void {
.link_libc = true,
});
exe_mod.addImport("panto", panto_lib_dep.module("panto"));
- exe_mod.linkLibrary(lua);
+ exe_mod.addImport("versions", versions_mod.createModule());
+ exe_mod.addImport("embedded_luarocks", b.createModule(.{
+ .root_source_file = luarocks_embed_path,
+ }));
+ exe_mod.addImport("embedded_lua_headers", b.createModule(.{
+ .root_source_file = lua_headers_embed_path,
+ }));
+ // Link Lua. We can't use a static library here because the
+ // standard archive-linking behavior drops object files whose
+ // symbols nothing in our code references — e.g. `lua_settable`,
+ // which luv.so needs at runtime via `dlopen` but neither panto
+ // itself nor the embedded lua.c repl ever call directly. Instead,
+ // compile the Lua sources directly *into* the executable's own
+ // module so every object lands in the binary. `rdynamic = true`
+ // below ensures the symbols are exported into the dynamic symbol
+ // table for `dlopen`'d rocks to resolve.
+ addLuaSources(exe_mod, lua_src, lua_anchor_path);
exe_mod.addIncludePath(lua_src.path("src"));
+ // Compile lua.c (the upstream standalone interpreter, ~600 lines)
+ // as a separate static library so we can route the `panto lua`
+ // subcommand into its main() without it conflicting with our own
+ // main().
+ const lua_repl = buildLuaRepl(b, target, optimize, lua_src);
+ exe_mod.linkLibrary(lua_repl);
+
const exe = b.addExecutable(.{
.name = "panto",
.root_module = exe_mod,
});
+ // C rocks (luv.so and anything else loaded via dlopen) need
+ // Lua's C API symbols to be findable in the running process's
+ // symbol table. Without `-rdynamic`, the linker strips them as
+ // unused-from-outside; `dlopen` then fails at load time with
+ // "symbol not found in flat namespace '_lua_settable'" etc.
+ exe.rdynamic = true;
b.installArtifact(exe);
@@ -41,7 +95,7 @@ pub fn build(b: *std.Build) void {
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
- // CLI unit tests (minimal — most tests live in libpanto)
+ // CLI unit tests
const test_mod = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
@@ -49,8 +103,16 @@ pub fn build(b: *std.Build) void {
.link_libc = true,
});
test_mod.addImport("panto", panto_lib_dep.module("panto"));
- test_mod.linkLibrary(lua);
+ test_mod.addImport("versions", versions_mod.createModule());
+ test_mod.addImport("embedded_luarocks", b.createModule(.{
+ .root_source_file = luarocks_embed_path,
+ }));
+ test_mod.addImport("embedded_lua_headers", b.createModule(.{
+ .root_source_file = lua_headers_embed_path,
+ }));
+ addLuaSources(test_mod, lua_src, lua_anchor_path);
test_mod.addIncludePath(lua_src.path("src"));
+ test_mod.linkLibrary(lua_repl);
const unit_tests = b.addTest(.{
.name = "panto-tests",
@@ -66,16 +128,20 @@ pub fn build(b: *std.Build) void {
test_step.dependOn(&lib_test_step.step);
}
-/// Compile Lua 5.4.7 as a static library against the upstream source tarball.
-/// Excludes `lua.c` (interpreter REPL) and `luac.c` (bytecode compiler) — we
-/// only want the embedded library.
-fn buildLua(
+/// Compile a thin wrapper around the upstream `lua.c` standalone
+/// interpreter that exposes `pmain` for panto's `lua` subcommand to
+/// invoke against a pre-configured `lua_State`.
+///
+/// We rename `main` to `lua_unused_main` so the symbol doesn't collide
+/// with panto's `main` (the wrapper never references it; the linker
+/// just needs a place for it to land).
+fn buildLuaRepl(
b: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
lua_src: *std.Build.Dependency,
) *std.Build.Step.Compile {
- const lua_mod = b.createModule(.{
+ const mod = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
@@ -84,33 +150,176 @@ fn buildLua(
const cflags = [_][]const u8{
"-std=gnu99",
"-Wall",
- "-Wextra",
"-Wno-unused-parameter",
+ "-Wno-unused-function",
+ // Rename upstream `main` so it doesn't collide with panto's.
+ // We never call it; the wrapper invokes `pmain` directly.
+ "-Dmain=lua_unused_main",
};
- lua_mod.addCSourceFiles(.{
+ mod.addCSourceFile(.{
+ .file = b.path("build/panto_lua_repl.c"),
+ .flags = &cflags,
+ });
+ // The wrapper `#include`s lua.c by name; we add lua_src/src to
+ // the search path so `#include "lua.c"` resolves.
+ mod.addIncludePath(lua_src.path("src"));
+ addLuaPlatformMacros(mod, target);
+
+ return b.addLibrary(.{
+ .name = "lua-repl",
+ .linkage = .static,
+ .root_module = mod,
+ });
+}
+
+/// Add all the Lua 5.4 core + standard-library C sources directly to
+/// `mod`. Compared to building a separate static library and linking,
+/// this guarantees the linker doesn't drop "unused" objects — we need
+/// every Lua API symbol present in the final binary so C rocks loaded
+/// via `dlopen` can resolve them against the host process's symbol
+/// table.
+fn addLuaSources(
+ mod: *std.Build.Module,
+ lua_src: *std.Build.Dependency,
+ lua_anchor_path: std.Build.LazyPath,
+) void {
+ const cflags = [_][]const u8{
+ "-std=gnu99",
+ "-Wall",
+ "-Wextra",
+ "-Wno-unused-parameter",
+ };
+ mod.addCSourceFiles(.{
.root = lua_src.path("src"),
.files = &lua_files,
.flags = &cflags,
});
+ // Tiny extra TU (codegen'd at build time): takes the address of
+ // every Lua API function into a `__attribute__((used))` array.
+ // This forces the linker to keep every API function in the binary
+ // even under ReleaseFast's LTO and gc-sections, regardless of
+ // whether our own code references them. C rocks loaded via
+ // `dlopen` (luv.so etc.) need every public Lua symbol resolvable
+ // in the host process's symbol table; combined with
+ // `rdynamic = true` on the executable, this gets them all into
+ // the dynamic symbol table.
+ mod.addCSourceFile(.{
+ .file = lua_anchor_path,
+ .flags = &cflags,
+ });
+ addLuaPlatformMacros(mod, mod.resolved_target.?);
+}
+fn addLuaPlatformMacros(
+ mod: *std.Build.Module,
+ target: std.Build.ResolvedTarget,
+) void {
// Tell Lua which platform features are available. The macros change
// which `dlopen`/readline/etc. paths Lua compiles in.
switch (target.result.os.tag) {
- .macos => lua_mod.addCMacro("LUA_USE_MACOSX", ""),
- .linux => lua_mod.addCMacro("LUA_USE_LINUX", ""),
- .freebsd, .netbsd, .openbsd => lua_mod.addCMacro("LUA_USE_POSIX", ""),
+ .macos => mod.addCMacro("LUA_USE_MACOSX", ""),
+ .linux => mod.addCMacro("LUA_USE_LINUX", ""),
+ .freebsd, .netbsd, .openbsd => mod.addCMacro("LUA_USE_POSIX", ""),
else => {},
}
+}
- return b.addLibrary(.{
- .name = "lua",
- .linkage = .static,
- .root_module = lua_mod,
+/// Codegen step: walk the luarocks tarball, emit a Zig module that
+/// exposes every Lua source as a `@embedFile`d entry plus a top-level
+/// table mapping `require` paths (e.g. `"luarocks.core.cfg"`) to those
+/// contents. The runtime installs this table into `package.preload` so
+/// `require("luarocks.*")` resolves without touching disk.
+///
+/// The generator emits `@embedFile("luarocks/<...>.lua")`-style paths.
+/// We then materialize the luarocks source tree alongside the
+/// generated Zig file via `addCopyDirectory`, so the embedded paths
+/// resolve against the same root the module compiles from. (`@embedFile`
+/// only allows reading files inside the module's import-path tree.)
+///
+/// Two outputs are present in the resulting module:
+/// - `pub const files: []const Entry` — { require_name, contents }
+/// - `pub const luarocks_main` / `pub const luarocks_admin_main`
+/// for the two `src/bin/` driver scripts.
+fn generateLuaAnchor(
+ b: *std.Build,
+ lua_src: *std.Build.Dependency,
+) std.Build.LazyPath {
+ const tool = b.addExecutable(.{
+ .name = "gen-lua-anchor",
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("build/gen_lua_anchor.zig"),
+ .target = b.graph.host,
+ .optimize = .Debug,
+ }),
+ });
+
+ const run = b.addRunArtifact(tool);
+ run.addDirectoryArg(lua_src.path("src"));
+ return run.addOutputFileArg("lua_anchor.c");
+}
+
+fn generateLuarocksEmbed(
+ b: *std.Build,
+ luarocks_src: *std.Build.Dependency,
+) std.Build.LazyPath {
+ const tool = b.addExecutable(.{
+ .name = "gen-luarocks-embed",
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("build/gen_luarocks_embed.zig"),
+ .target = b.graph.host,
+ .optimize = .Debug,
+ }),
});
+
+ const run = b.addRunArtifact(tool);
+ // Pass a marker arg the tool will treat as the "embed-relative"
+ // subpath — every `@embedFile` reference uses this prefix so
+ // they resolve against the same directory the generated zig file
+ // lives in (the one we set up next via addWriteFiles).
+ run.addArg("luarocks_src");
+ run.addDirectoryArg(luarocks_src.path("src"));
+ const gen_file = run.addOutputFileArg("embedded_luarocks.zig");
+
+ // Stage the generated zig file next to a copy of the luarocks src
+ // tree, so `@embedFile("luarocks_src/...")` resolves.
+ const wf = b.addWriteFiles();
+ const out_zig = wf.addCopyFile(gen_file, "embedded_luarocks.zig");
+ _ = wf.addCopyDirectory(luarocks_src.path("src"), "luarocks_src", .{});
+ return out_zig;
+}
+
+/// Codegen step: emit a Zig module that exposes every Lua public header
+/// from the Lua source tarball as embedded bytes. The bootstrap stages
+/// these under `$PANTO_HOME/rocks/lua-X.Y.Z/include/` so luarocks can
+/// compile C rocks against them.
+fn generateLuaHeadersEmbed(
+ b: *std.Build,
+ lua_src: *std.Build.Dependency,
+) std.Build.LazyPath {
+ const tool = b.addExecutable(.{
+ .name = "gen-lua-headers-embed",
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("build/gen_lua_headers_embed.zig"),
+ .target = b.graph.host,
+ .optimize = .Debug,
+ }),
+ });
+
+ const run = b.addRunArtifact(tool);
+ // Marker subpath: each emitted `@embedFile` reference is
+ // `lua_src/<header>`, and we stage the headers under that name.
+ run.addArg("lua_src");
+ run.addDirectoryArg(lua_src.path("src"));
+ const gen_file = run.addOutputFileArg("embedded_lua_headers.zig");
+
+ const wf = b.addWriteFiles();
+ const out_zig = wf.addCopyFile(gen_file, "embedded_lua_headers.zig");
+ _ = wf.addCopyDirectory(lua_src.path("src"), "lua_src", .{});
+ return out_zig;
}
-// Lua 5.4.7 core VM + standard library. Excludes lua.c (interpreter entry
+// Lua 5.4.x core VM + standard library. Excludes lua.c (interpreter entry
// point) and luac.c (compiler entry point).
const lua_files = [_][]const u8{
// Core VM