CLI
The mach binary is the compiler driver. It dispatches on the first argument to a small set of commands - build, run, test, and more - that compile, execute, and manage a project rooted at a mach.toml.
Invocation
mach <command> [options]
The compiler dispatches on argv[1]. With no command, or an unknown one, it prints usage and exits non-zero. The project commands - build, run, test, and doc - take the project root as a required positional and walk up from it until a mach.toml is found. A bare invocation with no path is a user error.
Flags are matched exactly: --flag value (the value follows in the next argument) or a bare --flag toggle.
The combined --flag=value form and bundled short flags are not recognized. Each flag and its value are separate arguments.
Commands
| Command | Summary |
|---|---|
build | compile the project to objects and (for a [bin.*]) a linked binary |
run | build, then execute the produced binary |
test | build the test binary and run the collected tests |
dep | manage git-backed dependencies (clone, lock, vendor) |
init | scaffold a new project |
doc | generate Markdown reference docs from source doc-comments |
info | print compiler version, build host, and registered target capabilities |
help | print usage; mach help <command> for detail |
Global flags
Read by build, run, test, and doc, which share one config parser. Passing --verbose and --quiet together is a parse error.
| Flag | Value | Effect |
|---|---|---|
--verbose, -v | - | write extra detail (per-stage compile progress) to stderr |
--quiet, -q | - | suppress non-error output |
--color <mode> | auto, always, never | color preference (default auto); an unknown mode is a parse error |
--target <name> | target name | select a declared target; absent, defers to [project].target |
--profile <name> | profile name | select a [profile.<name>] variant; absent, the first declared |
--bin <name> | artifact name | narrow the build to one [bin.<name>] artifact |
--lib <name> | artifact name | narrow to one [lib.<name>] artifact (mutually exclusive with --bin) |
-o <path> | path | override the artifact path, rooted at the project root |
--all-targets | - | build every declared [target.*], not just the default |
--emit-asm | - | emit per-module assembly text (.s); forces the profile's emit_asm on |
--emit-ir | - | emit per-module SSA IR text (.ir); forces the profile's emit_ir on |
--no-emit-asm | - | force assembly emission off, overriding the profile |
--no-emit-ir | - | force IR emission off, overriding the profile |
--verify-ir | - | run the IR verifier after each optimisation pass |
mach dep and mach init do not use the shared config parser; they read only their own flags.
build
mach build <path> [options]
Compiles the project rooted at <path> (for example mach build .). With no --bin or --lib, it builds every declared artifact for the default target and profile. Every reachable module is driven through sema, lower, optimise, and codegen to one relocatable object under the manifest's obj template. For a [bin.*] the objects are linked into the resolved out path; for a [lib.*] (or with --emit obj) the objects are the deliverable and nothing is linked.
| Flag | Value | Effect |
|---|---|---|
--release | - | select the release optimisation pipeline |
-O0 | - | force the debug pipeline (overrides --release) |
-O1, -O2 | - | select the release pipeline (both select the same one today) |
--emit <kind> | obj, exe | obj stops at the objects; exe (default) links a binary |
-L <dir> | dir | add a search directory for -l-resolved inputs; repeatable |
-l <name> | name | link a named object, archive, or shared library; repeatable |
Plus the global flags. The -O<n> flags override --release when both are present; absent any optimisation flag, the build uses the debug pipeline (the bootstrap-stable default).
Link inputs
ext fun declarations are forward references resolved at link time by external precompiled code - a loose .o object, a static .a archive, or a shared .so library. Inputs come from the command line and from the manifest's merged libs overlay; both sets are linked. An input that resolves to no existing file is a hard error, so a typo never silently drops a dependency.
- Explicit input path - a bare (non-flag) argument that contains a
/, ends in.oor.a, or names a.sois treated as an input path. The first non-flag positional afterbuildis the project root and is skipped; the rest are link inputs. A relative path is tried verbatim against the working directory first, then rooted at the project root. -l <name>- each-L <dir>is searched forlib<name>.o,<name>.o,lib<name>.a, then<name>.a; if none hit, the same candidates are tried against the working directory. Only if no static object or archive is found does resolution fall back to a sharedlib<name>.so(the-Ldirs, then the target OS's default and common system library directories).-L <dir>- adds a search directory for the resolution above. Both-Land-lmay be repeated.
How an input resolves decides static vs dynamic linking. A loose .o or static .a is a static input merged into the executable (an archive contributes every member object). A shared .so is a dynamic dependency: its DT_SONAME is recorded and undefined ext symbols are bound at load time through an emitted PLT. A static definition always wins over a dynamic import of the same name. -l prefers a static candidate, so the .so fallback applies only when none exists (the common case for system libraries like libc). Manifest libs resolve before CLI inputs, giving a deterministic link order.
Dynamic linking is implemented for the ELF (Linux) and PE (Windows) targets. The Mach-O (Darwin) import path is not yet implemented.
run
mach run <path> [options] [-- args...]
Builds the project at <path> exactly like mach build, then executes the produced binary. Arguments after a -- separator are forwarded to the child as its argv; the child's exit code becomes this command's exit code. --emit is rejected - running a relocatable object is not meaningful. All other build and global flags apply.
| Flag | Value | Effect |
|---|---|---|
--runner <cmd> | command | execute the binary as <cmd> <binary> <args...> instead of directly |
--runner names a host-side launcher for binaries the host cannot exec directly, for example mach run . --target windows --runner wine. <cmd> is a single command name or path (no shell-style word splitting); a bare name is resolved on PATH. Without the flag the binary is exec'd directly, and a launch failure reports exit 127 when execve rejects the binary, with no auto-detection.
test
mach test <path> [options]
Builds one standalone executable per test declaration (the test plus the project's transitive code, with a synthesized main calling just that test), then spawns each as a separate process and reports a per-test name file:line PASS|FAIL line. A crashing test reports its signal and the run continues. A test build always links executables, even for a library target.
| Flag | Value | Effect |
|---|---|---|
--filter <pattern> | pattern | run only tests whose name contains <pattern> |
--list | - | list the collected tests and exit |
--runner <cmd> | command | launch every test as <cmd> <exe> instead of exec'ing it directly |
Plus the build and global flags. --runner has the same semantics as on run. Without it, a test the host cannot launch reports a per-test failure - FAIL(exit 127) when execve rejects the binary, FAIL(spawn) when the spawn itself fails.
dep
mach dep <action> [args]
Manages the project's dependency tree. A dependency has exactly one source form: a git URL plus a ref, acquired into the dep directory with plain git operations, or a path to another project tree, materialised as a relative symlink so the build resolves it by the same vendor layout.
| Action | Args | Effect |
|---|---|---|
pull | - | realise the manifest: clone missing git deps (transitively), link path deps, re-resolve a changed ref, repair drift, write mach.lock. Idempotent. |
update | <name> or --all | the only lock-advancer: re-resolve branch refs to current remote tips. Tag/commit refs are a no-op. Never edits the manifest. |
add | <name> --git <url> [--ref <ref>] or --path <dir> | append a [deps.<name>] stanza, then pull. |
remove | <name> [--purge] | drop the entry from mach.toml and mach.lock; --purge also deletes the vendored checkout. |
list | - | print each entry with its source form, ref, locked commit, and state (synced, missing, drifted, path). |
mach build never requires git or the network: a project whose dep tree is present builds on a bare machine. Only the network-shaped commands (pull, update, add) use git, discovered on PATH and invoked with an allowlisted environment. Git's absence is a clean error naming the operation that needed it. The same name required from two different sources or refs is a hard error naming both requirers; there is no version resolution.
Lockfile
The manifest is intent; mach.lock is the record of resolving it. After a pull, mach dep writes a TOML file recording each git dep's url, ref, and resolved commit (path deps have no lock entry).
# generated by `mach dep pull`; do not edit by hand.
version = 1
[deps.mach-std]
url = "https://github.com/briar-systems/mach-std"
ref = "branch/dev"
commit = "6b78ae1e8c3c9cc45e4ab4b916fd191d61e76aff"
pull honours the lock except where the manifest ref was edited, where it re-resolves loudly. A checked-out commit that differs from the lock is drift, repaired and reported, never silent. The writer is idempotent: an up-to-date lock is left untouched. Commit mach.lock to pin builds.
init
mach init [dir] [options]
Scaffolds a new project in [dir] (default: the current directory). Writes a complete mach.toml with a [project] block, [target.*] platforms for linux/windows/darwin, one artifact, debug and release profiles, a [deps.mach-std] dependency, a starter source file, and a cloned mach-std. Every collision is checked before any file is written, so a refused init leaves nothing behind.
| Flag | Value | Effect |
|---|---|---|
--name <name> | name | project id (default: the directory base name) |
--force | - | scaffold even when mach.toml, src/main.mach, or src/lib.mach already exists |
--lib | - | library layout: write src/lib.mach and scaffold a [lib.<id>] artifact (kind = "static") instead of [bin.<id>] |
The first non-flag argument is the target directory. A default binary scaffold links and runs from mach build . without further manifest edits.
doc, info, and help
mach doc <path> loads the module graph and generates Markdown reference docs from source doc-comments - one page per module plus an index. Each pub declaration is paired with the run of # comment lines immediately preceding it. --out <dir> sets the output directory (default doc/api); --target <name> selects a target for module discovery. The hand-written language material is never touched.
mach info prints an at-a-glance identity of the binary: its version, the host it was built for, and the registered capability surface. It needs no project. The output is line-oriented and stable for scripts.
mach
host: linux/x86_64
isa: x86_64 aarch64 riscv64
os: linux darwin windows freestanding
abi: sysv64 win64 aapcs64 lp64
object: elf coff macho raw
The capability lines are read from the binary's target registries, so they report exactly what this build can target. mach info --version prints the version string alone on one line, for tooling.
mach help [command] prints the top-level usage summary, or - with a known command - that command's detail page. An unknown command prints usage and exits non-zero.
Exit codes
The commands share a stable convention for scripting:
| Code | Meaning |
|---|---|
0 | success (for test, all tests passed) |
1 | user error: missing project path, no mach.toml, unknown target, compile errors, an unresolvable link input (for test, any test failed) |
2 | internal error |
mach run instead returns the child's exit code on a successful build, falling back to 1 on a build/user error and 2 internally.
See also
- Manifest - the
mach.tomlreference for targets, profiles, and libs - Dependencies - git and path deps managed by
mach dep - Testing - the
testdeclaration behindmach test - Project layout - the directory shape
mach initscaffolds