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.
dowel <command> [options] [args]
--name value and --name=value--confg → did you mean --config?)| 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.
| 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.
| 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 |
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).
| 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 newdowel 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 adddowel 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:
<path> — creates a library package at the sub-path (relative to the
package root) and appends the matching [[dependencies]] entry with a
path source--git <url> — appends a git dependency instead of scaffolding
anything. The manifest only ever receives a full 40-digit sha: an
explicit 40-digit --rev is written as-is, while a name (or, with --rev
omitted, HEAD) is resolved once via git ls-remote and the
resolved sha is what gets pinned — the same judgment as dowelup pin.
dowel check fetches it on first use| 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 checkdowel 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 builddowel 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/.
Executables land in its bin/ (./.dowel/build/*/bin/<name>)[toolchain] in dowel.toml (default: cc on
PATH). Toolchain fetching is not implemented; whatever is named must be on
PATH--target=<triple>
requires a [toolchain.<triple>] declaration
(11-toml-reference.md); a triple with none is
refused before building with missing-toolchain rather than silently
building host artifacts under that triple’s namedowel testdowel 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 |
--target=<triple> differs from the host, launch goes through the
declared runner ([runner.<triple>] in
12-build-reference.md). If no runner is declared,
the launch is refused with a diagnostic beforehand--message-format=json emits one result per line on stdoutdowel inspectdowel 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.
== <target>: <name> (<tool>) ==
headings to stderr, so dowel inspect > sizes.txt keeps just the reports--message-format=json emits one object per inspection per line, carrying
the full command, the exit verdict, and the outputdowel whydowel 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 graphdowel 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 verifydowel 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 importdowel 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: EXECUTABLE → bin; STATIC_LIBRARY / OBJECT_LIBRARY (and,
with a note, SHARED_LIBRARY) → lib; in-project dependencies →
target(...); 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 dumpdowel 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 cachedowel 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 lspdowel 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.
publishDiagnostics in response to
changes. Cross-file diagnostics (the feature vocabulary, dep(...) /
target(...) resolution, merge conflicts, cycles) come from a workspace
model in which the open buffers overlay the disk. The editor session never
fetches and never touches the store. Plan-stage checks that scan the file
system are not produced (dowel_lsp::UNSUPPORTED lists them with reasons)dowel.toml is recognized by name and held to strict TOML validationThe VS Code client lives in editors/vscode/.
--message-format=json emits one JSON diagnostic per line on stdout. Each
diagnostic carries:
unknown-property, …). Codes are a
compatibility surfaceThe list of codes, with the minimal input that produces each, is defined in
the case table of crates/dowel-cli/tests/diagnostics.rs.
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.
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.