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 => {}, } }