Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ec14541
refactor: 重构嵌入数据生成与引用机制
actboy168 Apr 17, 2026
8697b2d
refactor: 重构嵌入系统,支持groups配置与模块化生成
actboy168 Apr 17, 2026
18f5d98
chore: 为bee_glue启用添加组校验与断言,防编译缺失
actboy168 Apr 17, 2026
d59cf31
Update skills/SKILL.md
actboy168 Apr 17, 2026
2937791
refactor: 优化路径处理与输出目录创建逻辑
actboy168 Apr 17, 2026
0d118c8
feat: 支持 bee_glue 预加载组自动模块名扫描
actboy168 Apr 17, 2026
a27adb5
refactor: 移除 bee_glue.c 中预加载模块逻辑
actboy168 Apr 17, 2026
35ffa27
refactor: 重构标识符生成,支持哈希防撞与统一处理非法字符
actboy168 Apr 17, 2026
6c14fec
docs: 为 l_embed_index 函数添加设计与性能注释
actboy168 Apr 17, 2026
ca603c8
feat: 优化 Lua 模块扫描与覆盖逻辑,避免 C 代码名称冲突
actboy168 Apr 17, 2026
af52dab
docs: 补充 lua_embed_data.h 相关说明并更新引用
actboy168 Apr 17, 2026
b01239d
feat: 新增并完善 lm:lua_embed 高阶嵌入与目标文档
actboy168 Apr 17, 2026
6b1b2e6
refactor: 改用全名拼接C标识符并移除冗余转换,提升唯一性
actboy168 Apr 17, 2026
79e98b7
feat: 完善bee_glue布尔校验与编译判断逻辑
actboy168 Apr 17, 2026
bcfe6c7
feat: 支持无分组场景,生成头文件时添加占位成员并替换遍历逻辑
actboy168 Apr 17, 2026
3a18146
chore: 修正 Lua 嵌入脚本引用路径为统一相对路径
actboy168 Apr 17, 2026
c7e1983
docs: 完善 Lua 嵌入说明并修正 main 组入口脚本索引
actboy168 Apr 17, 2026
9e41de9
docs: 在README末尾新增Documentation章节及编写make.lua指南链接
actboy168 Apr 17, 2026
655ffde
feat: 支持宿主预加载模块并完善生成文件注释
actboy168 Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
/.ninja_deps
/tools/*/lua.def
/compile/lua/*.c
/.claude/
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,7 @@ Run lua file.
> luamake test

Equivalent to `luamake lua test.lua`

## Documentation

See [`skills/SKILL.md`](skills/SKILL.md) for the full guide on writing `make.lua`.
233 changes: 123 additions & 110 deletions scripts/lua_embed.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,23 @@
-- Helper module for the lm:lua_embed target (registered in writer.lua).
-- Provides: config file generation and input file collection.

local fs = require "bee.filesystem"
local fsutil = require "fsutil"
local fs = require "bee.filesystem"
local fsutil = require "fsutil"
local pathutil = require "pathutil"

-- Directory containing lua_embed_gen.lua, lua_embed.h, etc.
local EMBED_DIR = fsutil.join(package.procdir, "scripts", "lua_embed")
local GEN_SCRIPT = fsutil.join(EMBED_DIR, "lua_embed_gen.lua")
local HEADER = fsutil.join(EMBED_DIR, "lua_embed.h")

local BEE_GLUE = fsutil.join(EMBED_DIR, "bee_glue.c")
local BEE_GLUE = fsutil.join(EMBED_DIR, "bee_glue.c")

local m = {}
m.GEN_SCRIPT = GEN_SCRIPT
m.HEADER = HEADER
m.EMBED_DIR = EMBED_DIR
m.BEE_GLUE = BEE_GLUE

-- Recursively collect files under dirpath.
-- When lua_only=true, only *.lua files are collected.
local function resolve_path(rootdir, p)
return rootdir and pathutil.tostr(rootdir, p) or p
end

-- Recursively collect files under dirpath (lua_only filters to *.lua).
function m.scan_inputs(dirpath, lua_only)
local result = {}
local root = fs.path(dirpath)
Expand All @@ -42,142 +40,157 @@ function m.scan_inputs(dirpath, lua_only)
return result
end

-- 将 dir/file 路径相对于 rootdir 解析为绝对路径
local function resolve_path(rootdir, p)
if rootdir then
return pathutil.tostr(rootdir, p)
-- Serialize one entry { dir=, prefix=, pattern= } or { file=, name= }.
local function serialize_entry(e, rootdir)
local parts = {}
if e.dir then
parts[#parts+1] = "dir = " .. string.format("%q", resolve_path(rootdir, e.dir))
if e.prefix and e.prefix ~= "" then
parts[#parts+1] = "prefix = " .. string.format("%q", e.prefix)
end
if e.pattern then
parts[#parts+1] = "pattern = " .. string.format("%q", e.pattern)
end
elseif e.file then
parts[#parts+1] = "file = " .. string.format("%q", resolve_path(rootdir, e.file))
parts[#parts+1] = "name = " .. string.format("%q",
assert(e.name, "entry with 'file' requires 'name'"))
end
return p
return " { " .. table.concat(parts, ", ") .. " },"
end

-- Write a config.lua file for lua_embed_gen.lua to dofile().
-- attribute: the lm:lua_embed attribute table.
-- rootdir: (optional) root directory for resolving relative paths in attribute.
-- Returns the path of the written config file.
--
-- 只在内容实际变化时才写入文件,避免时间戳更新导致 Ninja 不必要的重建。
-- A group uses lua_mode (module-name scanning) when:
-- 1. any dir-entry in the group has 'pattern', or
-- 2. bee_glue is enabled and this is the 'preload' group
-- (the _PRELOAD hard-coded contract requires lua module names,
-- not raw filenames, as keys).
local function group_lua_mode(grp, grp_name, bee_glue)
if bee_glue and grp_name == "preload" then return true end
for _, e in ipairs(grp) do
if type(e) == "table" and e.pattern then return true end
end
return false
end

-- Write config.lua for lua_embed_gen.lua.
-- Groups are derived from attribute.data string keys, sorted alphabetically.
-- Writes only when content changes (avoids unnecessary Ninja rebuilds).
function m.write_config(outdir, attribute, rootdir)
fs.create_directories(outdir)
local config_path = outdir .. "/config.lua"

local lines = {}
lines[#lines+1] = "-- auto-generated lua_embed config"
lines[#lines+1] = "return {"

if attribute.bee_glue then
-- bee_glue 文件路径写入配置,生成器会将其嵌入 lua_embed.c 中
-- (通过 lua_embed_get_main() 访问,不暴露到 data_table)
local main_path = resolve_path(rootdir, attribute.bee_glue)
lines[#lines+1] = string.format(" main = %q,", main_path)
local lines = {
"-- auto-generated lua_embed config",
"return {",
" groups = {",
}

local data = attribute.data or {}

-- Normalize bee_glue: accept only boolean / nil. Anything else (string,
-- number, table, ...) is almost certainly a user mistake, so fail fast
-- with a clear message instead of silently (mis)interpreting it.
local bee_glue = attribute.bee_glue
assert(bee_glue == nil or type(bee_glue) == "boolean",
string.format("lua_embed: bee_glue must be a boolean, got %s", type(bee_glue)))

-- When bee_glue is enabled, bee_glue.c references lua_embed.{main,preload,data}
-- by name. These groups must be declared (even if empty) so the generated
-- lua_embed_bundle struct contains the corresponding fields; otherwise
-- bee_glue.c will fail to compile with "struct has no member" errors.
if bee_glue == true then
for _, required in ipairs({ "main", "preload", "data" }) do
assert(type(data[required]) == "table",
string.format(
"lua_embed: bee_glue requires group %q to be defined (use an empty table {} if unused)",
required))
end
end

if attribute.bytecode then
lines[#lines+1] = " bytecode = true,"
local order = {}
for k in pairs(data) do
if type(k) == "string" then order[#order+1] = k end
end

-- preload
lines[#lines+1] = " preload = {"
for _, e in ipairs(attribute.preload or {}) do
if e.dir then
local dir = resolve_path(rootdir, e.dir)
local s = " { dir = " .. string.format("%q", dir)
if e.prefix and e.prefix ~= "" then
s = s .. ", prefix = " .. string.format("%q", e.prefix)
end
if e.pattern then
s = s .. ", pattern = " .. string.format("%q", e.pattern)
end
s = s .. " },"
lines[#lines+1] = s
elseif e.file then
local file = resolve_path(rootdir, e.file)
lines[#lines+1] = " { file = " .. string.format("%q", file)
.. ", name = " .. string.format("%q",
assert(e.name, "preload file entry requires 'name'"))
.. " },"
table.sort(order)

for _, grp_name in ipairs(order) do
local grp = data[grp_name]
if type(grp) ~= "table" then goto continue end
assert(grp_name:match("^[%a_][%w_]*$"),
string.format("lua_embed group name %q is not a valid C identifier", grp_name))

lines[#lines+1] = " {"
lines[#lines+1] = string.format(" name = %q,", grp_name)
if grp.bytecode then
lines[#lines+1] = " bytecode = true,"
end
end
lines[#lines+1] = " },"

-- data
lines[#lines+1] = " data = {"
for _, e in ipairs(attribute.data or {}) do
if e.dir then
local dir = resolve_path(rootdir, e.dir)
local s = " { dir = " .. string.format("%q", dir)
if e.prefix then
s = s .. ", prefix = " .. string.format("%q", e.prefix)
if group_lua_mode(grp, grp_name, bee_glue == true) then
lines[#lines+1] = " lua_mode = true,"
end
lines[#lines+1] = " entries = {"
for _, e in ipairs(grp) do
if type(e) == "string" then
local abspath = resolve_path(rootdir, e)
local fname = abspath:match("[^/\\]+$") or e
lines[#lines+1] = " { file = "
.. string.format("%q", abspath)
.. ", name = " .. string.format("%q", fname) .. " },"
elseif type(e) == "table" then
lines[#lines+1] = serialize_entry(e, rootdir)
end
s = s .. " },"
lines[#lines+1] = s
elseif e.file then
local file = resolve_path(rootdir, e.file)
lines[#lines+1] = " { file = " .. string.format("%q", file)
.. ", name = " .. string.format("%q",
assert(e.name, "data file entry requires 'name'"))
.. " },"
end
lines[#lines+1] = " },"
lines[#lines+1] = " },"
::continue::
end
lines[#lines+1] = " },"

lines[#lines+1] = " },"
lines[#lines+1] = "}"
lines[#lines+1] = "" -- 末尾换行
lines[#lines+1] = ""

local new_content = table.concat(lines, "\n")

-- 只在内容变化时写入,避免时间戳更新导致不必要的重建
local old_f = io.open(config_path, "rb")
if old_f then
local old_content = old_f:read "*a"
local old = old_f:read "*a"
old_f:close()
if old_content == new_content then
return config_path
end
if old == new_content then return config_path end
end

local f = assert(io.open(config_path, "wb"),
"cannot write config: " .. config_path)
local f = assert(io.open(config_path, "wb"), "cannot write config: " .. config_path)
f:write(new_content)
f:close()
return config_path
end

-- Collect all input files (for ninja dependency tracking).
-- 除了 Lua 源文件和生成器脚本外,config.lua 也作为输入被追踪,
-- 这样当用户修改 lua_embed 选项(bytecode/pattern/prefix/bee_glue 等)时,
-- Ninja 能检测到配置变化并重新生成 lua_embed.c。
-- write_config 已做内容比对,内容不变时不会更新文件时间戳,
-- 因此不会导致不必要的重建。
-- rootdir: (optional) root directory for resolving relative paths in attribute.
-- Collect all input files for Ninja dependency tracking.
function m.collect_inputs(attribute, rootdir, config_path)
local inputs = { GEN_SCRIPT }
if config_path then
inputs[#inputs+1] = config_path
end
for _, e in ipairs(attribute.preload or {}) do
if e.dir then
local dir = resolve_path(rootdir, e.dir)
for _, p in ipairs(m.scan_inputs(dir, true)) do
inputs[#inputs+1] = p
end
elseif e.file then
inputs[#inputs+1] = resolve_path(rootdir, e.file)
end
end
for _, e in ipairs(attribute.data or {}) do
if e.dir then
local dir = resolve_path(rootdir, e.dir)
for _, p in ipairs(m.scan_inputs(dir, false)) do
inputs[#inputs+1] = p

local data = attribute.data or {}
local bee_glue = attribute.bee_glue == true
for grp_name, grp in pairs(data) do
if type(grp_name) ~= "string" or type(grp) ~= "table" then goto continue end
local lua_mode = group_lua_mode(grp, grp_name, bee_glue)
for _, e in ipairs(grp) do
Comment thread
actboy168 marked this conversation as resolved.
if type(e) == "string" then
inputs[#inputs+1] = resolve_path(rootdir, e)
elseif type(e) == "table" then
if e.dir then
local lua_only = lua_mode or (e.pattern ~= nil)
for _, p in ipairs(m.scan_inputs(resolve_path(rootdir, e.dir), lua_only)) do
inputs[#inputs+1] = p
end
elseif e.file then
inputs[#inputs+1] = resolve_path(rootdir, e.file)
end
end
elseif e.file then
inputs[#inputs+1] = resolve_path(rootdir, e.file)
end
::continue::
end
-- bee_glue 文件也需要追踪(会嵌入到 lua_embed.c 中)
if attribute.bee_glue then
inputs[#inputs+1] = resolve_path(rootdir, attribute.bee_glue)
end

table.sort(inputs)
return inputs
end
Expand Down
Loading
Loading