summaryrefslogtreecommitdiff
path: root/libpanto-lua/build.zig
blob: 8bfaf88dedb6421b4326c32b76a5140c978da0f3 (plain)
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
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 => {},
    }
}