GitHub - Water-Run/luainstaller: A tool that packages Lua scripts into a binary executable. Supports Windows and Linux.

7 min read Original article ↗

中文

luainstaller is a tool that packages Lua projects into distributable executables, supporting Windows and Linux. It is open-sourced on GitHub and licensed under LGPL.

luainstaller provides dependency analysis and single-file bundling capabilities, and can package non-pure-Lua content inside the wrapper program. It is important to note that luainstaller guarantees that the packaged binary will run on the same system environment as yours (excluding the lua environment itself).

luainstaller was previously provided as a Python library. Older versions were out-of-the-box and cross-platform, but could only bundle pure Lua scripts. (See the deprecated-python-lib branch)


Installation

Install via luarocks:

luarocks install luainstaller

Environment Dependencies

Before running, ensure the following dependencies are installed:

  • luastatic
  • A GCC-compatible C toolchain (e.g. gcc / mingw-w64 gcc)
  • windres (Windows only; required when using --icon)

Usage

luainstaller can be used as a CLI tool or invoked directly from Lua scripts.


Command-Line Tool (CLI)

CLI command name: luainstaller

Show help:

luainstaller v0.1.0

Usage:
  luainstaller bundle <entry.lua> [options]
  luainstaller analyze <entry.lua> [options]
  luainstaller version

Options:
  ...

On Linux, you can also use man luainstaller to view the full manual (if the manpage is installed).


bundle — Bundling

bundle is the most commonly used command, used to package a Lua project into an executable.

Default mode (outputs a directory):

luainstaller bundle <path_to_lua_entry_file>
success.
<entry.lua> => <output_dir>/

By default, luainstaller performs static dependency analysis starting from the entry .lua file and outputs all required runtime files into a directory.

Single-file mode (--onefile, outputs a single executable):

luainstaller bundle <path_to_lua_entry_file> --onefile
success.
<entry.lua> => <output_file>

--onefile further wraps the directory bundle output into a single executable file.

Optional parameters:

Option Description Example
-o, --out <path> Output path: directory in default mode; file when --onefile is enabled --out ../dist/ / --onefile --out ../dist/app.exe
--onefile Generate a single-file executable (disabled by default) luainstaller bundle main.lua --onefile
-v, --verbose Print more detailed analysis and bundling logs luainstaller bundle main.lua --verbose
--max-deps <n> Maximum number of dependencies (default: 36) luainstaller bundle main.lua --max-deps 100
--include <path> Manually include dependencies (repeatable; for dynamic require and other cases static analysis cannot detect) --include ./require.lua --include ./plugin.lua
--exclude <path> Manually exclude dependencies (repeatable; to remove false positives such as pcall(require, ...); takes priority over --include) luainstaller bundle main.lua --exclude ./test_utils.lua
--no-depscan Disable dependency scanning, entering fully manual mode (all dependencies must be specified via --include) --no-depscan --include ./a.lua --include ./b.lua
--icon (Windows only) Set the application icon (.ico file) luainstaller bundle main.lua --onefile --icon ./logo.ico

analyze — Dependency Analysis

Perform dependency analysis only, without bundling:

luainstaller analyze <path_to_lua_entry_file>
success.
N dependencies found:
  1) ...
  2) ...

Lua API

The Lua API uses the same parameter semantics as the CLI. Dependency scanning is enabled by default; set depscan = false to disable it.

local luainstaller = require("luainstaller")

bundle — Bundling

Function signature

local ok, out = luainstaller.bundle(opts)

Return values: when ok=true, out is the output path; when ok=false, out is an error message string.

Parameters (opts table)

Parameter Type Default Description
entry string required Entry script path
out string Output path (directory path when onefile=false; file path when onefile=true)
onefile boolean false Single-file mode switch
verbose boolean false Whether to output detailed logs
max_deps number 36 Maximum number of dependencies
include string[] {} Manually include dependencies (for dynamic require and other cases that cannot be auto-detected)
exclude string[] {} Manually exclude dependencies (to remove false positives; takes priority over include)
depscan boolean true Whether to enable automatic dependency analysis (false is equivalent to CLI --no-depscan)
icon string Application icon path (.ico file, Windows only)

Examples

-- Simplest bundling: outputs a directory by default, automatic dependency scanning
local ok, out = luainstaller.bundle({ entry = "main.lua" })
if ok then print("Bundle succeeded: " .. out)
else      print("Bundle failed: " .. out) end

