summaryrefslogtreecommitdiff
path: root/libpanto-lua/build.zig
diff options
context:
space:
mode:
Diffstat (limited to 'libpanto-lua/build.zig')
-rw-r--r--libpanto-lua/build.zig143
1 files changed, 143 insertions, 0 deletions
diff --git a/libpanto-lua/build.zig b/libpanto-lua/build.zig
new file mode 100644
index 0000000..8bfaf88
--- /dev/null
+++ b/libpanto-lua/build.zig
@@ -0,0 +1,143 @@
+const std = @import("std");
+
+/// `libpanto-lua` — a native Lua 5.4 C-module implemented in pure Zig.
+///
+/// Emits a loadable `panto.so` (no `lib` prefix) exporting `luaopen_panto`,
+/// discovered on `package.cpath` and loaded by `require('panto')`. The
+/// module `@cImport`s the Lua 5.4 headers and calls the Zig `libpanto` API
+/// directly — no C translation units in this package, no dependency on
+/// `libpanto-c`.
+///
+/// Targets Lua 5.4 only (see `docs/libpanto-bindings.md`). A Lua C-module
+/// does not link the Lua library: the host interpreter supplies every
+/// `lua_*` symbol at load time, so we link only against the headers and
+/// resolve the symbols via dynamic lookup at runtime.
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const panto_dep = b.dependency("panto", .{
+ .target = target,
+ .optimize = optimize,
+ });
+ const lua_src = b.dependency("lua_src", .{});
+
+ const mod = b.createModule(.{
+ .root_source_file = b.path("src/module.zig"),
+ .target = target,
+ .optimize = optimize,
+ .link_libc = true,
+ });
+ mod.addImport("panto", panto_dep.module("panto"));
+ // Lua headers for `@cImport`. Headers only — see the note above.
+ mod.addIncludePath(lua_src.path("src"));
+
+ const lib = b.addLibrary(.{
+ .name = "panto",
+ .root_module = mod,
+ .linkage = .dynamic,
+ });
+ // Lua modules export a fixed-name init function; keep it in the
+ // dynamic symbol table so the host's `require` can find it.
+ lib.rdynamic = true;
+ // The `lua_*` / `luaL_*` symbols are provided by the host
+ // interpreter at `dlopen` time, not by this module. Allow them to be
+ // left undefined at link time and resolved dynamically when loaded.
+ allowUndefinedHostSymbols(lib, target);
+
+ // Register the Compile step as a named artifact so dependents (the
+ // panto CLI build) can address it via `dep.artifact("panto")` to embed
+ // the compiled `.so`. This installs `libpanto.so`/`libpanto.dylib`
+ // under the default `lib/` name; the bare-name staging below produces
+ // the `panto.so` a Lua `require` needs.
+ b.installArtifact(lib);
+
+ // A Lua C-module must be named exactly `panto.so` (no `lib` prefix,
+ // no version suffix) to be found as `require('panto')` on `cpath`.
+ // `addLibrary` produces `libpanto.so` (or `.dylib`); install it under
+ // the bare module name into `lib/`.
+ const install_so = b.addInstallFileWithDir(
+ lib.getEmittedBin(),
+ .lib,
+ "panto.so",
+ );
+ b.getInstallStep().dependOn(&install_so.step);
+
+ // Unit tests. The test binary is an ordinary executable that links a
+ // real Lua so the C symbols resolve; we compile the Lua sources into
+ // it directly (see `addLuaForTests`).
+ const test_mod = b.createModule(.{
+ .root_source_file = b.path("src/module.zig"),
+ .target = target,
+ .optimize = optimize,
+ .link_libc = true,
+ });
+ test_mod.addImport("panto", panto_dep.module("panto"));
+ test_mod.addIncludePath(lua_src.path("src"));
+ addLuaForTests(test_mod, lua_src);
+
+ const unit_tests = b.addTest(.{
+ .name = "panto-lua-tests",
+ .root_module = test_mod,
+ });
+ const run_unit_tests = b.addRunArtifact(unit_tests);
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_unit_tests.step);
+}
+
+/// On macOS the linker rejects undefined symbols by default; a Lua
+/// C-module relies on the host interpreter to provide every `lua_*`
+/// symbol at `dlopen` time. Pass the flag that defers resolution to load
+/// time. On ELF (Linux/BSD) shared objects already allow undefined
+/// symbols resolved by the loader, so nothing is needed.
+fn allowUndefinedHostSymbols(
+ lib: *std.Build.Step.Compile,
+ target: std.Build.ResolvedTarget,
+) void {
+ switch (target.result.os.tag) {
+ .macos => {
+ lib.linker_allow_shlib_undefined = true;
+ lib.root_module.addCMacro("LUA_USE_MACOSX", "");
+ },
+ .linux => lib.root_module.addCMacro("LUA_USE_LINUX", ""),
+ .freebsd, .netbsd, .openbsd => lib.root_module.addCMacro("LUA_USE_POSIX", ""),
+ else => {},
+ }
+}
+
+/// The Lua source files needed to compile a self-contained Lua into the
+/// test binary so the `lua_*` symbols resolve (the standalone `.so`
+/// borrows them from a host instead). This is the same `core + lib + aux`
+/// set the CLI compiles, minus `lua.c`/`luac.c` (the standalone front
+/// ends, which carry their own `main`).
+const lua_files = [_][]const u8{
+ // core
+ "lapi.c", "lcode.c", "lctype.c", "ldebug.c", "ldo.c",
+ "ldump.c", "lfunc.c", "lgc.c", "llex.c", "lmem.c",
+ "lobject.c", "lopcodes.c", "lparser.c", "lstate.c", "lstring.c",
+ "ltable.c", "ltm.c", "lundump.c", "lvm.c", "lzio.c",
+ // lib
+ "lauxlib.c", "lbaselib.c", "lcorolib.c", "ldblib.c", "liolib.c",
+ "lmathlib.c", "loadlib.c", "loslib.c", "lstrlib.c", "ltablib.c",
+ "lutf8lib.c", "linit.c",
+};
+
+fn addLuaForTests(mod: *std.Build.Module, lua_src: *std.Build.Dependency) void {
+ const cflags = [_][]const u8{
+ "-std=gnu99",
+ "-Wall",
+ "-Wextra",
+ "-Wno-unused-parameter",
+ };
+ mod.addCSourceFiles(.{
+ .root = lua_src.path("src"),
+ .files = &lua_files,
+ .flags = &cflags,
+ });
+ switch (mod.resolved_target.?.result.os.tag) {
+ .macos => mod.addCMacro("LUA_USE_MACOSX", ""),
+ .linux => mod.addCMacro("LUA_USE_LINUX", ""),
+ .freebsd, .netbsd, .openbsd => mod.addCMacro("LUA_USE_POSIX", ""),
+ else => {},
+ }
+}