ADR-0028: What was asked of a tool is recorded outside the project, keyed by the tool’s identity
Status: Accepted
Context
20-architecture.md section 9 decomposes cold
configure into probe execution, discovery sweeps, and manifest evaluation,
and names the countermeasure: treat probe results not as an implicit cache
(CMakeCache.txt-style) but as an independent fact database — keyed by
toolchain identity, shareable across projects, and the top of the durability
hierarchy (section 5). Section 3’s query catalog lists probe(toolchain,
check).
Nothing of it existed. Two things followed.
An unrecorded input. dowel’s host triple was assembled from the OS and
architecture it was compiled for — x86_64-unknown-linux-gnu, always. What
the machine’s compiler calls itself was never asked. A user whose gcc
says x86_64-pc-linux-gnu, passing that spelling to --target, is treated
as cross-compiling and asked for a runner that cannot exist. The comment on
default_triple said as much: provisional, to be replaced by the probe-fact
DB.
Repeated questions. Every run re-asked whether ninja answers
--version — a process launch, on every dowel build, in every tree.
Decision
Facts learned about a tool are recorded in the user’s cache area, not the project’s:
$XDG_CACHE_HOME/dowel/facts/v1/facts one record per line, <key>\t<value>
- Outside the project, because a fact belongs to the tool. As long as
the same compiler is used the answer is the same in every tree; under
.dowel/cache/the same question is re-asked once per tree. The thing at the top of the durability hierarchy would be living in the most volatile place. - The key carries the tool’s identity — path, size, mtime — alongside
the question. There is deliberately no invalidation mechanism: replace the
tool and the key changes, so the old fact is simply never asked for again.
dowel cache gccollects what is no longer reachable. - Only questions that start a process are recorded. Scanning
PATHis a fewstatcalls; recording it would cost more to keep honest than it saves. What gets recorded is-dumpmachineand--version. - A recorded resolution is still checked for existence. A tool can be
removed without
PATHchanging, and reporting an absent tool as present turns a clear diagnostic into an exec failure later. - “It did not answer” is a fact too.
clhas no-dumpmachine; without recording the silence, every run asks again. - Unwritable is not an error. No lock, no failure — the same judgment as the store (20-architecture.md 5.3): what is lost is the saving, not the answer.
The first reader is the host triple. configure asks the host C
compiler what it calls itself and uses that as Config::host; a target
matching either that name or the assembled approximation counts as the host.
Consequences
- The host triple becomes a recorded input rather than a property of “the
machine dowel was compiled on”. A user’s own spelling of their triple now
works with
--target. - Cold configure loses a process launch per run once the facts are warm, and the effect compounds across projects — which is the point of putting them outside the tree.
- A fact file shared between dowel versions is possible; the format version in the path is what keeps a future format from being misread.
- mtime is the identity, not content. A tool rebuilt with identical content looks new (a re-probe, which is cheap); a tool replaced with the timestamp preserved looks old (a stale fact). The second is the real risk, and it is the same bet every build system makes about source files.
- This is not yet
try_compile. The catalog’sprobe(toolchain, check)wants “does this flag work”, which needs a compile in a temporary directory; the mechanism here — key, storage, sharing, invalidation by identity — is what that would be built on. - The facts are not consulted by the language server, which starts no external processes at all (20-architecture.md 6).