How a dowel project is described. This page is the model overview; the detailed references are:
| Document | Contents |
|---|---|
| 11-toml-reference.md | dowel.toml: every table and key that is read, and how it is validated |
| 12-build-reference.md | dowel.build: the complete syntax, and every configurable target/runner property |
| 13-semantics.md | how it functions: evaluation, specialization, propagation and merging, planning, execution |
Everything in these references describes the current implementation. Where a designed feature is not implemented yet, it is marked in place; the summary list is in 91-implementation-status.md.
A package is a directory containing two manifest files. The split is deliberate (ADR-0003).
| File | Format | Written by | Contents |
|---|---|---|---|
dowel.toml |
strict TOML | machines (read and write) | package identity, dependencies, toolchain, feature flags |
dowel.build |
TOML-style dialect | humans | target definitions, propagated properties, conditionals |
dowel.lock |
generated | machines | resolved external dependencies (version deps via pkg-config); detects environment drift (11-toml-reference.md) |
dowel.toml stays strict TOML — expressions are rejected in value position
(diagnostic expression-in-strict-toml) — so third-party tools (SBOM
generators, vulnerability scanners, update bots) can read it without
implementing this language.
dowel.build is a superset dialect of TOML that adds expressions in value
position only. Its extension is deliberately not .toml, so editors do not
apply TOML mode; completion, highlighting, and diagnostics come from
dowel lsp instead.
dowel.build is evaluated into typed values that carry their source
location and provenance. Conditionals (match, when) are not resolved
here--config,
--target, --features): match picks an arm, when keeps or drops
elementsunion, append,
error_on_conflict, must_equal, replace)glob(...), resolves paths, and builds the
action graph (compile / archive / link), which a backend — ninja by
default — runsBecause step 2 is separate from steps 3–5, switching --config or
--target does not re-evaluate manifests, and every value can answer
dowel why <target> <property> with the exact chain of declarations that
produced it.
# dowel.toml
[package]
name = "libfoo"
version = "0.3.1"
# dowel.build
[lib.foo]
sources = glob("src/**.c")
[lib.foo.public]
includes = [dir("include")] # propagates to dependents
[lib.foo.private]
includes = [dir("src")] # affects only this target
flags = match cfg.opt {
debug => ["-O0", "-g3"],
release => ["-O2", "-DNDEBUG"],
}
A complete two-package example, with a dependency and a test, lives at
examples/hello and is built by the test suite on every
run.