Dependencies
A mach project declares its dependencies in mach.toml and vendors them into a flat dep/ tree. mach dep realizes that tree from git or local paths; mach build then resolves everything by vendor layout alone, with no git and no network.
Declaring a dependency
Each [deps.<alias>] stanza names a dependency materialized under <dep>/<alias>/ (the dep dir, default dep/). A stanza declares exactly one source key: a git URL or a local path.
# in mach.toml
[deps.mach-std]
git = "https://github.com/briar-systems/mach-std"
ref = "v0.4.0"
[deps.local-lib]
path = "../sibling/local-lib"
| Key | Type | Meaning |
|---|---|---|
git | string | Git URL cloned into <dep>/<alias>/. |
path | string | Local path to another project tree, resolved relative to this manifest; never fetched. |
ref | string | Git ref to check out (with git). Absent means the remote default branch. |
A git ref resolves in one of these forms:
tag/<name>- the named tag.branch/<name>- the remote-tracking branch tip.commit/<name>or a 7-40 char hex SHA - the literal commit.- a bare name - auto-detected as a remote branch (tracking its tip), else a literal tag or commit.
- the empty ref - the remote default branch.
A registry-style version = key is reserved and rejected. There is no version resolution: dependencies are pinned by git ref or local path, nothing more.
The mach dep command
mach dep <action> manages the tree under <dep>. It reads mach.toml from the current directory directly (it does not walk up to find a project root).
| Action | Effect |
|---|---|
pull | Realize the manifest: clone missing git deps (transitively), link path deps, re-resolve a changed ref, repair checkout drift, write mach.lock. Idempotent. |
update | The only lock-advancer: re-resolve branch refs to current remote tips. Takes <name> or --all. Tag and commit refs are an immutable no-op; never edits the manifest. |
add | Append a [deps.<name>] stanza to the manifest, then pull. Takes --git <url> [--ref <ref>] or --path <dir>. |
remove | Drop the entry from mach.toml and mach.lock; --purge also deletes <dep>/<name>/. |
list | Print each entry with its source form, ref, locked commit, and state (synced, missing, drifted, path). |
# add a git dep and vendor it in one step
mach dep add mach-std --git https://github.com/briar-systems/mach-std --ref v0.4.0
A git dep is cloned, then checked out as a detached HEAD at the resolved commit. A path dep is never fetched: pull materializes it at <dep>/<alias>/ as a relative symlink, so the build reaches it by the same vendor layout as a git dep. Linking is idempotent - a stale link is replaced, a correct one left in place, and a source already living at the vendor location is a no-op. A path pointing at a missing directory, a tree with no mach.toml, or a vendor slot occupied by a real directory is a hard error.
The lockfile
The manifest is intent; mach.lock is the record of resolving it. After a pull, mach dep writes a TOML lock recording each git dep's url, ref, and resolved commit. Path deps carry no pinned content, so they 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 - there it re-resolves loudly. A checked-out commit that differs from the lock is drift, repaired by pull and reported, never silent. The writer is idempotent: an up-to-date lock is left untouched. Commit mach.lock to pin builds.
The flat dep tree
Transitive deps resolve into a flat tree: every git dep, direct or transitive, lives at <dep>/<name>/. The build resolves a dependency purely by vendor layout - it reads that directory's own mach.toml for its [project].id and [project].src, and routes any module path whose head segment matches a dep's id into that tree.
The same name required from two different sources or refs is a hard error naming both requirers. There is no version reconciliation.
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. Git's absence is a clean error naming the operation that needed it.
Importing a dependency
Once vendored, a dependency's modules are imported by their full path - the head segment is the dep's [project].id, the rest mirrors its source tree. A dependency that declares a [project].module can also be imported by its bare id alone.
# in the dependency's manifest:
[project]
id = "glfw"
module = "glfw.mach" # the surface a bare use binds
# in a consumer:
use glfw; # binds glfw's [project].module
A bare import of a project that declares no module is a resolution error naming the fix. Longer paths are unaffected, so this is purely additive.
Cascading link requirements
A dependency declares its own external link inputs as [target.*].libs (full-tuple) or [os.<name>].libs (os-component) overlays. 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.
# in the dependency's manifest:
[os.windows]
libs = ["kernel32.dll"] # linked into every windows build, this dep's and any consumer's
Matching is by tuple or component equality; target names are local to each manifest. Across dependencies the order is topological, then declaration order, and the union is deduplicated by name. See the manifest's link-input merge law for the full overlay rules.
See also
- Manifest - the full
mach.tomlreference, including[deps.*]and link overlays - CLI - every
machcommand, includingmach depactions - Modules - how source files map to importable module paths
- Project layout - where
dep/and the manifest live in a project