-- Single-file mode
local ok, out = luainstaller.bundle({
  entry   = "main.lua",
  onefile = true,
  out     = "../dist/app.exe",
})

-- Manual dependency management
local ok, out = luainstaller.bundle({
  entry   = "main.lua",
  include = { "./lib/plugin.lua", "./lib/config.lua" },  -- add deps that static analysis cannot detect
  exclude = { "./test/test_utils.lua" },                 -- remove false positive deps
})

-- Fully manual mode: disable automatic analysis, specify all dependencies manually
local ok, out = luainstaller.bundle({
  entry   = "main.lua",
  depscan = false,
  include = { "./module1.lua", "./module2.lua" },
})

-- Full example with all parameters
local ok, out = luainstaller.bundle({
  entry    = "src/main.lua",
  out      = "build/",
  onefile  = false,
  verbose  = true,
  max_deps = 50,
  include  = { "plugins/extra.lua" },
  exclude  = { "test/mock.lua" },
  depscan  = true,
  icon     = "./logo.ico",    -- Windows only
})
if ok then print("Bundle succeeded: " .. out)
else      print("Bundle failed: " .. out) end

analyze — Dependency Analysis

Function signature

local ok, deps = luainstaller.analyze(entry, opts)

Return values: when ok=true, deps is an array of dependency path strings (string[]); when ok=false, deps is an error message string.

Parameters

Parameter Type Default Description
entry string required Entry script path
opts.max_deps number 36 Maximum number of dependencies
opts.depscan boolean true Whether to enable automatic dependency analysis (false disables automatic scanning)

Examples

-- Basic usage
local ok, deps = luainstaller.analyze("main.lua")
if ok then
  print("Found " .. #deps .. " dependencies")
  for i, dep in ipairs(deps) do
    print(string.format("  %d) %s", i, dep))
  end
else
  print("Analysis failed: " .. deps)
end

-- Full example with all parameters
local ok, deps = luainstaller.analyze("src/main.lua", {
  max_deps = 50,
  depscan  = true,
})
if ok then
  for i, dep in ipairs(deps) do
    print(string.format("  %d) %s", i, dep))
  end
else
  print("Analysis failed: " .. deps)
end

analyze only performs dependency analysis and produces no bundling artifacts. It is suitable for pre-checking whether all dependencies are complete and whether any false positives exist before performing the actual bundle.


version — Get Version

print("luainstaller version: " .. luainstaller.version())

How It Works

The workflow of luainstaller can be summarized as: analyze entry script → collect dependencies → build executable → optionally wrap into a single file.

When bundle is executed, luainstaller first performs static dependency analysis starting from the entry .lua file. It scans the source code for common reference patterns such as require(...), recursively finding all Lua files the project depends on. For cases that static analysis cannot fully cover — such as runtime module name concatenation or conditional module loading — dependencies can be supplemented manually via --include and false positives can be removed via --exclude.

Once the dependency set is determined, luainstaller organizes the entry script and the collected Lua code into a bundle set ready for packaging. For pure Lua projects, this step typically only involves the scripts themselves; for projects containing non-pure-Lua content, additional files must also be handled to ensure the final artifact runs correctly on the same system environment.

In the executable generation stage, luainstaller uses luastatic as its core backend, paired with a GCC-compatible toolchain to complete compilation and linking. The fundamental approach is not to rely on a pre-installed lua environment on the target machine to run scripts, but instead to construct the Lua program and related content into a directly distributable program. On Windows, if --icon is specified, windres is additionally used to compile icon resources into the final executable.

By default, luainstaller uses directory output mode: it generates an output directory containing the executable and required files. When --onefile is enabled, it further performs single-file packaging on top of this, bundling all runtime content into a single standalone executable for easier distribution and deployment.

Overall, luainstaller's technology stack remains fairly straightforward: the Lua side handles the CLI, dependency analysis, and bundling process control; luastatic handles the static integration of the Lua program; the GCC-compatible toolchain handles final compilation and linking; and windres handles icon and other resource embedding on Windows.

The overall process can be summarized as:

[entry.lua]
     |
     v
[Static Dependency Analysis]
     |
     v
[Collect Lua files / manual --include / --exclude]
     |
     v
[Organize bundle content]
     |
     v
[luastatic + GCC compile & link]
     |
     +----------------------+
     |                      |
     v                      v
[Directory mode output]   [--onefile single-file packaging]
     |                      |
     +----------+-----------+
                |
                v
      [Distributable executable]