What's lua-wrap do? It wraps Lua/Fennel projects in a C executable with any libraries you want to include with your Lua project. Libraries are statically linked into the wrapper and any Lua sources are directly embedded into it and you are left with a single executable that only relies on your system C library. Included by default are a couple common Lua libraries to get projects started and give linking examples.
The goal is to allow easy embedding and distribution of applications written in Lua/Fennel. Who doesn't love shipping a project as a single executable with little to no dependencies?
This is something that I put together for some personal projects that I thought others might find useful. Feedback and pull requests are welcome!
- C Compiler
- gcc (11.5.0+; tested on Alma Linux 9)
- clang (17.0.0; tested on macOS 15.5)
- MSVC (untested)
- git
This project has a few submodules, if you run into any build/linking issues make sure they
are populated in vendor/.
git clone https://github.com/grant-wade/lua-wrap.git --recursivecd lua-wrapFirst we compile our driver. Feel free to take a look at build.c, it
brings together some tools for us to put this project together. cbuild.h is
a mostly declarative build system focused on C, but is adaptable enough to fit
in as the compiler driver for lua-wrap.
cbuild is my, obviously biased, favorite C build system.
This repo may or may not have a never version, I redeisgned some parts of it
for the codegen file dependencies this projects build.c uses and have yet
to test everything and update the live version.
gcc -o cbuild build.cThe driver is configured to setup the build envrionment by first building
a copy of Lua that it can use for some scripting and then it has everything
it needs to build the project in src/ into a single executable.
The driver is configured to compile any Fennel code to Lua, copy Lua sources, then build a C source file with all the Lua embeded inline. These Lua files are loaded at start to the package preload before the main entrypoint. With any statically linked library having their load method exposed in the executable for Lua.
Checkout the scripts/ directory to see how Fennel is pre-compiled and how
the wrapper.c is put together for the final executable.
./cbuildClick to view output
COMPILE vendor/lua/onelua.c
LINK build/lua
COMPILE vendor/lua/onelua.c
LINK build/liblua_static.a
FILE_DEP Updating/Ensuring file: build/wrapper.c
COMMAND Compile:Fennel
COMMAND Build fennel
Compiling src/fs.fnl -> build///fs.lua
Compiling src/main.fnl -> build///main.lua
Compilation complete!
COMMAND Lua:CopySource
COMMAND Lua:CopyLuaSocketLua
COMMAND CodeGen:wrapper.c
COMMAND Make wrapper.c
Generated C file: build/wrapper.c
FILE_DEP build/wrapper.c (updated)
COMPILE vendor/isocline/src/isocline.c
LINK build/libisocline.a
COMPILE vendor/luafilesystem/src/lfs.c
LINK build/liblfs.a
COMPILE vendor/lua-cjson/strbuf.c
COMPILE vendor/lua-cjson/lua_cjson.c
COMPILE vendor/lua-cjson/fpconv.c
LINK build/libcjson.a
COMPILE vendor/luasocket/src/auxiliar.c
COMPILE COMPILE vendor/luasocket/src/except.c
vendor/luasocket/src/compat.c
COMPILE vendor/luasocket/src/buffer.c
COMPILE vendor/luasocket/src/inet.c
COMPILE vendor/luasocket/src/io.c
COMPILE vendor/luasocket/src/luasocket.c
COMPILE vendor/luasocket/src/mime.c
COMPILE vendor/luasocket/src/options.c
COMPILE vendor/luasocket/src/select.c
COMPILE vendor/luasocket/src/serial.c
COMPILE vendor/luasocket/src/tcp.c
COMPILE vendor/luasocket/src/timeout.c
COMPILE vendor/luasocket/src/udp.c
COMPILE vendor/luasocket/src/unix.c
COMPILE vendor/luasocket/src/unixdgram.c
COMPILE vendor/luasocket/src/unixstream.c
COMPILE vendor/luasocket/src/usocket.c
LINK build/libluasocket.a
COMPILE build/wrapper.c
LINK build/wrapper
✔ Build succeeded.You can run the wrapper with the driver as well using the run subcommand
./cbuild run
FILE_DEP build/wrapper.c (up-to-date)
SUBCMD Running 'run': build/wrapper
~ main.fnl starting: hello! ~This is equivalent if there are no file changes:
./build/wrapper
~ main.fnl starting: hello! ~The wrapper includes a Lua REPL as well as a Fennel REPL. It has similar caveots to the Lua REPL with global/local variables. Both run in the Lua environment after the entrypoint script has been evaluated.
./build/wrapper --repl
~ main.fnl starting ~
its a string my dude
~ main.fnl ending ~
Lua REPL (type 'exit' to quit)
>>> socket = require "socket"
>>> sd = socket.bind("*", 0)
>>> print(sd)
tcp{server}: 0x55d2d1fe77f8
>>>./build/wrapper --fennel
~ main.fnl starting ~
its a string my dude
~ main.fnl ending ~
Fennel REPL (type 'exit' or 'quit' to leave)
>>> (global socket (require :socket))
nil
>>> (global sd (socket.bind "*" 0))
nil
>>> (print sd)
tcp{server}: 0x560a0b78a618Right now this project only supports modern PUC Lua but plans to expand to other Lua versions and LuaJit/Luau in the future.
All the following Lua libraries are aviable in the embedded runtime by
require("name") in Lua or (require "name") in Fennel
- Configure
build.cfor Windows builds (cbuild.hmay need some changes as well asbuild.c) - Expand support to other Lua versions (LuaJit, Luau, Lua 5.1+)
- Setup some docs on how to use this project as a template
- Integrate Fennel REPL with Isocline for better scope handling (requires
(global name val)right now in REPL) - Make statically linked Lua Libraries optional
- Explore integrating LuaRocks into the environment
I found srlua and luastatic after building this project and they both look like good solutions that solve this same problem.