Just want a runtime? A prebuilt release archive needs no toolchain at all - see "Get a runtime" in README.md. Build from source only when you must compile on the target box or want to get into the low levels.
This guide helps you build and use Lunet in your own application. No prior xmake experience required.
Lunet uses xmake as its build system. xmake is a lightweight, cross-platform build tool (similar to CMake or Make). You don't need to learn xmake in depth - just follow the commands below.
Install xmake:
# Linux / macOS (curl)
curl -fsSL https://xmake.io/install.sh | bash
# Or via package manager
# macOS: brew install xmake
# Ubuntu: add-apt-repository ppa:xmake-io/xmake && apt install xmakegit clone https://github.com/lua-lunet/lunet.git
cd lunet
xmake f -m release --lunet_trace=n --lunet_verbose_trace=n -y
xmake buildWhat this does:
xmake f= configure-m release= optimized build (fast, smaller binary)--lunet_trace=n= no debug tracing (recommended for production)-y= accept defaults without prompting
Output files:
build/<platform>/<arch>/release/lunet.so- the Lua modulebuild/<platform>/<arch>/release/lunet-run- standalone runner
Lunet ships optional native modules as separate xmake targets. Build only what you need.
Examples:
# Database drivers
xmake build lunet-sqlite3
xmake build lunet-mysql
xmake build lunet-postgres
# Outbound HTTPS client (libcurl)
xmake build lunet-httpcLUNET_BIN=$(find build -path '*/release/lunet-run' -type f | head -1)
"$LUNET_BIN" path/to/your_app.luaFor deployments where Lua source should not live on disk, enable script embedding in release builds:
xmake f -c -m release --lunet_embed_scripts=y --lunet_embed_scripts_dir=lua -y
xmake build lunet-binWhen enabled, lunet-run extracts the embedded script tree into a private temp directory at startup and prepends that location to package.path and package.cpath. The temp directory is removed again at shutdown.
Note: the entry script argument must be a safe relative path inside the embedded tree (for example main.lua). Absolute paths, .. segments, and scripts outside the blob are rejected - there is no fallback to running a file from disk in an embed build.
For release consumers who need their own native main() and an embedded Lua
application, see EMBEDDING.md instead of rebuilding Lunet as an
xmake subproject.
If you prefer to use luajit directly:
export LUA_CPATH="$(pwd)/build/$(xmake l print(os.host()))/$(xmake l print(os.arch()))/release/?.so;;"
luajit -e 'local lunet=require("lunet"); print(type(lunet))'If your app has its own xmake.lua, include Lunet as a subproject and link it
with add_deps("lunet"):
set_project("myapp")
set_languages("c99")
add_rules("mode.debug", "mode.release")
includes("lunet")
add_requires("pkgconfig::luajit", "pkgconfig::libuv")
target("myapp")
set_kind("binary")
add_files("src/*.c")
add_deps("lunet")
add_packages("luajit", "libuv")
target_end()Lunet keeps the Lua module output as lunet.so and also emits a compatibility
liblunet.so for toolchains that link dependencies via -l<name>.
| Profile | Use case | Command |
|---|---|---|
| Release | Production, best performance | xmake f -c -m release --lunet_trace=n --lunet_verbose_trace=n -y |
| Debug + trace | Development, catches bugs | xmake f -c -m debug --lunet_trace=y --lunet_verbose_trace=n -y |
| Verbose trace | Detailed debugging, logs every event | xmake f -c -m debug --lunet_trace=y --lunet_verbose_trace=y -y |
| ASan + EasyMem | Memory bugs (ASan + allocator integrity diagnostics) | xmake f -c -m debug --lunet_trace=y --asan=y -y |
Tip: Use -c to force a clean reconfigure when switching profiles.
If your app uses Lunet and you run CI (e.g. GitHub Actions), test with these profiles:
- Release -
--lunet_trace=n --lunet_verbose_trace=n - Debug trace -
--lunet_trace=y --lunet_verbose_trace=n - Verbose trace -
--lunet_trace=y --lunet_verbose_trace=y - ASan + EasyMem -
--asan=y --lunet_trace=y
This catches most lifecycle and coroutine issues early.
xmake testThis runs three steps in order:
xmake check-types- validates the LuaCATS annotations intypes/with lua-language-server. Type errors abort the run before any test executes.xmake build lunet-httpc- builds the outbound HTTP client C module sospec/httpc_spec.luaalways runs; a platform that cannot build it fails here instead of skipping.busted spec/- the Lua suite:spec/httpc_spec.lua(tests against thelunet.httpcC module) andspec/lint_spec.lua(runsluajit bin/lint_c_safety.lua).
Output is teed to .tmp/logs/<timestamp>/busted.txt.
lunet's C modules link libluajit-5.1, so loading one into a PUC Lua process puts two incompatible Lua runtimes in one address space: the suite segfaults on Linux and hangs on macOS. The busted wrapper on PATH cannot be trusted to avoid that - luarocks bakes whichever interpreter it detected at install time into the wrapper (exec /usr/bin/lua5.1 … bin/busted on Debian/Ubuntu even when installed with --lua-dir pointing at LuaJIT; lua5.5 under Homebrew).
xmake test therefore ignores the wrapper and launches busted's Lua entry point itself:
luajit bin/busted_runner.lua spec/
LUA_PATH / LUA_CPATH are seeded from luarocks --lua-version=5.1 path, so busted is found in the 5.1 user and system trees regardless of the interpreter's own default paths. Overrides: $LUNET_LUAJIT (interpreter) and $LUNET_BUSTED (path to busted's Lua entry script).
Any test left in busted's pending state fails the whole run:
error: busted reported N pending test(s) -- pending tests are forbidden ...
For local iteration only, set LUNET_ALLOW_PENDING=1 to bypass the guard - never set it in CI. (The guard parses the captured busted output, so it applies on the Unix path only.)
xmake check-typesRuns lua-language-server --check over types/ with the project .luarc.json and parses the JSON report as the gate (LuaLS 3.15+ exits 0 even when it reports diagnostics). Its meta cache (--metapath) is pointed at a writable per-run directory under .tmp/logs/; without this LuaLS writes into its own install directory, which is root-owned in CI and silently drops all built-in types (hundreds of bogus undefined-doc-name errors).
The ubuntu-latest build job in .github/workflows/build.yml runs the suite via make test (the Makefile test target delegates to xmake test).
Use the built-in EasyMem preflight task to run the same fast leak/smoke checks locally that CI uses for the EasyMem+ASan profile:
xmake preflight-easy-memoryWhat it does:
- configures
debug + trace + ASan + EasyMem - builds
lunet-binand DB modules (MySQL/Postgres optional if deps are missing locally) - runs
test/ci_easy_memory_db_stress.lua - runs
test/ci_easy_memory_lsan_regression.lua - writes all step logs to
.tmp/logs/YYYYMMDD_HHMMSS/easy_memory_preflight/
Lunet supports EasyMem/easy_memory as an allocator backend.
EasyMem is enabled automatically when either of these is enabled:
--lunet_trace=y--asan=y
Enable EasyMem explicitly in a development profile without enabling trace:
xmake f -c -m debug --lunet_trace=n --lunet_verbose_trace=n --easy_memory=y -y
xmake buildRelease builds should keep EasyMem stripped by default.
Tune EasyMem arena capacity in MB:
xmake f --easy_memory_arena_mb=256 -yDefault is 128 MB (minimum clamped to 8 MB).
For deep memory debugging (LuaJIT + Lunet both instrumented), use the macOS-only helpers:
xmake luajit-asan
xmake build-debug-asan-luajit
xmake repro-50-asan-luajitThese helpers configure --asan=y --lunet_trace=y, so EasyMem is also enabled automatically.
LuaJIT version pins are in xmake.lua. Override if needed:
xmake f --luajit_snapshot=2.1.0+openresty20250117 --luajit_debian_version=2.1.0+openresty20250117-2 -y| Problem | Solution |
|---|---|
xmake: command not found |
Install xmake (see "What is xmake?" above) |
libuv not found |
Install: apt install libuv1-dev (Linux), brew install libuv (macOS) |
luajit not found |
Install: apt install libluajit-5.1-dev (Linux), brew install luajit (macOS) |
| Build fails after changing options | Run xmake f -c -y then reconfigure |
| Wrong architecture | Use xmake f -a arm64 (or x64) to target a specific arch |
--asan=y fails on a specific Windows toolchain |
Ensure your MSVC/clang-cl version supports /fsanitize=address |
Debug tracing adds roughly 7–8% overhead in typical workloads. Use release builds (--lunet_trace=n) for production.