# Build & Test Systems

## Build system

Lilush uses a custom build generator (`buildgen/generate.lua`) that compiles
Lua modules into LuaJIT bytecode, links them with C libraries, and produces
a single static binary. The generator itself runs on plain LuaJIT with no
external Lua dependencies.

Docker is used purely as a build medium — it provides the Alpine toolchain
and LuaJIT, then runs the generator. No runtime images are produced.

## Testing system

Lilush uses a minimal custom test framework called `testimony` (`src/testimony/`) for
regression prevention and refactoring safety. 

Tests live in `tests/` and organized into subdirs per module.

## Building

```
./build.bash [app]
```

`app` defaults to `lilush`. In-tree apps: `lilu`, `lilush`
(each has a `buildgen/apps/<app>.lua` config).
The built binary is placed in the repo root. 
To install system-wide, do `sudo mv [app] /usr/[local]/bin/`

### Building an external LILPACK into a custom binary

Some apps live in their own LILPACK repos rather than in-tree (e.g. RECALL),
yet can still be compiled into a custom, tailored static binary instead of
being installed at runtime:

```
./build.bash --pack <git-url> --ref <tag-or-sha> --name <binary>
```

The pack is cloned at `--ref` (use an immutable tag or commit SHA) and built
from its own `build/<name>.lua` descriptor. The pack's own Lua modules are
auto-scanned from its tree (names derived from file paths, exactly like the
LILPACK packer); the descriptor only lists the *shared* lilush module groups
(`luamods`/`c_libs`) it depends on. The same repo can also be `lilpack pack`ed
into a signed `.lilpack` for runtime install — one source, two outputs.

A pack binary reports its **own** version via `-v`, taken from the pack's
`lilpack.json`, alongside the lilush toolchain it was built against — e.g.
`recall -v` prints `recall 0.3.1 (lilush 0.8.5)`. In-tree builds (`lilush`,
`lilu`) keep the plain `version 0.8.5` form.

## Running Tests

### Testing after recent changes

After making changes, build lilush and run: 

* For running all tests (requires the binary to be in the repo root, which is default after build):
  ```bash
  ./run_tests.bash
  ```
* You can also use a subdirectory name to run all tests for a module:
  ```
  ./run_tests.bash std
  ./run_tests.bash term
  ```
* Or run individual test files:
  ```bash
  ./lilush tests/std/test_tbl.lua
  ```

## File layout

```
Dockerfile                          Docker build template (takes APP build arg)
build.bash                          Build entry point
buildgen/
  generate.lua                      Build orchestrator (runs under luajit)
  c_tmpl                            C source template with preload infrastructure
  modinfo.lua                       Module registry (Lua module names -> C identifiers)
  default_main.c                    Default main() for apps without a custom one
  apps/
    version                         Lilush toolchain version (drives {{LILUSH_VERSION}} and the {{VERSION_STRING}} shown by -v)
    lilush.lua                      App config for lilush
    lilu.lua                        App config for lilu
  entrypoints/
    <app>/
      *.lua                         Lua entrypoint code (embedded as C string constants)
      main.c                        Custom main() (optional, per-app)
```

## App configs

Each app config (`buildgen/apps/<app>.lua`) returns a table:

- `binary` — output binary name
- `luamods` — list of Lua module groups to include (keys into `modinfo.lua`)
- `c_libs` — list of C library groups to include (keys into `modinfo.lua`)
- `start_code` — table mapping C constant names to `.lua` file paths under
  `buildgen/entrypoints/`. Each file is read, escaped, and emitted as a
  `static const char NAME[] = "...";` declaration.
- `custom_main` (optional) — path to a `.c` file under `buildgen/entrypoints/`
  containing the app's `main()`. If absent, `buildgen/default_main.c` is used.

## Build pipeline

What `generate.lua` does, in order:

1. **Load config** — reads the app config and `modinfo.lua`
2. **Generate entrypoint constants** — reads each `.lua` file referenced in
   `start_code`, escapes it for C, and emits `static const char` declarations
3. **Compile C modules** — runs `make -C src/<lib>` for each C library,
   strips debug symbols from the resulting `.o` files
4. **Compile Lua modules** — for each Lua module group, finds all `.lua`
   files under `src/<mod>/`, compiles each to a bytecode header with
   `luajit -b`, and patches the header to use the project's naming scheme
5. **Assemble C source** — fills the `c_tmpl` template with:
   - `{{START_CODE}}` — the entrypoint constant declarations
   - `{{LUAMODS}}` — bytecode `#include`s and the `lua_preload[]` table
   - `{{CLIBS}}` — `extern` declarations and the `c_preload[]` table
   - Appends the app's `main()` (custom or default)
   - Substitutes `{{VERSION_STRING}}` (the `-v` line), `{{LILUSH_VERSION}}`
     (exposed to Lua as the `LILUSH_VERSION` global), and `{{APP_NAME}}`
6. **Build static library** — `ar rcs` all `.o` files into `liblilush.a`
7. **Link binary** — `clang` with static linking against LuaJIT
   and `liblilush.a`

The output is written to `/build/<binary>` inside the Docker container.

## Docker

`Dockerfile` accepts a single build arg `APP` (the app name).
It installs the Alpine build toolchain, compiles LuaJIT from
source, copies `src/` and `buildgen/` into the container, and runs
`generate.lua apps/${APP}.lua`.

`build.bash` builds the Docker image, copies the binary out, and cleans up
the container.

## Build dependencies

Provided by the Docker image (Alpine):

- `clang` (compiler/linker)
- `alpine-sdk` (make, ar, strip, etc.)
- `git` (to clone LuaJIT)
- `linux-headers` (kernel headers for C modules)

Built from source inside Docker:

- **LuaJIT** (`v2.1`) — built with `-DLUAJIT_DISABLE_FFI -DLUAJIT_ENABLE_LUA52COMPAT`

Cryptographic primitives are provided by LITLS (`src/litls/`), which ships
in-tree and requires no external library installation.
