feat(runtime): embed platform modules as bytecode
ui.lua and hints.lua are compiled to LUA_32BITS bytecode (matching the firmware's Lua build) and linked into the binary. A new package.searchers entry checks them as the fallback after the SD card, so a local /.lua/lib/ui.lua still shadows the packaged one for debugging. Bytecode is ~40% smaller than source and loads without parsing. A fresh SD card with no make sdcard now has the platform available.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
// Dumps a Lua source file as bytecode, linked against the same vendored Lua the firmware
|
||||
// runs (LUA_32BITS), so the header matches. Stand-in for luac when only the library is
|
||||
// vendored.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "lauxlib.h"
|
||||
#include "lua.h"
|
||||
|
||||
static int writer(lua_State*, const void* p, size_t size, void* ud) {
|
||||
return fwrite(p, 1, size, (FILE*)ud) != size ? 1 : 0;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "usage: %s input.lua output.luac\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
lua_State* L = luaL_newstate();
|
||||
if (luaL_loadfilex(L, argv[1], "t") != LUA_OK) {
|
||||
fprintf(stderr, "%s: %s\n", argv[1], lua_tostring(L, -1));
|
||||
lua_close(L);
|
||||
return 1;
|
||||
}
|
||||
FILE* out = fopen(argv[2], "wb");
|
||||
if (!out) {
|
||||
fprintf(stderr, "cannot open %s\n", argv[2]);
|
||||
lua_close(L);
|
||||
return 1;
|
||||
}
|
||||
const int err = lua_dump(L, writer, out, 1); // strip debug info
|
||||
fclose(out);
|
||||
lua_close(L);
|
||||
return err ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compiles lua/lib/*.lua to LUA_32BITS bytecode and generates a C++ source file embedding
|
||||
the arrays. The runtime's module searcher checks these as a fallback after the SD card, so
|
||||
a local copy always shadows the packaged one.
|
||||
|
||||
Usage:
|
||||
tools/embed.py <luac32> <lua_lib_dir> <output.cpp> [--check]
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import pathlib
|
||||
|
||||
HEADER = """\
|
||||
// GENERATED by tools/embed.py — do not edit. Run `make embed` to regenerate.
|
||||
#include <lua/embedded_modules.h>
|
||||
|
||||
namespace esp32lua {
|
||||
|
||||
"""
|
||||
|
||||
FOOTER = """
|
||||
|
||||
} // namespace esp32lua
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
check = "--check" in sys.argv
|
||||
args = [a for a in sys.argv[1:] if a != "--check"]
|
||||
if len(args) != 3:
|
||||
sys.exit("usage: embed.py <luac32> <lua_lib_dir> <output.cpp> [--check]")
|
||||
|
||||
luac32, lib_dir, out_path = args
|
||||
lib_dir = pathlib.Path(lib_dir)
|
||||
out_path = pathlib.Path(out_path)
|
||||
|
||||
sources = sorted(lib_dir.glob("*.lua"))
|
||||
arrays = []
|
||||
rows = []
|
||||
for src in sources:
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(suffix=".luac", delete=False) as tmp:
|
||||
tmp_path = tmp.name
|
||||
subprocess.run([luac32, str(src), tmp_path], check=True)
|
||||
bytecode = pathlib.Path(tmp_path).read_bytes()
|
||||
pathlib.Path(tmp_path).unlink()
|
||||
name = src.stem
|
||||
comma = ", ".join(f"0x{b:02x}" for b in bytecode)
|
||||
arrays.append(f"static const unsigned char {name}[] = {{{comma}}};\n")
|
||||
rows.append(f' {{"{name}", {name}, sizeof({name})}},')
|
||||
|
||||
footer = "const EmbeddedModule embedded_modules[] = {{\n{rows}\n}};\nconst size_t embedded_modules_count = {count};\n"
|
||||
generated = HEADER + "".join(arrays) + footer.format(rows="\n".join(rows), count=len(sources)) + FOOTER
|
||||
|
||||
if check:
|
||||
existing = out_path.read_text() if out_path.exists() else ""
|
||||
if existing != generated:
|
||||
sys.exit(f"{out_path} is stale; run `make embed`")
|
||||
return
|
||||
|
||||
out_path.write_text(generated)
|
||||
print(f"wrote {len(sources)} modules to {out_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user