No build system required — dhake evaluates its own buildfile and builds dhake.com from source. The committed dhake.com is the bootstrap binary.
$ ./dhake.com.dbg # evaluates Dhakefile.dhall, builds dhake.com $ ./dhake.com.dbg --list # list targets $ ./tests/build.sh ./dhake.com.dbg # run the test suite
-j N runs up to N independent targets concurrently. On first failure it stops scheduling new targets but lets running ones finish (make-style).
Recipes are Dhall tagged-union values: Shell, Copy, Mkdir, Rm, Touch, Move, Symlink, Chmod, Echo, Env, Run.
Topologically orders targets, detects cycles, and builds only the subgraph reachable from the requested target(s).
Nanosecond-mtime up-to-date checks skip unchanged targets and their dependency closures.
-n prints the actions without running them; phony targets (e.g. clean) are always rebuilt.
A single cosmocc APE that runs on Linux, macOS, Windows, and the BSDs — and builds itself, with every source and the produced binary pinned by hash.
hash / depsHash pin the expected sha256 of outputs and source deps, checked before and after every build. Mismatches fail hard — unless you pass --warn-hash-mismatch, which prints the new hash so pins are trivial to update.
Opt-in write-containment by default — recipes can only write under the build tree, /tmp, and an explicit unveil whitelist. Set readExec to also restrict reads and execs to the toolchain. Set denyNetwork to block network socket creation via seccomp. A rogue recipe can't touch the rest of your disk or phone home.
Mkdir and Rm support parents / recursive flags for mkdir -p and rm -rf behaviour, while legacy bare-path usage stays non-recursive.
--lock[=FILE] writes a machine-readable dhake.lock after a successful build: every target's output and dep hashes plus its transitive dependency closure. A commit-and-diff supply-chain artifact for CI and provenance tools.
--verify (alias --check) pre-flights a build: it checks every pinned hash and up-to-dateness without running any recipe. A cheap CI gate.
--hash-uptodate decides up-to-dateness by content hash instead of mtime — a touched-but-unchanged file no longer triggers a rebuild.
--watch (alias -w) uses inotify on your source-file dependencies and rebuilds on any change — edit-save-refresh becomes a single seamless loop.
--explain (alias --why) prints why each dirty target needs rebuilding — newer dep, hash mismatch, or missing output — without running anything.
--quiet (alias -s) suppresses per-recipe command echo; summaries and errors stay visible.
--define KEY=VALUE (alias -D) injects values into the buildfile's env: imports — one Dhakefile does debug/release or -O0/-O3, CMake-style.
--graph[=dot|mermaid] dumps the full resolved graph (target→dep edges, phony styling, expected hashes) — perfect for inspecting the topology and for docs.
An optional cwd field runs a target's recipe in a subdirectory — no shell cd boilerplate, and it composes with the sandbox.
--arch=NAME sets $DHAKE_ARCH and lets targets carry an arch filter — one Dhakefile cross-compiles x86_64 and aarch64 from a single run.
--cache[=DIR] adds a ccache-style cache keyed on the target name, arch, recipe, and content hashes of all inputs. Unchanged inputs restore the output and skip the recipe — pinned output hashes are still verified on restore.
let Action = < Shell : Text | Rm : Text > let Target = { deps : List Text, phony : Bool, recipe : List Action , hash : Text , depsHash : List { path : Text, hash : Text } , cwd : Text , arch : Optional Text } in { sandbox = { enable = True, readExec = True, denyNetwork = True, unveil = [ "rwc:~/.cache", "rwc:~/.npm" ] } , targets = [ { mapKey = "hello" , mapValue = { deps = [ "hello.c" ], phony = False , recipe = [ < Shell = "cc -o hello hello.c" > ] , hash = "sha256:abc123…" , depsHash = [ { path = "hello.c", hash = "sha256:def456…" } ] , cwd = "src" , arch = None Text } } , { mapKey = "clean" , mapValue = { deps = [] : List Text, phony = True , recipe = [ < Rm = "hello" > ] } } ] , default = "hello" }
Add optional hash and depsHash fields to pin the expected sha256 of outputs and source deps, cwd to run the recipe in a subdirectory, and arch to limit a target to a specific architecture. A top-level sandbox block runs every recipe in a Landlock write-containment sandbox — see the README.
Makefiles are turing-tarpit shell scripts with surprising whitespace semantics. Dhall is a strongly-typed, total configuration language — your build definition gets type checking, imports, and reusable functions, and it always terminates. The build plan is a plain value you can reason about, not a recipe of shell side effects.