Manifest
Every mach project has a mach.toml at its root: the complete, readable statement of what the project builds - its identity, target platforms, artifacts, build variants, and dependencies. The compiler finds it by walking up from the working directory, so any subcommand run inside a project tree resolves the same manifest.
The schema
The manifest separates the three orthogonal axes of a build: what is built (the [bin.*] / [lib.*] artifacts), where it runs (the [target.*] platforms), and where outputs land (the path templates). Repetition between axes is eliminated by the build engine's cartesian product, never restated in the file. Nothing is inferred: every value resolves from a stanza present in the file.
[project]
id = "demo" # required: root of every module path the project exposes
version = "0.1.0" # required
src = "src" # source dir (default "src")
dep = "dep" # vendored-dependency dir (default "dep")
target = "native" # default target selector (default "native")
out = "out/{target}/{profile}/bin/{name}{ext}" # artifact path template
obj = "out/{target}/{profile}/obj" # object dir template
ir = "out/{target}/{profile}/ir" # IR-dump dir template
asm = "out/{target}/{profile}/asm" # ASM-dump dir template
tests = "out/{target}/{profile}/test/{name}" # per-test executable template
# optional metadata: name, description, license, authors
[target.linux] # a platform: a fully-spelled tuple, nothing inferred
isa = "x86_64"
os = "linux"
abi = "sysv64"
[target.windows]
isa = "x86_64"
os = "windows"
abi = "win64"
ext = ".exe" # artifact extension (default "")
libs = ["kernel32.dll"] # platform link overlay, inherited by every artifact
defines = [] # per-target comptime defines
[os.windows] # os-component link overlay (any windows target)
libs = ["kernel32.dll"] # inherited by every windows build, yours and consumers'
[bin.hello] # an executable artifact, named by its table key
entry = "hello.mach" # entry source, relative to the src dir (required)
[lib.core] # a library artifact
entry = "lib.mach"
kind = "static" # "static" (default) | "shared"
[bin.hello.target.windows] # a per-cell exception refining one artifact-target pair
entry = "hello_win.mach" # overrides the artifact's entry for this target only
libs = ["user32.dll"] # appended to the merged link overlay
[profile.debug] # a build variant
opt = 0 # 0 (none) | 1 (standard) | 2 (aggressive)
[profile.release]
opt = 2
emit_ir = true # emit per-module IR for this profile (default false)
emit_asm = false # emit per-module assembly for this profile
[deps.mach-std] # a dependency: exactly one of git|path, plus ref for git
git = "https://github.com/briar-systems/mach-std"
ref = "v0.4.0"
[project]
Project identity and the path roots everything else resolves against.
| Key | Req | Default | Meaning |
|---|---|---|---|
id | yes | - | Root segment of every module path the project exposes. A file at <src>/foo/bar.mach is reachable as <id>.foo.bar. |
version | yes | - | Project version. Read by the $project.version comptime root. |
module | no | - | Src-relative path a bare use <id> / fwd <id> resolves to (see Bare project-id imports). Never inferred. |
src | no | "src" | Source root, relative to the project root. Module paths resolve under it. |
dep | no | "dep" | Vendored-dependency root. Each dep lives at <dep>/<alias>/. |
target | no | "native" | Default target when --target is not passed. native matches the host. |
out | no | out/{target}/{profile}/bin/{name}{ext} | Artifact path template (see Path templates). |
obj | no | out/{target}/{profile}/obj | Per-module object tree template. |
ir | no | out/{target}/{profile}/ir | Per-module IR-dump template (used when emission is on). |
asm | no | out/{target}/{profile}/asm | Per-module assembly-dump template. |
tests | no | out/{target}/{profile}/test/{name} | Per-test executable template for mach test. |
Optional metadata read by the $project.* comptime roots but not the build: name, description, license, authors.
Targets
Each [target.<name>] declares a selector you pass to --target <name> (or set as [project].target). At least one target must be declared. A target is a fully-spelled platform tuple; nothing is inferred from another key.
| Key | Req | Default | Meaning |
|---|---|---|---|
isa | yes | - | Instruction-set architecture. Read by $project.target.arch. |
os | yes | - | Operating system. Read by $project.target.os. |
abi | yes | - | Application binary interface (e.g. sysv64, win64). Read by $project.target.abi. |
of | no | (os default) | Object format, selected by name (e.g. "raw"). Omit to derive it from the os default (see the recognized values below). |
ext | no | "" | Artifact filename extension expanded by {ext} (e.g. ".exe"). |
libs | no | [] | Platform link overlay inherited by every artifact built for this target (see Link inputs). |
defines | no | [] | Per-target comptime defines. Each is NAME (a true flag) or NAME=VALUE, readable as $mach.build.NAME. |
native is a reserved name: declaring [target.native] is an error. It is a selector that resolves to a declared target, not a target itself.
Recognized isa / os / object-format values
| Value | Axis | Status |
|---|---|---|
x86_64 | isa | Supported - the only fully working ISA today. |
aarch64 | isa | Recognized; an ISA vtable exists, but codegen is not yet validated end-to-end. |
riscv64 | isa | Recognized; full RV64 codegen (selection, encoder, relocations) byte-verified against llvm and run under qemu. Cross-compile only - no mach-std riscv64 runtime yet, so no hosted toolchain. |
linux | os | Supported - the primary host and target. |
windows | os | Supported as a cross-compilation target (PE/COFF, Win64 ABI). |
darwin | os | Recognized; vtables exist, but the toolchain is not yet validated end-to-end. |
freestanding | os | Bare-metal: no syscalls, no OS runtime, a custom _start, and image base 0. Defaults to the raw object format. |
elf / coff / macho | of | Container object formats for linux / windows / darwin respectively; selected automatically unless of overrides. |
raw | of | Flat-image writer: each segment is laid at its virtual address and emitted with no container, entered at the image base. The default for freestanding. |
Artifacts
Every artifact is declared explicitly and named by its table key. A [bin.<name>] links an executable; a [lib.<name>] produces a library. The name drives {name} in the output templates and is read by $bin.name.
| Key | Applies | Req | Default | Meaning |
|---|---|---|---|---|
entry | bin, lib | yes | - | Entry source, relative to the project src dir. The entry module's FQN is <id>.<entry without .mach>, with / turned into .. |
kind | lib | no | "static" | "static" leaves the per-module objects as the deliverable; "shared" produces a shared library. |
out | bin, lib | no | project out | Per-artifact override of the path template. |
libs | bin, lib | no | [] | Per-artifact link inputs, merged into the link overlay. |
defines | bin, lib | no | [] | Per-artifact comptime defines. |
Per-cell exceptions
A [bin.<name>.target.<t>] table refines one artifact for one target. It may override entry and out, and append libs / defines at the highest precedence level. An unset key inherits the artifact's value.
Profiles
A [profile.<name>] is a build variant. The optimization level and the debug-emission toggles live here because they are variant concerns.
| Key | Default | Meaning |
|---|---|---|
opt | (debug) | Optimization level: 0 (none beyond the always-on pipeline), 1 (standard), or 2 (aggressive). Any other value is a manifest error. |
emit_ir | false | Write per-module IR dumps under the ir template for this profile. |
emit_asm | false | Write per-module assembly dumps under the asm template for this profile. |
A CLI -O0 / -O1 / -O2 / --release flag overrides the selected profile's opt per invocation, and --emit-ir / --emit-asm / --no-emit-ir / --no-emit-asm override the emission toggles. Absent --profile / --release, the first declared profile is used; with no profile declared, a debug default (no optimization, no emission) applies.
1 and 2 currently select the same pass set; 2 is where future loop and vectorization work lands.
Dependencies
Each [deps.<alias>] names a dependency materialised under <dep>/<alias>/. The build resolves a dependency purely by vendor layout: it reads that directory's own mach.toml for its [project].id and [project].src. A module path whose head matches a dep's id resolves into that dep's tree. A stanza declares exactly one source key.
| Key | Meaning |
|---|---|
git | Git URL to clone into <dep>/<alias>/. |
path | Local path to another project tree, resolved relative to this manifest; never fetched. mach dep pull materialises it at the vendor location as a relative symlink. |
ref | Git ref to check out (with git): tag/<name>, branch/<name>, a bare tag or branch, or a commit SHA. An absent ref means the remote default branch. |
A git dep is pinned to a resolved commit in mach.lock; a path dep has no pinned content, so it carries no lock entry. mach dep performs only plain git operations, so a checkout you also commit as a submodule composes naturally; mach never invokes git submodule. mach dep pull is idempotent: a stale link is replaced, a correct one is left untouched, and a source already at the vendor location is a no-op.
A registry-style version = key is reserved and rejected. Cloning, lockfile handling, and transitive resolution are covered in Dependencies.
Link inputs
A libs entry - at the target, artifact, or per-cell level - is either an explicit path (a .o object, a .a archive, or a .so shared library, project-root-relative or absolute) or a bare -l-style name resolved at link time to lib<name>.o / <name>.o / lib<name>.a / <name>.a, then a shared lib<name>.so.
Loose .o objects and static .a archives link statically (an .a contributes every member object); a shared .so is recorded as a dynamic dependency. The overlays merge target < artifact < per-cell, deduplicated by name, and join every mach build / run / test link alongside any CLI -L / -l / object arguments.
OS overlays and cascading
An [os.<name>] overlay scopes a link requirement to a single tuple component - the operating system - rather than a full (isa, os, abi) tuple. A build matches iff its target's os equals <name>.
[os.windows]
libs = ["kernel32.dll"] # linked into every windows build, this project's and any consumer's
A dependency declares its own link requirements as [target.*].libs (full-tuple) or [os.<name>].libs (os-component) overlays, and a consumer inherits every such lib that matches the build it is producing. So a platform link requirement lives once, in the providing dependency's manifest, and out of every consumer. Matching is by tuple/component equality; target names are local to each manifest.
The single-component [isa.<name>] and [abi.<name>] overlays are reserved: declaring either is an error until a real case demands them.
Bare project-id imports
A one-segment use / fwd path equal to a resolvable project id - a dependency's [project].id, or the current project's own id - resolves to that project's declared [project].module. So a library that sets module = "glfw.mach" is imported as use glfw; instead of by the full path to its surface file. Longer paths are unaffected, so this is purely additive.
# in glfw's manifest:
[project]
id = "glfw"
module = "glfw.mach" # the surface a bare use glfw; binds
# in a consumer:
use glfw; # binds the glfw module (glfw's [project].module)
A bare import of a project that declares no module is a resolution error naming the fix. A declared module that names no file is a manifest error at build start, whether or not anything imports it.
Path templates
Output paths come only from the declared templates, expanded over four variables:
{target}- the resolved target name (never the literalnative).{profile}- the selected profile name.{name}- the artifact name.{ext}- the target'sext(the empty string by default).
Manifest paths are always /-separated and normalized to the host separator at the filesystem boundary, so the same manifest is portable; a literal \ is a hard error. Two artifacts that resolve to the same out path collide and fail at build start.
The tests template expands the same way, except {name} is the per-test name. mach test substitutes it per test as <index>-<sanitized label>: the zero-based collection index keeps the name unique and stable, and every label byte outside [A-Za-z0-9._-] becomes _. A test labeled "parses empty input" collected third lands at out/linux/debug/test/2-parses_empty_input.
The build matrix
A build cell is one artifact times one target times one profile.
mach build .builds every declared[bin.*]/[lib.*]for the default target and profile.--all-targetscrosses every artifact with every declared target.--bin <name>/--lib <name>narrow to one artifact;--target <name>selects a target;--profile <name>(with--releasesugar) selects a profile.mach run .andmach test .build exactly one artifact; with several declared and no--bin/--lib, they ask you to pick one, naming every candidate.
native target resolution
native resolves the host's (isa, os) tuple against the declared targets only - never a synthesized tuple. Exactly one host match is chosen; several matching tuples is an ambiguity error naming the candidates; no match warns and falls back to the first declared target, so cross-only projects still build on a foreign host.
Annotated example
The compiler's own manifest builds one binary for two targets, keeping its output paths flat (no {profile} segment) so released paths stay stable.
[project]
id = "mach" # module-path root: this project's code is mach.*
name = "Mach Compiler" # metadata
version = "1.3.0"
src = "src" # sources under ./src
dep = "dep" # vendored deps under ./dep
target = "native" # default target: the host-matching tuple below
out = "out/{target}/bin/{name}{ext}" # e.g. out/linux/bin/mach
obj = "out/{target}/obj"
tests = "out/{target}/test/{name}"
[target.linux]
isa = "x86_64"
os = "linux"
abi = "sysv64"
[target.windows]
isa = "x86_64"
os = "windows"
abi = "win64"
ext = ".exe" # the windows artifact is out/windows/bin/mach.exe
libs = ["kernel32.dll"] # the windows platform link requirement
[bin.mach]
entry = "main.mach" # entry module mach.main, at src/main.mach
[deps.mach-std]
git = "https://github.com/briar-systems/mach-std"
ref = "branch/dev"
mach build . (no --target) selects linux because [project].target = "native" resolves to the host-matching tuple, compiles src/main.mach and its transitive imports - including modules from mach-std vendored at dep/mach-std/ - into objects under out/linux/obj/, and links out/linux/bin/mach. The dependency is realized by mach dep pull from the manifest pin, and the build then resolves it purely by that vendor path: no git at build time.
See also
- Dependencies - vendoring, mach.lock, and the dep resolver
- CLI - the mach command-line reference
- Modules - how files map to module paths
- Project layout - src, lib.mach, and main.mach