dowel

Command reference

The specification of every command and option dowel provides. Everything in this document is implemented; the not-yet-implemented list is in 91-implementation-status.md. Task-oriented how-tos are in 63-guides.md.

Invocation

dowel <command> [options] [args]

Contract shared by every command

Output streams

Stream Contents
stdout Artifacts: JSON diagnostics, graphs, the schema, why results
stderr Progress and logs

Because of this split, dowel graph --format=dot | dot -Tsvg works at any log level.

Exit status

Status Meaning
0 Success, including runs whose diagnostics are warnings only
anything else An error occurred; diagnostics appear on stdout / stderr per the split above

dowel test returns nonzero if even one test fails. When --fail-fast cut the run short, the summary reports how many tests were not run.

Common options

Option Values Default Meaning
-C, --directory <path> path . operate on the package in this directory
--config <name> debug / release debug build configuration
--target <triple> target triple host cross-compilation target (63-guides.md section 5)
--features <a,b> comma-separated feature flags to enable; may be repeated
--no-default-features do not pull in default from [features]
--message-format <fmt> human / json human diagnostic format
-v, --verbose more logging; once for info, twice or more for debug
--log-level <level> off / error / warn / info / debug / trace log level; an explicit value overrides -v
--log-format <fmt> text / json text log format (one object per line)
--max-nesting <n> number 64 maximum value-nesting depth the parser accepts (see below)
--color <when> auto / always / never auto color; auto currently resolves to no color (no terminal detection), so pass always explicitly when needed
-h, --help print usage
-V, --version print the version

Nesting limit

Parsing accepts value nesting up to 64 levels by default; anything deeper gets a nesting-too-deep diagnostic at the offending position. If a generated manifest exceeds this, raise it with --max-nesting=<n> (capped at 512 — accepting stack-exhausting depth would return an abort instead of a diagnostic).

Environment variables

Variable Meaning
DOWEL_LOG same as --log-level; DOWEL_LOG=trace dowel build

What each log level shows (debug: per-stage timing and graph sizes; trace: dependency edges and the full command line of every action) is broken down in 91-implementation-status.md.

dowel new

dowel new <path> [--lib]

Creates a package in a new directory (which must not exist or be empty). The package name is the last path component and must be a valid identifier (a letter or _, then letters, digits, _, -).

Option Meaning
--lib generate a library package instead of an executable

The default skeleton is a bin package (dowel.toml, dowel.build, src/main.c, .gitignore); --lib generates a library with a public header under include/, a private src/, and a test target that dowel test runs as-is. The generated packages are built and executed by the test suite on every run, so the skeletons cannot silently rot.

dowel add

dowel add <path> [--name <n>]
dowel add --git <url> [--rev <rev>] [--name <n>]

Run inside a package; declares a dependency in its dowel.toml. Two forms:

Option Meaning
--git <url> declare a git dependency instead of scaffolding a package
--rev <rev> the commit to pin; a non-sha name is resolved once via git ls-remote
--name <n> the dependency name (default: the last path or URL component)

The append preserves the existing manifest text untouched — position carries no meaning for array tables in strict TOML.

What it does not do: wire the dependency into a target. Which target uses it is a per-target choice, so the command prints the exact deps = [dep("<name>")] line to add instead (12-build-reference.md). A name already declared in dowel.toml is refused.

dowel check

dowel check [common options]

Runs through the planning stage and reports diagnostics only. Nothing is compiled, linked, or executed. Because it covers glob expansion, path resolution, and toolchain existence, the configuration diagnostics that build would report also come out of check (the scope is set by ADR-0010). It is faster than a build and intended to run on every save.

dowel build

dowel build [target...] [common options] [build options]

Plans the build and hands it to a backend. With no targets named, builds every bin and test. Naming accepts <target> or <package>:<target>.

Option Values Default Meaning
--backend <name> ninja / direct / make / graph ninja when available who runs the build (below)
-j, --jobs <n> number the backend’s default parallelism, passed to the backend
--no-compdb do not write compile_commands.json

The backend is the output stage (ADR-0018). All of them receive the same build graph, so which one runs is not supposed to change what gets built.

