Skip to content

Instantly share code, notes, and snippets.

@q962
Last active August 8, 2024 09:50
Show Gist options
  • Save q962/871967a69d181c61d8adcac3be2b6723 to your computer and use it in GitHub Desktop.
Save q962/871967a69d181c61d8adcac3be2b6723 to your computer and use it in GitHub Desktop.
c 集成 lua 模块的方法
includes("./luafiles.lua")
--[[
对于这个target就不需要调用 <module_name>_loadmodules 函数了
见 test.c 中的例子
]]
target("lua_require")
set_kind("static")
add_files("require.lua")
add_rules("luafiles", {root_path = ".", module_name = "require"})
-- 为每一个 lua 提供一个 target 目标
target("Penlight")
set_kind("static")
add_files("Penlight/lua/pl/**.lua")
add_rules("luafiles", {root_path = "Penlight/lua", module_name = "pl"})
--[[
]]
target("Program")
add_deps("lua_require", Penlight)
--[[
利用 objcopy 将 root_path 下的所有 lua 文件变成符号
需要配合特殊的 require 去识别这些内容。
该规则会生成一个 c 文件
暴露一个符号: <module_name>_loadmodules,需要在 c 中调用。
不需要 include 文件。 直接 extern void <module_name>_loadmodules( lua_State * state );
该函数会向栈顶的 table 注册注册一个名为 <module_name> 的 table
该 table 将按照 路径:文件内容 的模式存数存储所有的lua 文件内容。
该路径是 lua 文件相对于 root_path 的路径。
并且将分割替换为 '.', 去除文件后缀。
利用 debug 模块提供的调用源的 source 字段,实现相对路径的识别。
这会临时修改 package.path 印象 lua 的文件查找逻辑
]]
rule("luafiles")
set_extensions(".lua")
on_load(function(target)
local root_path = target:extraconf("rules", "luafiles", "root_path")
local module_name = target:extraconf("rules", "luafiles", "module_name") or target:name()
assert(root_path)
local chunks = "";
local changed = false
for _, sourcefile in ipairs(target:sourcebatches().luafiles.sourcefiles) do
local filepath = sourcefile
local objectfile = target:objectfile(sourcefile)
local f_mtime = os.mtime(filepath);
local o_mtime = os.mtime(objectfile);
local projectdir = vformat("$(projectdir)")
local r_filepath = path.relative(projectdir .. "/" .. sourcefile, target:scriptdir() .. "/" .. root_path)
local binary_name = r_filepath:gsub("[\\/.-]", "_")
local chunk_name = r_filepath:gsub("[\\/]", "."):sub(1, -1 - 4)
chunks = chunks .. format([[
{
#define XXX(X) \
extern const char ]] .. module_name .. [[_binary_##X##_start; \
extern const char ]] .. module_name .. [[_binary_##X##_end; \
const char* _start = &]] .. module_name .. [[_binary_##X##_start; \
const char* _end = &]] .. module_name .. [[_binary_##X##_end; \
size_t _size = _end - _start;
XXX(%s)
lua_pushstring(L, "%s" );
luaL_loadbuffer(L, _start, _size, "@M@%s");
lua_settable(L, -3);
#undef XXX
}
]], binary_name, chunk_name, chunk_name)
if f_mtime > o_mtime then
changed = true
end
end
local module_c_filepath = target:autogendir() .. "/" .. module_name .. ".c"
if changed or not os.isfile(module_c_filepath) then
io.writefile(module_c_filepath, [[
#include <lualib.h>
#include <lauxlib.h>
#include <string.h>
void ]] .. module_name .. [[_loadmodules(lua_State* L) {
lua_pushstring(L, "]] .. module_name .. [[");
lua_newtable(L);
]] .. chunks .. [[
lua_settable( L, -3 );
}
]]);
end
target:add("files", module_c_filepath)
end)
before_build(function(target)
import("lib.detect.find_program")
local root_path = target:extraconf("rules", "luafiles", "root_path")
local module_name = target:extraconf("rules", "luafiles", "module_name") or target:name()
assert(root_path)
local projectdir = vformat("$(projectdir)/")
local scriptdir = vformat("$(scriptdir)/")
local sourcedir = target:scriptdir() .. "/" .. root_path
local changed = false;
local lua_files = {};
local luac = find_program("luac")
for _, sourcefile in ipairs(target:sourcebatches().luafiles.sourcefiles) do
local objectfile = path.relative(target:objectfile(sourcefile), projectdir)
sourcefile = projectdir .. sourcefile
local r_filepath = path.relative(sourcefile, sourcedir)
local a_filepath = projectdir .. objectfile
local f_mtime = os.mtime(sourcefile);
local a_mtime = os.mtime(a_filepath);
table.insert(lua_files, a_filepath)
if f_mtime > a_mtime then
changed = true;
os.cd(sourcedir)
os.mkdir(path.directory(a_filepath))
os.execv("objcopy",
{"-I", "binary", "-O", "elf64-x86-64", "--prefix-symbols", module_name, r_filepath, a_filepath})
os.execv("objcopy",
{"-I", "binary", "-O", "elf64-x86-64", "--prefix-symbols", module_name, r_filepath, a_filepath})
os.cd("-")
end
end
local out_filepath = projectdir .. path.relative(target:autogendir() .. "/" .. module_name .. ".obj", projectdir)
-- 合并 obj
if changed then
os.mkdir(path.directory(out_filepath))
os.runv(target:tool("ld"), table.join2({"-r", "-o", out_filepath}, lua_files))
end
table.insert(target:objectfiles(), out_filepath)
end)
local realRequire = require;
local __internal_packages = {}
local function string_split(path, sep)
local stack = {};
local _last_end = 1
local _s = 1 -- 游标
while _s do
_s = path:find(sep, _s)
if _s then
table.insert(stack, path:sub(_last_end, _s - 1))
_s = _s + 1
_last_end = _s
else
table.insert(stack, path:sub(_last_end))
break
end
end
return stack
end
local function string_find_last(string, substring, count)
count = count or 1
local list = {};
table.insert(list, 0)
local _s = 1
while _s do
_s = string:find(substring, _s)
if _s then
table.insert(list, _s)
_s = _s + 1
else
break
end
end
return list[#list - (count - 1)]
end
-- 针对文件
local function get_source_dir(up)
if up:sub(1, 1) ~= '@' then
return realRequire(name);
end
up = up:sub(2)
local sep_index = string_find_last(up, "/");
if sep_index ~= 0 then
return up:sub(1, sep_index)
end
return
end
local function get_module(name)
local module = package.loaded[name]
if module then
return module
end
for package_name, modules in pairs(__internal_packages) do
local module = modules[name] or modules[name .. ".init"];
if module then
module = module()
package.loaded[name] = module;
return module or true
end
end
end
-- root: a.b.c.d.e
-- path: ./
-- path: ../././../
-- path 确保是 ./ 开头
local function fix_relative_paths(root, path)
path = path:gsub("[\\]", "/"):gsub("\\\\", "")
local root_path_stack = string_split(root, "[.]")
local path_stack = string_split(path, "/")
table.remove(root_path_stack, #root_path_stack)
local remove_count = 0
for _, v in ipairs(path_stack) do
if v == "." then
elseif v == ".." then
if #root_path_stack == 0 then
break
end
table.remove(root_path_stack, #root_path_stack)
else
break
end
remove_count = remove_count + 1
end
if (#path_stack - remove_count) ~= 1 then
return nil
end
for i = 1, remove_count do
table.remove(path_stack, 1)
end
local parent_path = table.concat(root_path_stack, ".");
local child_path = table.concat(path_stack, ".");
-- 如果找到 . 则去掉 .*
if #parent_path > 0 then
return parent_path .. "." .. child_path
end
return child_path
end
local function relative_require(name)
if name:sub(1, 1) == '.' then
local _name = name:gsub("\\", "/");
local rela_path = _name:match("^[./]+")
local short_name = _name:sub(#rela_path + 1)
local up = debug.getinfo(2, "S").source:gsub("\\", "/");
if up:sub(1, 3) == "@M@" then -- 内部模块
local up_module_path = up:sub(4)
module_name = fix_relative_paths(up_module_path, name)
local module = get_module(module_name)
if (module) then
return module
else
error("Couldn't find module: " .. name)
end
elseif up:sub(1, 1) == "@" then -- 文件路径
local source_dir = get_source_dir(up)
local old_ppath = package.path;
if source_dir then
local pdir = (source_dir .. "/" .. rela_path .. "/"):gsub("//", "/")
package.path = pdir .. "?.lua;" .. pdir .. "?/init.lua;" .. package.path
end
local ret = realRequire(short_name)
package.path = old_ppath;
return ret;
else --
return;
end
else
return get_module(name) or realRequire(name)
end
end
function require(name)
if not name or #name == 0 then
return realRequire(name)
end
return relative_require(name)
end
return __internal_packages
int set(){
int require_ret = LUA_OK;
do {
extern const char require_binary_require_lua_start;
extern const char require_binary_require_lua_end;
size_t require_binary_require_lua_size = &require_binary_require_lua_end - &require_binary_require_lua_start;
require_ret = luaL_loadbuffer(
L, &require_binary_require_lua_start, require_binary_require_lua_size, "_binary_require_lua" );
if ( require_ret != LUA_OK )
break;
require_ret = lua_pcall( L, 0, 1, 0 ); // return local:__internal_packages
if ( require_ret != LUA_OK )
break;
extern void pl_loadmodules( lua_State * state );
pl_loadmodules( L );
lua_pop( L, 1 ); // pop table:__internal_packages
} while ( 0 );
assert( require_ret == LUA_OK );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment