Version: 1.12
Language: Luma
Lumix is a build system for the Luma programming language that automates dependency detection, module compilation, and linking. It scans your project directory for .lx files, analyzes their @use directives, and generates the appropriate compilation commands. Lumix can be configured via a lumix.toml file in your project root, or driven entirely from the command line.
- Automatic Entry Point Detection — Finds the file containing
pub const main -> fnautomatically - Dependency Scanning — Parses
@usedirectives to identify module dependencies - Deduplication — Ensures each dependency is included only once in the build command
- Standard Library Support — Automatically converts
std_*module names tostd/*.lxpaths - TOML Configuration — Project settings can be stored in
lumix.toml - Interactive CLI — Simple command-based interface for building, cleaning, and inspecting dependencies
- Colored Output — Uses terminal colors to highlight commands and dependency trees
Compile Lumix with the following command:
luma src/lumix.lx -l std/string.lx std/memory.lx std/io.lx src/file_system.lx src/getch.lx src/parser.lx src/toml.lx src/user_input.lx src/utility.lx std/termfx.lx std/sys.lx src/module.lx --no-sanitize -O3 -name bin/lumix -O0This creates the lumix executable at bin/lumix.
- Navigate to your Luma project directory
- Run
./lumix <command>
| Command | Description |
|---|---|
build [output_name] |
Compile your project |
clean |
Remove build artifacts |
config |
Show resolved TOML configuration |
deps |
Show dependency tree |
init |
Scaffold a new lumix.toml |
run |
Build and execute the project binary |
test |
Compile and run test suites from [test] in lumix.toml |
version |
Print the current Lumix version |
./lumix build [output_name]
The output name can be supplied as the first argument after build. If omitted, Lumix falls back to the name field in lumix.toml. If neither is present, the build will fail with an error.
The build process:
- Loads
lumix.tomlif one is present in the current directory - Resolves the entry point — TOML
entryfield takes priority, then auto-detection (scans forpub const main -> fn), then a manual prompt - Resolves the scan directory — TOML
directoryfield, defaulting to. - Resolves the binary output directory — TOML
binary_dirfield (optional) - Resolves build flags — TOML
build_flagsfield (optional) - Resolves dependency paths —
[dependencies].pathsentries ending in.lxare treated as files; entries without.lxare scanned as directories for all.lxfiles - Builds the dependency graph by scanning all
.lxfiles in the scan directory - Constructs and executes the
lumacompile command, including all resolved dependency files
Example output:
lumix.toml found — using config file.
=== Building Luma Project ===
Entry point: src/game.lx
Output name: my_game
Executing command:
luma src/game.lx -l std/io.lx std/string.lx src/player.lx src/renderer.lx --no-sanitize -O3 -name bin/my_game
Build completed successfully. Output: my_game
./lumix clean
Prompts for confirmation (y/n), then removes:
obj/directorymainexecutable- Temporary files matching
/tmp/luma_*.txt
./lumix deps
Scans the current directory and prints a colored dependency tree:
=== Dependency Tree ===
Module dependencies:
game depends on:
- player
- renderer
- std_io
player depends on:
- std_string
- std_memory
renderer (no dependencies)
./lumix version
Prints the current Lumix version string (e.g. Luma Build System v1.10).
./lumix init
Scaffolds a new lumix.toml in the current directory. If one already exists, prompts for confirmation before overwriting.
./lumix config
Parses and displays the resolved configuration from lumix.toml, including all sections and auto-detected values (such as the entry point file).
Example output:
=== Configuration ===
Config file: lumix.toml
[project]
name = "lumix"
entry = "src/lumix.lx"
directory = "src"
[build]
flags = "-O3"
[output]
binary_dir = "bin"
[dependencies]
paths = (none)
[run]
(none)
--- Auto-Detected ---
entry = "src/utility.lx"
./lumix run # run the first (default) command
./lumix run build # run the command keyed "build"
./lumix run build install # run multiple commands in sequence
./lumix run 1 # run the command keyed "1"
Runs one or more shell commands selected from the [run] args array in lumix.toml. Each entry is a key => "command" pair; keys may be names or numbers. With no argument, the first entry is run. When multiple keys are given, each command runs in sequence, stopping at the first failure. Commands are executed directly via the shell, with multiline commands collapsed to a single line.
./lumix test
Compiles and runs test suites declared under [test] in lumix.toml. Each test is a standalone .lx program compiled individually with the luma compiler:
- Valid tests (
valid = [...]) must compile and exit with code0when executed. - Error tests (
errors = [...]) must fail to compile — ideal for testing that bad code produces compiler errors.
Entries follow the same rules as [dependencies] paths: strings ending in .lx are single test files; all other entries are directories scanned recursively for .lx files.
Example output:
=== Running Tests ===
Running 3 test(s): 2 valid, 1 error-expected
[PASS] tests/valid/add.lx
[FAIL] tests/valid/crash.lx
exited with code 7
[PASS] tests/errors/bad.lx
2/3 tests passed (1 failed)
Failing valid tests show the captured compiler/runtime output beneath their line, making failures easy to debug. The command exits non-zero when any test fails, so it can gate CI pipelines.
Notes:
- If no
[test]section is present, Lumix falls back to the conventionaltests/valid/andtests/errors/directories. - Each test file must begin with an
@moduledeclaration and contain its ownpub const main -> fn. - Tests should be self-contained or depend only on
std_*modules (project modules are not linked into tests). - Lumix wipes the transient
obj/cache before each test compile to prevent stalemainsymbols from breaking linking.
Lumix automatically reads lumix.toml from the current working directory when running build. All fields are optional — any field not set falls back to the default behavior described below.
[project]
name = "lumix"
directory = "src"
entry = "src/lumix.lx"
[dependencies]
paths = [
"libs/sdl2/",
"lib/audio.lx"
]
[build]
flags = "--no-sanitize -O3"
[output]
binary_dir = "bin"
[run]
args = [
1 => "luma test/test.lx -name testing"
build => "luma src/main.lx -l lib/progress_bar.lx -O2 -name bin/luma"
]
[test]
valid = [
"tests/valid/",
"tests/extra_checks.lx"
]
errors = [
"tests/errors/"
]| Field | Type | Description | Default |
|---|---|---|---|
name |
string | Output binary name | Required (or pass as CLI arg) |
entry |
string | Path to the entry point .lx file |
Auto-detected |
directory |
string | Directory to scan for .lx source files |
. (current directory) |
| Field | Type | Description | Default |
|---|---|---|---|
paths |
array of strings | Extra source files or directories to include. Paths ending in .lx are added as files; all others are scanned recursively for .lx files (e.g. "libs/sdl2/" includes every .lx under libs/sdl2/) |
(none) |
| Field | Type | Description | Default |
|---|---|---|---|
flags |
string | Extra flags passed to the luma compiler (e.g. --no-sanitize -O3) |
(none) |
| Field | Type | Description | Default |
|---|---|---|---|
binary_dir |
string | Directory to place the compiled binary in | (none — binary placed in current dir) |
| Field | Type | Description | Default |
|---|---|---|---|
args |
array of key => "command" |
Named shell commands run by run. run uses the first entry; run <key> selects by key |
(none) |
| Field | Type | Description | Default |
|---|---|---|---|
valid |
array of strings | Test programs that must compile and exit 0. .lx entries are single files; others are scanned recursively |
(none — falls back to tests/valid/) |
errors |
array of strings | Test programs that must fail to compile. Same expansion rules as valid |
(none — falls back to tests/errors/) |
When both lumix.toml and command-line arguments are present, the following priority order applies:
- Output name: CLI argument (
./lumix build myapp) >[project] name - Entry point:
[project] entry> auto-detection > interactive prompt - Scan directory:
[project] directory>. - Binary directory:
[output] binary_dir> current directory - Build flags:
[build] flags> (none) - Dependency paths:
[dependencies] paths— appended as extra source files; directories expanded to all.lxfiles - Run commands:
[run] args— a keyed array of shell commands selectable viarun/run <key> - Test suites:
[test] valid/[test] errors— used bytest; falls back to conventional directories when unset
TOML comments use #:
# This is a comment
[project]
name = "myapp" # inline comments are also supportedValues (including [run] commands) may span multiple lines using TOML triple-quoted strings, which preserve the contained newlines:
[run]
args = [
build => """
luma src/main.lx -l src/ast/expr.lx src/ast/module.lx
std/cstring.lx std/vector.lx
-O2 -name bin/luma
"""
]Run commands are normalized before execution: newlines and indentation are collapsed into single spaces, so a multiline [run] command is executed as one shell command line.
Lumix uses find to locate all .lx files under the configured scan path:
find <path> -name '*.lx' -type f 2>/dev/null > /tmp/luma_files.txtFalls back to ls if find is unavailable.
Scans each file for the pattern:
pub const main -> fn
The first file containing this pattern becomes the entry point.
For each file, Lumix reads the content, finds all @use directives at line starts (ignoring // comments), and extracts the module name from the quoted string:
@use "module_name" as alias
Standard library modules are converted automatically:
std_io→std/io.lxstd_string→std/string.lxstd_memory→std/memory.lxstd_termfx→std/termfx.lx- (etc.)
Lumix constructs a command of the form:
luma <entry_point> -l <std_libs> <other_files> <dep_paths> [build_flags] -name <binary_dir/output_name>Dependency paths from [dependencies].paths are appended after the scanned source files. Directories are expanded into individual .lx files during command construction.
The has_dep() function checks whether a dependency path is already present in the command before adding it, preventing duplicate inclusions via string comparison.
| File | Module | Role |
|---|---|---|
lumix.lx |
build |
Main entry point, CLI dispatcher, build orchestration, test runner |
file_system.lx |
file_system |
File discovery and directory operations |
parser.lx |
parse |
Parsing @use directives and file path lines |
module.lx |
module |
Module name extraction from file paths |
utility.lx |
utility |
Helper functions (string ops, file existence, main detection) |
user_input.lx |
user_input |
get_user_input helper for prompted input with defaults |
getch.lx |
getch |
Single-character terminal input, yes/no prompts |
toml.lx |
toml |
lumix.toml parser and TomlConfig struct |
const BuildConfig -> struct {
entry_point: *byte, // Path to the entry point file
output_name: *byte, // Binary name
stdlib_path: *byte, // Path to std lib (always "std")
scan_path: *byte, // Directory to scan for .lx files
binary_dir: *byte, // Output directory for the binary (optional)
build_flags: *byte, // Extra compiler flags from lumix.toml
optimize: int, // Optimization level (reserved)
debug_symbols: int, // Debug symbols flag (reserved)
verbose: int // Verbose output (always 1)
};
The dependency graph is stored in module-level global arrays:
g_module_names— Module names extracted from file pathsg_file_paths— Full paths to source filesg_dependencies— Dependency lists per file (from@usedirectives)g_dep_counts— Number of dependencies per fileg_graph_size— Number of files in the graph
- Maximum 500 files per project (
fs::MAX_FILES) - Maximum 200 dependencies per file (
parser::MAX_DEPS) - No circular dependency detection
- Unix only —
getchand file discovery usestty/find/system()calls - Local files only — no remote package management
"No .lx files found"
Ensure you're in a directory with .lx files, or that [project] directory in lumix.toml points to the right path.
"No entry point auto-detected"
Make sure exactly one file contains pub const main -> fn. Check for typos in the signature.
"No output name provided"
Supply a name as a CLI argument (./lumix build myapp) or set name under [project] in lumix.toml.
"Could not extract module name"
Files must have a .lx extension and a valid filename.
Build fails with missing dependencies
Ensure all imported modules exist as .lx files in the scan path, and that the standard library is in the std/ directory accessible to the compiler. If you installed Luma using install.sh, this should be set up automatically.
- Incremental builds (only recompile changed files)
- Parallel compilation
- Cross-platform support (Windows/macOS)
- Circular dependency detection