diff --git a/build.zig b/build.zig index 77c8ac1..06add14 100644 --- a/build.zig +++ b/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); const Build = std.Build; -pub const Language = lua_setup.Language; const Step = std.Build.Step; const Translator = @import("translate_c").Translator; @@ -9,6 +8,26 @@ const lua_setup = @import("build/lua.zig"); const luau_setup = @import("build/luau.zig"); const luajit_setup = @import("build/luajit.zig"); +/// The Lua version to compile and link. +pub const Language = enum { + lua51, + lua52, + lua53, + lua54, + lua55, + luajit, + luau, +}; + +pub const ApiCheck = enum { + /// Enables apicheck in debug builds. + debug, + /// Enables apicheck. + on, + /// Disables apicheck. + off, +}; + pub fn build(b: *Build) void { // Remove the default install and uninstall steps b.top_level_steps = .{}; @@ -23,6 +42,7 @@ pub fn build(b: *Build) void { const luau_use_4_vector = b.option(bool, "luau_use_4_vector", "Build Luau to use 4-vectors instead of the default 3-vector.") orelse false; const lua_user_h = b.option(Build.LazyPath, "lua_user_h", "Lazy path to user supplied c header file") orelse null; const additional_system_headers = b.option(Build.LazyPath, "additional_system_headers", "Lazy path to additional system headers to include when building Lua") orelse null; + const api_check = b.option(ApiCheck, "apicheck", "Enable parameter checks in the Lua API") orelse .debug; if (lang == .luau and shared) { std.debug.panic("Luau does not support compiling or loading shared modules", .{}); @@ -86,13 +106,14 @@ pub fn build(b: *Build) void { } } else if (b.lazyDependency(@tagName(lang), .{})) |upstream| { const lib = switch (lang) { - .luajit => luajit_setup.configure(b, target, optimize, upstream, shared), + .luajit => luajit_setup.configure(b, target, optimize, upstream, shared, api_check), .luau => luau_setup.configure(b, target, optimize, upstream, luau_use_4_vector), else => lua_setup.configure(b, target, optimize, upstream, .{ .lang = lang, .shared = shared, .library_name = library_name, .lua_user_h = lua_user_h, + .api_check = api_check, }), }; diff --git a/build/header_gen.zig b/build/header_gen.zig new file mode 100644 index 0000000..df175af --- /dev/null +++ b/build/header_gen.zig @@ -0,0 +1,48 @@ +//! Concatenates two files together. + +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + + var iter = try init.minimal.args.iterateAllocator(arena); + + // Skip executable name + _ = iter.next(); + + const file1 = iter.next() orelse @panic("Missing file1 argument"); + const file2 = iter.next() orelse @panic("Missing file2 argument"); + const output_path = iter.next() orelse @panic("Missing output_path argument"); + if (iter.next() != null) @panic("Too many arguments"); + + const output_file = try Io.Dir.cwd().openFile(init.io, output_file, .{}); + defer output_file.close(init.io); + var out_buf: [4096]u8 = undefined; + var writer = output_file.writer(init.io, &out_buf); + defer (&writer.interface).flush() catch {}; + + { + const file = try Io.Dir.cwd().openFile(init.io, file1, .{ .mode = .read_only }); + defer file.deinit(init.io); + + var buf: [4096]u8 = undefined; + var reader = file.reader(init.io, &buf); + + try (&reader.interface).stream(&writer.interface, .unlimited); + } + + try (&writer.interface).writeByte('\n'); + + { + const file = try Io.Dir.cwd().openFile(init.io, file2, .{ .mode = .read_only }); + defer file.deinit(init.io); + + var buf: [4096]u8 = undefined; + var reader = file.reader(init.io, &buf); + + try (&reader.interface).stream(&writer.interface, .unlimited); + } +} + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Io = std.Io; +const File = Io.File; diff --git a/build/lua.zig b/build/lua.zig index 1f2791e..2c4c01a 100644 --- a/build/lua.zig +++ b/build/lua.zig @@ -3,23 +3,21 @@ const std = @import("std"); const Build = std.Build; const Step = std.Build.Step; -const applyPatchToFile = @import("utils.zig").applyPatchToFile; - -pub const Language = enum { - lua51, - lua52, - lua53, - lua54, - lua55, - luajit, - luau, -}; +const utils = @import("utils.zig"); +const applyPatchToFile = utils.applyPatchToFile; +const concatenateFiles = utils.concatenateFiles; + +const build = @import("../build.zig"); + +const ApiCheck = build.ApiCheck; +const Language = build.Language; pub const Options = struct { lang: Language, shared: bool, library_name: []const u8, lua_user_h: ?Build.LazyPath, + api_check: ApiCheck, }; pub fn configure( @@ -60,6 +58,8 @@ pub fn configure( const user_header = "user.h"; + const enable_apicheck = opts.api_check == .on or (opts.api_check == .debug and optimize == .Debug); + const flags = [_][]const u8{ // Standard version used in Lua Makefile "-std=gnu99", @@ -73,12 +73,12 @@ pub fn configure( }, // Enable api check - if (optimize == .Debug) "-DLUA_USE_APICHECK" else "", + if (lang == .lua55 and enable_apicheck) "-DLUA_USE_APICHECK" else "", // Build as DLL for windows if shared if (target.result.os.tag == .windows and shared) "-DLUA_BUILD_AS_DLL" else "", - if (lua_user_h) |_| b.fmt("-DLUA_USER_H=\"{s}\"", .{user_header}) else "", + if (enable_apicheck or lua_user_h != null) b.fmt("-DLUA_USER_H=\"{s}\"", .{user_header}) else "", }; const lua_source_files = switch (lang) { @@ -114,8 +114,19 @@ pub fn configure( library.installHeader(upstream.path("src/luaconf.h"), "luaconf.h"); if (lua_user_h) |user_h| { - library.root_module.addIncludePath(user_h.dirname()); - library.installHeader(user_h, user_header); + if (enable_apicheck) { + const concat = concatenateFiles(b, b.graph.host, user_h, b.path("src/user.h"), user_header); + library.step.dependOn(&concat.run.step); + + library.root_module.addIncludePath(concat.output.dirname()); + library.installHeader(concat.output, user_header); + } else { + library.root_module.addIncludePath(user_h.dirname()); + library.installHeader(user_h, user_header); + } + } else if (enable_apicheck) { + library.root_module.addIncludePath(b.path("src")); + library.installHeader(b.path("src/user.h"), user_header); } return library; diff --git a/build/luajit.zig b/build/luajit.zig index 61f229a..51e73c0 100644 --- a/build/luajit.zig +++ b/build/luajit.zig @@ -3,9 +3,18 @@ const std = @import("std"); const Build = std.Build; const Step = std.Build.Step; +const ApiCheck = @import("../build.zig").ApiCheck; + const applyPatchToFile = @import("utils.zig").applyPatchToFile; -pub fn configure(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, upstream: *Build.Dependency, shared: bool) *Step.Compile { +pub fn configure( + b: *Build, + target: Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + upstream: *Build.Dependency, + shared: bool, + api_check: ApiCheck, +) *Step.Compile { // TODO: extract this to the main build function because it is shared between all specialized build functions const lib = b.createModule(.{ @@ -216,12 +225,18 @@ pub fn configure(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin. library.root_module.addIncludePath(recdef_header.dirname()); library.root_module.addIncludePath(folddef_header.dirname()); + const flags = [_][]const u8{ + // Enable api check + if (api_check == .on or (api_check == .debug and optimize == .Debug)) "-DLUA_USE_APICHECK" else "", + }; + lib.addCSourceFiles(.{ .root = .{ .dependency = .{ .dependency = upstream, .sub_path = "", } }, .files = &luajit_vm, + .flags = &flags, }); lib.sanitize_c = .off; diff --git a/build/utils.zig b/build/utils.zig index 16637de..005d5d5 100644 --- a/build/utils.zig +++ b/build/utils.zig @@ -3,7 +3,7 @@ const std = @import("std"); const Build = std.Build; const Step = std.Build.Step; -const PatchFile = struct { +const RunOutput = struct { run: *Step.Run, output: Build.LazyPath, }; @@ -14,7 +14,7 @@ pub fn applyPatchToFile( file: Build.LazyPath, patch_file: Build.LazyPath, output_file: []const u8, -) PatchFile { +) RunOutput { const patch = b.addExecutable(.{ .name = "patch", .root_module = b.createModule(.{ @@ -34,3 +34,30 @@ pub fn applyPatchToFile( .output = out, }; } + +pub fn concatenateFiles( + b: *Build, + target: Build.ResolvedTarget, + file1: Build.LazyPath, + file2: Build.LazyPath, + output_file: []const u8, +) RunOutput { + const concatenate = b.addExecutable(.{ + .name = "concat", + .root_module = b.createModule(.{ + .root_source_file = b.path("build/header_gen.zig"), + .target = target, + }), + }); + + const concatenate_run = b.addRunArtifact(concatenate); + concatenate_run.addFileArg(file1); + concatenate_run.addFileArg(file2); + + const out = concatenate_run.addOutputFileArg(output_file); + + return .{ + .run = concatenate_run, + .output = out, + }; +} diff --git a/src/lib.zig b/src/lib.zig index cf8eee9..bb13cdd 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -111,6 +111,10 @@ extern "c" fn zig_registerAssertionHandler() void; /// This function is defined in luau.cpp and ensures Zig uses the correct free when compiling luau code extern "c" fn zig_luau_free(ptr: *anyopaque) void; +export fn zlua_assert(ok: bool) void { + std.debug.assert(ok); +} + const Allocator = std.mem.Allocator; // Types @@ -4493,7 +4497,10 @@ pub const Lua = opaque { pub fn openBit32(lua: *Lua) void { switch (lang) { .lua52, .lua53 => lua.requireF(c.LUA_BITLIBNAME, c.luaopen_bit32, true), - .luajit => lua.requireF(c.LUA_BITLIBNAME, c.luaopen_bit, true), + .luajit => { + lua.pushFunction(c.luaopen_bit); + lua.call(.{ .results = 1 }); + }, else => @compileError(@src().fn_name ++ " is only available in Lua 5.2 and LuaJIT."), } lua.pop(1); @@ -5451,7 +5458,7 @@ pub fn wrap(comptime function: anytype) TypeOfWrap(function) { const lua: *Lua = @ptrCast(state.?); if (str) |s| { const buf = s[0..len]; - return @call(.always_inline, function, .{lua, buf}); + return @call(.always_inline, function, .{ lua, buf }); } return -1; } diff --git a/src/user.h b/src/user.h new file mode 100644 index 0000000..36c5ac4 --- /dev/null +++ b/src/user.h @@ -0,0 +1,4 @@ +extern void zlua_assert(int e); + +#define luai_apicheck(l,e) zlua_assert(e) +