Task-oriented how-tos. Everything described here is implemented. Installation and the first build are in 62-getting-started.md, the complete option list in 60-cli.md, and the manifest syntax in 12-build-reference.md.
dowel build # build every bin / test
dowel build app # by name: <target> or <package>:<target>
dowel build --config=release
--config (debug / release; default
debug). Build directories are separated per configuration, so switching
never clobbers the other’s outputs--backend=direct runs the steps sequentially with no external generator.
--backend=make generates a Makefile, and --backend=graph writes the
build description for a tool of your own (14-build-graph.md)-j/--jobs is the parallelism passed to the backendcompile_commands.json is written on every build; suppress with
--no-compdbdowel test # every test target
dowel test app:unit # by name
dowel test --nocapture # pass output through (default shows failures only)
dowel test --failed # rerun only what failed last time
dowel test --fail-fast # stop at the first failure
dowel test --test-jobs=4 # run 4 at a time (default is sequential)
dowel test --no-run # build only; do not run
--failed persist in the build directory; verdicts of
targets that were not run are keptBranching on the manifest side uses match / when
(12-build-reference.md section 5). From the CLI:
dowel build --config=release
dowel build --features=zlib,png
dowel build --no-default-features
The domain of feature names is defined by [features] in dowel.toml.
Unknown names fail with a diagnostic — both when referenced from dowel.build
and when passed to --features — with suggestions. Switching --config /
--target does not re-run manifest evaluation (branch resolution is deferred
to the specialization stage).
Value provenance: where a propagated value came from, with source locations.
$ dowel why app:app includes
include/ Path
← public.includes of target:foo libfoo/dowel.build:18
← deps of target:app app/dowel.build:7
Graphs: the target dependency graph and the action graph, as text / dot / json.
dowel graph # target dependency graph
dowel graph --kind=action # action graph
dowel graph --format=dot | dot -Tsvg -o graph.svg
Rebuild reasons and the actual command lines are in the log:
DOWEL_LOG=debug dowel build # per-stage timing, graph sizes, freshness verdicts
DOWEL_LOG=trace dowel build # dependency edges, the full command line of every action
The per-source breakdown of trace output is in 91-implementation-status.md.
Declare the toolchain per target triple with [toolchain.<triple>] in
dowel.toml, and an execution wrapper with [runner.<triple>] in
dowel.build. dowel build --target=<triple> compiles with the declared
toolchain, and dowel test --target=<triple> launches through the wrapper
transparently. A --target with no declared toolchain is refused before
building (missing-toolchain) — the host compiler is never substituted.
# dowel.toml
[toolchain.riscv64gc-unknown-linux-gnu]
c = "riscv64-linux-gnu-gcc"
ar = "riscv64-linux-gnu-ar" # archives too — do not fall back to the host's ar
For bare-metal work, declare the tools that turn the ELF into something a programmer can write, and the images become part of the build:
# dowel.toml
[toolchain.thumbv7em-none-eabihf]
c = "arm-none-eabi-gcc"
ar = "arm-none-eabi-ar"
objcopy = "arm-none-eabi-objcopy"
# dowel.build
[bin.firmware.artifacts]
bin = { tool = "objcopy", args = ["-O", "binary"] }
hex = { tool = "objcopy", args = ["-O", "ihex"] }
dowel build --target=thumbv7em-none-eabihf now produces firmware.bin and
firmware.hex next to the ELF, re-running the conversion only when the ELF
changed (12-build-reference.md).
qemu:
[runner.riscv64gc-unknown-linux-gnu]
command = "qemu-riscv64"
args = ["-L", "/usr/riscv64-linux-gnu"]
Real hardware over SSH. When the target machine cannot see the build machine’s file system, declare a transfer:
[runner.aarch64-unknown-linux-gnu]
host = "board.local"
remote_dir = "/tmp/dowel"
transfer = ["scp", "-q"]
command = "ssh"
args = ["board.local"]
This expands to the following. Source and destination paths are not written in the manifest; the implementation appends them (ADR-0008).
scp -q <build>/bin/unit_test board.local:/tmp/dowel/unit_test
ssh board.local /tmp/dowel/unit_test
transfer and remote_dir are specified togetherssh, the target
machine’s exit status is the verdictExec format error reported as a test failure)There are three paths; they serve different files.
| Target | Path |
|---|---|
dowel.build / dowel.toml |
dowel lsp — a language server providing diagnostics and hover |
| C sources | clangd, fed by the compile_commands.json that dowel build writes |
| VS Code | the client in editors/vscode/: launches dowel lsp and adds syntax highlighting |
dowel lsp speaks LSP on stdin/stdout; the editor is the one that starts it
(nothing stays resident). Hover shows property types and merge rules, builtin
function signatures, and configuration key domains. Diagnostics cross files:
with dowel.toml and dowel.build open, an unknown feature name, an
undeclared dependency, or a merge conflict with a dependency shows up in the
editor, against unsaved buffer contents. Plan-stage checks (glob expansion,
path resolution, toolchain probing) still belong to dowel check.
Memoized evaluation results live under .dowel/cache/.
dowel cache info # store size
dowel cache gc # collect stores left by older formats
cache info / cache gc do not read the manifests, so cleanup works even
when a manifest is brokenflock); a process that cannot take
the lock reads onlyOutput can be machine-readable. stdout carries artifacts and stderr carries progress and logs — always — so piping is safe.
dowel check --message-format=json # one diagnostic per line, with stable codes, locations, fix suggestions
dowel test --message-format=json # one test result per line
dowel build --log-format=json # logs as JSON too
dowel schema dump # the schema and configuration vocabulary, machine-readable
dowel test returns nonzero if even one test failsunknown-property and so on) are a compatibility surfacedowel schema dump is also intended as context for LLM agents
(30-devexp.md section 4)Start from a draft extracted out of the real configuration, then keep checking against the old build until the port is equivalent:
# 1. have CMake emit its model, and draft manifests from it
mkdir -p build/.cmake/api/v1/query && touch build/.cmake/api/v1/query/codemodel-v2
cmake -B build ...
dowel migrate import build # writes UNVERIFIED dowel.toml / dowel.build
# 2. edit the draft (promote public headers, restore conditionals), then
dowel migrate verify build/compile_commands.json
The draft is deliberately conservative — everything private, sources listed explicitly — because it is a snapshot of one configuration and the intent is lost (60-cli.md).
Sources are matched one by one and their compile arguments compared after
normalization (spelling differences like -DX vs -D X, relative vs
absolute -I, and output/depfile flags don’t count). A ported source with
differing arguments fails the run and lists each difference with its
direction; sources you haven’t ported yet are reported but don’t fail —
migration proceeds target by target. --format=json for CI
(60-cli.md).
match arms, CLI options) come with edit-distance suggestions--message-format=json, fix suggestions carry a span and a replacement
string, and can be applied mechanicallydowel why (value provenance) and
DOWEL_LOG=debug (execution reasons)