machdocs
Home GitHub

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 [dep.<alias>] stanza names a dependency materialized under dep/<alias>/. A stanza declares exactly one source key: a git URL or a local path.

# in mach.toml
[dep.mach-std]
git = "https://github.com/briar-systems/mach-std"
ref = "v0.4.0"

[dep.local-lib]
path = "../sibling/local-lib"
KeyTypeMeaning
gitstringGit URL cloned into <dep>/<alias>/.
pathstringLocal path to another project tree, resolved relative to this manifest; never fetched.
refstringGit ref to check out (with git). Absent means the remote default branch.

A git ref resolves in one of these forms:

Note

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).

ActionEffect
pullRealize the manifest: clone missing git deps (transitively), link path deps, re-resolve a changed ref, repair checkout drift, write mach.lock. Idempotent.
updateThe 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.
addAppend a [dep.<name>] stanza to the manifest, then pull. Takes --git <url> [--ref <ref>] or --path <dir>.
removeDrop the entry from mach.toml and mach.lock; --purge also deletes <dep>/<name>/.
listPrint 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

[dep.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.

Note

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 requirements as [link.X] entries. An entry marked export = true also applies to any project that links this dependency's modules, so a platform link requirement lives once - in the manifest that needs it - and out of every consumer. A standalone build and a consumed build read the same entries, so nothing behaves differently as a dependency.

# in the dependency's manifest:
[link.kernel32]
source = "system"
name   = "kernel32.dll"
os     = "windows"          # filtered out on any non-windows cell
isa    = "*"
abi    = "*"
export = true                # ... and cascades to every consumer

An entry applies to a build cell when all three of its os / isa / abi axes match; target names are local to each manifest and are never matched on. Across dependencies the order is topological, then declaration order, and the union is deduplicated.

See also

  • Manifest - the full mach.toml reference, including [dep.*] and [link.*] requirements
  • CLI - every mach command, including mach dep actions
  • Modules - how source files map to importable module paths
  • Project layout - where dep/ and the manifest live in a project