lforge
lforge is the Logos build tool. It orchestrates logosc and cc to turn a Logos project into an executable or a linkable library, and it manages dependencies on other Logos projects. It is itself written in Logos — the build tool is a self-hosted Logos program.
lforge is installed alongside logosc (see Getting Started). It is an early MVP: it builds projects and resolves dependencies, but there is no watcher, no LSP/editor integration, and no cross-compilation yet.
A project
There is no scaffolding command. A project is a manifest at the root plus one or more source directories, all created by hand:
my-project/
lforge.writ # the manifest
src/
main.logos # entry point
The manifest is a Writ document in structured-data notation — JSON-like, with unquoted keys and tolerant of trailing commas. The smallest binary project:
{
name: "my-project",
version: "0.1.0",
targets: [
{ kind: "bin", name: "my-project", src: "src", entry: "main" }
]
}
lforge build from the project root compiles this to .lforge/debug/out/my-project.
Commands
Every command runs against the project rooted at the current directory.
| Command | What it does |
|---|---|
lforge build [target] |
Compile the project. With a target name, build only that target and its dependencies. |
lforge run [target] [-- args…] |
Build a bin target, then run it. The program’s exit code propagates; arguments after -- are forwarded to it. |
lforge test |
Compile every lib target, then compile and run each *.logos under tests/, reporting PASS/FAIL per file. |
lforge install [--prefix <path>] |
Copy bin targets to <prefix>/bin and lib targets to <prefix>/lib. Default prefix /usr/local. |
lforge update |
Re-resolve every git dependency, ignoring existing pins, and rewrite lforge.lock. |
lforge clean |
Remove the .lforge/ build directory. |
lforge doc |
Emit a docs.json documentation container for the project (consumed by this site). |
lforge version |
Print the tool version. |
Pass --release to any build command to select the release profile (output lands under .lforge/release/ instead of .lforge/debug/).
There is no
--helpor--versionflag. An unrecognised invocation prints a one-line usage string and exits non-zero; the version is available only through thelforge versionsubcommand.
The manifest
Top-level fields
| Field | Type | Required | Meaning |
|---|---|---|---|
name |
string | yes | Project name. |
version |
string | yes | Project version. |
requires_logos |
string | no | Minimum compiler version ("X.Y[.Z]"); lforge errors if logosc is older. |
targets |
array | yes | The build artifacts this project produces. |
deps |
array | no | Dependencies on other Logos projects. |
replace |
array | no | Local overrides for git dependencies (root manifest only). |
Targets
Each entry in targets describes one artifact:
{
kind: "bin" | "lib", // required
name: "<name>", // required
src: "src/<dir>", // directory of .logos sources
entry: "main", // bin only: entry file is <src>/<entry>.logos
deps: ["some-lib"], // sibling lib targets in this project
c_sources: ["native/util.c"], // optional C sources, project-root-relative
asm_sources: ["native/fast.S"] // optional assembly sources
}
A bin target compiles its entry file, then links the object against its sibling libraries and the standard library with cc. A lib target emits a lib<name>.a archive via logosc --emit-module. Any c_sources/asm_sources are compiled with cc and folded into the same archive. Sibling deps are built first, in dependency order.
Dependencies
Declare dependencies on other Logos projects in deps. Each entry is either a local path or a git reference, and lists the modules (library targets) to link from that project:
deps: [
{ path: "../shared-lib", modules: ["util"] },
{ project: "github.com/acme/http", tag: "v1.2.0", modules: ["http"] }
]
A git dependency pins exactly one of tag, branch, or sha. Bare host/path, explicit https:///ssh://, and user@host:path forms are all accepted and canonicalised to the same identity, so a repository shares one cache entry regardless of how it is written.
- Resolution uses Go-style minimum-version selection (highest requested version wins, no SAT solver). Sources are cloned into
~/.cache/lforge/, and compiled artifacts are cached there too, keyed by content so unchanged dependencies are never rebuilt. lforge.lockis written automatically wheneverdepsis non-empty. It pins every git dependency by commit SHA.lforge builduses those pins as-is;lforge updatere-resolves and rewrites the file. Commit the lockfile for applications; the.lforge/directory and (typically)lforge.lockare the only build artifacts.replacesubstitutes a local checkout for a git dependency — useful for developing a dependency against its consumer:
replace: [
{ project: "github.com/acme/http", path: "../my-fork-of-http" }
]
Only the root project’s replace entries take effect; those in nested dependencies are ignored.
Build output
Everything lforge produces lives under .lforge/, split by profile:
.lforge/
<profile>/ # debug (default) or release
_gen/<lib>.module # generated lib manifests
_files/<lib>/<stem>.o # per-file objects (incremental)
out/<bin> # linked executable
out/lib<lib>.a # library archive
test/<test>.bin # per-test binaries (lforge test)
doc/<name>.json # docs.json (lforge doc)
Rebuilds are incremental: per-file objects are recompiled only when their source is newer, and archives are relinked only when a member changed.
Environment
lforge locates the compiler and standard library through two environment variables:
LOGOSC— path to thelogoscbinary.LOGOS_LIB_DIR— directory holding the standard-library archives.
When run from inside a Logos source checkout, both fall back to the in-tree build (./build/bin/logosc and ./build/lib/logos). When you build a project elsewhere, point these at your installation.
See also
- Getting Started — install Logos and build your first program.
- Writ: code + data — the substrate the manifest format is built on.