dowel

How-to guides

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.

1. Building

dowel build                      # build every bin / test
dowel build app                  # by name: <target> or <package>:<target>
dowel build --config=release

2. Running tests

dowel 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

3. Switching configurations and feature flags

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

4. Investigating “why”

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.

5. Cross compilation and runners

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

6. Writing in an editor

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.

7. Managing the cache

Memoized evaluation results live under .dowel/cache/.

dowel cache info                 # store size
dowel cache gc                   # collect stores left by older formats

8. Using from CI and tools

Output 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

9. Migrating from CMake

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

10. Reading diagnostics