Backend What it does
ninja writes build.ninja into the build directory and runs ninja. The default where ninja is on PATH
direct runs the steps in process, one at a time, judging freshness by mtime, depfiles, and the command line itself. Needs no external generator. The fallback when ninja is absent
make writes Makefile and runs make. Refuses a build whose paths make cannot name (whitespace, :, #, $, %, ;, =, \, *, ?, [, ]) rather than writing a makefile that builds something else
graph writes build-graph.json — the backend-neutral description (14-build-graph.md) — and stops. Nothing is compiled; the document is for a tool of your own. dowel test and dowel inspect refuse it

--executor, the previous spelling, is refused with a message naming --backend: the set of values it takes has changed.

dowel test

dowel test [target...] [common options] [build options] [test options]

Builds the test targets, runs them, and judges pass/fail by exit status (0 = success). No test harness is imposed; the C convention applies. The working directory is the package root. By default only the output of failing tests is shown.

Option Values Default Meaning
--no-run build only; do not run
--nocapture pass test output through
--fail-fast keep going stop at the first failure; the summary reports how many were not run
--failed rerun only what failed last time; verdicts persist in the build directory, and verdicts of targets not run are kept
--test-jobs <n> number 1 (sequential) how many tests run at once; display is always in request order

dowel inspect

dowel inspect [target...] [common options] [build options]

Builds, then runs the tools declared in [<kind>.<name>.inspect] (12-build-reference.md) and passes what they report through. With no target, inspects every target that declares an inspection; a named target with none simply reports nothing.

An inspection produces no file, which is why it is a command rather than part of build: there is nothing to be up to date about, so running it on every build would be noise and running it never would make the declaration pointless. The tool’s output is not parsed — size’s format differs between implementations, and reading it is the tool’s job.

dowel why

dowel why <target> <property> [--format <text|json>]

Shows the path a value took to reach the target, down to its origin, 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
Option Values Default
--format <fmt> text / json text

dowel graph

dowel graph [--kind <target|action>] [--format <text|dot|json>]

Dumps a graph to stdout.

Option Values Default Meaning
--kind <kind> target / action target target dependency graph / action graph
--format <fmt> text / dot / json text output format; dot can be fed straight to Graphviz

--kind=action --format=json prints the build graph document — byte for byte what dowel build --backend=graph writes to a file (14-build-graph.md). There is one JSON description of an action graph, and it is the one the backends run on.

dowel migrate verify

dowel migrate verify <compile_commands.json> [--format <text|json>]

Compares a reference compile database — what the existing build system actually does — against dowel’s plan, source by source (13-semantics.md; the design is 40-migration.md section 4). Migration becomes a continuous equivalence check instead of a one-shot conversion: “this target is ported and produces the same compile arguments” is confirmed mechanically.

Commands are normalized before comparison, so equivalent-but-differently- spelled commands match: -D NAME / -DNAME / -DNAME=1 are the same define, -I paths are resolved against each entry’s directory, and the compiler name, -c / -o, and depfile flags (-MD family) are ignored. Configuration-level flags (optimization, debug info, NDEBUG) are dropped from both sides: dowel’s debug/release configuration supplies them on one side and the reference’s build type on the other, so they say nothing about whether the port is faithful. Remaining flags are compared as a multiset.

The report has four buckets:

Bucket Meaning Fails the run
equivalent same source, same normalized arguments
differing same source, different arguments; each difference is listed with its direction yes
not ported sources only in the reference no (porting is incremental)
only in dowel sources only in dowel’s plan (tests, new targets) no

Exit status is nonzero only when a ported source differs. --format=json prints the same report as one JSON object on stdout.

dowel migrate import

dowel migrate import <cmake-build-dir>

Drafts dowel.toml / dowel.build from a CMake File API reply (codemodel-v2), writing them into the CMake source directory — next to the code they describe. Existing manifests are never overwritten. To make CMake produce the reply:

mkdir -p build/.cmake/api/v1/query && touch build/.cmake/api/v1/query/codemodel-v2
cmake -B build ...
dowel migrate import build

The output is a draft, not a finished artifact (40-migration.md section 3): it is a snapshot of one configuration, conditionals are lost, and the public/private intent of includes and defines is unknowable from the File API — everything lands in private blocks, and sources are listed explicitly rather than globbed. Each generated file opens with an UNVERIFIED DRAFT header that says so and points at the follow-up:

dowel migrate verify <old-build>/compile_commands.json

Mapping: EXECUTABLEbin; STATIC_LIBRARY / OBJECT_LIBRARY (and, with a note, SHARED_LIBRARY) → lib; in-project dependenciestarget(...); external -l... libraries → link_flags; includes outside the source tree → -I flags. Target names are mapped to valid identifiers. Configuration-level flags coming from the CMake build type (-O / -g / -DNDEBUG) are not copied — dowel’s own --config supplies them, and copying them unconditionally would make a draft imported from Release produce optimized NDEBUG “debug” builds. The draft header states this.

dowel schema dump

dowel schema dump

Prints the schema and configuration vocabulary to stdout in machine-readable form: every kind, each property’s type and merge rule, and the domains of the configuration keys (cfg / host / feature / tc). This is the same table the language server’s hover and diagnostics read; it is not duplicated. The output is also intended as context for LLM agents (30-devexp.md section 4).

dowel cache

dowel cache info
dowel cache gc
Subcommand Meaning
info report the size and record count of the on-disk store
gc remove stores left by older formats

Neither reads the manifests: cleanup must work even when a manifest is broken. The store’s contents and guarantees are described under “The store” below.

dowel lsp

dowel lsp

Speaks LSP on stdin and stdout. The editor is the process that starts it, and it exits with the editor (it is not a resident daemon — ADR-0002). The CLI never depends on the language server’s existence.

The VS Code client lives in editors/vscode/.

Machine-readable diagnostics

--message-format=json emits one JSON diagnostic per line on stdout. Each diagnostic carries:

The list of codes, with the minimal input that produces each, is defined in the case table of crates/dowel-cli/tests/diagnostics.rs.

The store

Memos are kept under .dowel/cache/<format-version>/ (20-architecture.md section 5).

The writer is limited to one process. A process that cannot take the lock reads only and writes nothing back. Computation completes within the process either way, so all that is lost is the cached speedup — results never change. Deleting, truncating, or externally modifying the store likewise changes nothing but speed.

Examples

dowel check --message-format=json
dowel build --config=release
dowel test --failed --fail-fast
dowel why app:app includes
dowel graph --kind=action --format=dot | dot -Tsvg -o actions.svg
DOWEL_LOG=debug dowel build

A working example lives at examples/hello. crates/dowel-cli/tests/example.rs builds and checks it for real, so a change to syntax or semantics that misses the example is detected.