The registry schema
documents.yaml is the registry. It is read and validated by
docrefs.load() (per-document view, used by every
conf.py) and docrefs.manifest() (whole-registry
view, used by add_docs_from_registry) — both
built on the same validation pass, so a registry that CMake accepts is a
registry every document’s conf.py also accepts. See The registry, and what it derives
for how the derived structures are used; this page is the field-by-field
contract.
An invalid registry is a configure-time ValueError, naming the offending
document. Nothing described as “required” below degrades gracefully — a
missing field is a hard failure, not a silently empty result.
Top level
groups(required, non-empty)A list of group entries. Every document must belong to one of the ids declared here.
documents(required)A mapping of document id to document entry.
base_urlThe set’s base URL, used for cross-document links in output outside
deploy/html/(PDFs,html-live); the HTML tree links its peers relatively. There is no validation requiring this key to be present: an absentbase_urlresolves to the empty string, which the engine then turns into a single"/"("".rstrip("/") + "/") — those links would then be absolute URLs rooted at/. Treat this as effectively required for any set that builds PDFs. Overridable per build with-DZDOCS_DOC_BASE_URL=…(The ZDOCS_* contract).external_base_urlPrefix for a
remote-url:that has no scheme (seekind: externalbelow). Lets a whole externally-hosted document set move by editing one value. Overridable with theZDOCS_DOC_EXTERNAL_BASE_URLenvironment variable.check_external_urlsBoolean (YAML truthy strings also accepted via the
ZDOCS_DOC_CHECK_EXTERNAL_URLSenvironment override), default off. When on, every remote document’s URL gets a best-effort HTTPHEADat build time; a network error is reported as “not reachable”, never as a build failure. Off by default because a build host may have no route to an internal-only site.xref_smoketestDeploy-relative path (e.g.
"html/runbook/xref-test.html") to a pagedoccheckscans for cross-reference bullets that rendered as plain text or empty — see Command-line tools. Optional; without it,doc-checksimply skips that one check.doc_check_acceptedOptional list of
doccheckfindings to let through, for defects the consumer cannot fix (e.g. one inside a generated upstream API):doc_check_accepted: - finding: "html/api/structfoo.html: dead link -> structfoo_1_1_0d13.html" reason: "Doxygen does not generate nested anonymous struct pages"
findingis the finding’s exact printed text, never a pattern, so an acceptance cannot swallow a new, different finding.reasonis required. Accepted findings do not fail the check, but they are still printed underaccepted (N)with their reason. An entry that no longer matches anything is printed underaccepted but no longer foundwithout failing, so fixing the defect never breaks the build and the list does not rot. A malformed entry is a bad invocation (exit 2).doxygen_xmlProject-scoped boolean, default off. When true, every
kind: doxygendocument in the registry generates XML intodeploy/xml/<bare-name>/— not per document, and not influenced by what any individualDoxyfile.insays aboutGENERATE_XML(the engine forces it in both directions; see Doxyfile keys the engine owns below). Required for thetestmoduledirective, which parses that XML.docs_rootDefault
"docs". Read only bydocctl.py, to resolve a document’s source directory as<registry dir>/<docs_root>/<group dir>/<document id>when locating its.. doc_control::block (Command-line tools). Neitherdocrefs.pynor any CMake module reads this key at all — it has no effect on where Sphinx or Doxygen look for a document’s sources; that is entirelydoc_dir:(below) and theadd_sphinx_target/add_doxygen_targetDOCDIRconvention. A docset that runs nodocctlaction can ignore this key.
Groups
Each entry in groups::
id(required)Referenced by every document’s
group:.title(required)The heading shown above this group’s links in the navigation sidebar.
modeOne of
exclude_if_selected(default — a page never links to itself),single_doc_title_merge(same exclusion, but rendered merged into the caption for a group that always resolves to exactly one document), oralways_keep(every document in the group is always listed, including the current page).displayOne of
disabled_collapsing(default — always fully shown, no expand/collapse control),collapsed_at_opening,not_collapsed_at_opening, orno-display(the group is never shown, regardless of how many links it has).dirA subdirectory name, read only by
docctl.pyas the middle segment of thedocs_root-based path above. No sample or fixture in this repository sets it — every registry either omits it (empty middle segment) or does not usedocctlat all, so this field’s behaviour beyond the default is documented but unexercised.
Documents
Each entry in documents:, keyed by its id:
group(required)Must name a declared group id.
kindOne of
sphinx(default),doxygen,external,sphinx-external,doxygen-external— see The five kinds below and Documents zdocs does not build.titleShown in navigation and as the Sphinx project title (
html_title). Falls back to the bare id if omitted.prefixThe document’s cross-reference prefix. Defaults to the id with
-replaced by_. Not validated for uniqueness — nothing indocrefs.pyrejects two documents sharing a prefix; sinceintersphinx_mapping/doxylinkare built by assigning into a plain dict keyed by prefix, a collision would silently make one document’s mapping entry overwrite the other’s, in registry order, with no diagnostic. Keep prefixes distinct by convention.buildersRequired, non-empty, for a
sphinxdocument (or one omittingkind:entirely) — e.g.[html]or[html, latex]. Asphinxentry with nobuilders:is a configure-timeFATAL_ERRORnaming the document, raised byadd_docs_from_registrybefore any target in the registry is created. Meaningless (and ignored) for every other kind.doc_dirRelocates where this document’s sources are read from, without changing its id, target names or deploy path. When set through
add_docs_from_registry, a relativedoc_dir:is resolved against the directory containing documents.yaml itself — not against whicheverCMakeLists.txtcalledadd_docs_from_registry(). Those two directories are usually the same one, but are not the same thing by definition.version_scopeA git-tag namespace (e.g.
"widget", matched as<scope>/v*in the consuming repository, then stripped for display —widget/v2.3renders asv2.3). Exercised throughout this repository’s samples and fixtures.version_projectAn alternative to
version_scope: a west project name, whose own tags are used instead (west list --format {abspath} <project>thengit describein that path). No sample or fixture in this repository sets it; this path is implemented and read (docrefs.resolve_version()) but unverified against a real registry here.pathDeploy-relative path override, defaulting to
html/<id>. No known registry entry in this repository sets it explicitly — every one relies on the default, which is also what the builder-first deploy layout (The deploy tree) assumes everywhere else. Treat an explicit override as unverified.remote-urlRequired for
external,sphinx-externalanddoxygen-external. An absolute URL (has a scheme) is used as-is; a relative one is resolved againstexternal_base_url.remote-tagfileRequired for
doxygen-externalonly, in addition to (never instead of)remote-url— the two are validated independently, since Doxygen tag file names are not standardised and one cannot be derived from the other. Downloaded at build time and stored locally asdoxygen.tag, whatever the remote file is actually called.crossrefBoolean, default
true.falsetakes the document out of the cross-reference graph in both directions: no peer gets an intersphinx, doxylink, external-needs or DoxygenTAGFILESentry for it, and it gets none for its peers. It is still built, deployed and listed in the navigation. Meant for a large reference build that documents a superset of its peers’ symbols — a project’s full API beside a scoped subset. Doxygen projects that import each other’s tag files leave shared symbols to the other project, so neither generates their pages. A quoted"false"is a configure-time error rather than a truthy string.needsOpt-in sub-block; presence is what makes this document importable as external needs by every peer. Two shapes:
needs: source: json # this document already runs sphinx-needs
needs: source: inventory # synthesize needs from a plain objects.inv filter: "^DUTY_" # regex on the label name; default matches all type: requirement # need type stamped on every synthesized need status: approved version: "1.0"
source: inventoryis for a Sphinx document that publishes labels but runs no sphinx-needs of its own (e.g. a requirements tool that only emits anobjects.inv); the stub is regenerated whenever the inventory is newer than the last-generated one. Atestmodule.spec:(below) must point at a document withsource: jsonspecifically — asource: inventorydocument is rejected at configure time as aspec:target, becausetestreportcorrelates againstneeds.json, and the synthesized stub is not that.When the needs that a peer imports from this document change, the peer reads all its sources again on the next incremental build (the engine’s
needs_config_stateextension). Otherwise the pages of the peer do not show a new incoming link from this document.doxygen_tagOpt-in; publishes this document’s needs as Doxygen requirements, so a
\verifiesor\satisfiesin anykind: doxygenpeer resolves against requirements authored in reStructuredText, and links to their Sphinx pages. Two shapes:doxygen_tag: true # every need this document defines
doxygen_tag: types: [requirement] # only needs of these types
Requires
kind: sphinxandneeds: {source: json}, because the tag file is generated from this document’s ownneeds.json. Anything else is a configure-time error, as are an unknown key and an empty or non-listtypes. The engine adds a<id>-needstagtarget that writesdeploy/html/<id>/needs.tagafter the document’s stage-1 index, and lists that file in every Doxygen peer’sTAGFILES. Imported (external) needs are left out, because their own document publishes them.Nothing but Sphinx parses the requirements, so no
.doxis generated and no Doxygen project is built for them. A\verifiesnaming an id the tag does not declare warnsReference to unknown requirement, which the stage-2 warning gate (ZDOCS_DOXYGEN_WARN_FAIL_PATTERNS) fails on.crossref: falseon either side removes the entry, as for any tag file. Needs Doxygen 1.16 or newer.symbol_needsOpt-in sub-block; loads the
symbolneedsdirective (Directives and roles) into this document, which emits one need per API symbol that carries a Doxygen\satisfies, linked to the requirements it names. One key, required:api-documentation: kind: sphinx builders: [html] needs: source: json # so peers import the symbol needs symbol_needs: doxygen_source: dox-safety-api # a kind: doxygen document
doxygen_sourcenames thekind: doxygendocument whose XML (deploy/xml/<id>/, so the top-leveldoxygen_xml: trueis needed) holds the symbols; every stage-2 builder of this document waits for it, as fortestmodule.doxygen_source. Its HTML is what each need links to. Only akind: sphinxdocument may carry the block; a missing or unknown key, a non-mapping value, or adoxygen_sourcethat is not an existingkind: doxygendocument is a configure-time error.Add
needs: {source: json}as shown: it is what makes every peer import the symbol needs, so a requirement authored in another document lists its implementing symbols (“satisfied by”, or whatever incoming name yourneeds_config.tomlgives the link) beside its verifying test cases. Without the block, nothing changes: the extension is not loaded.testmoduleOpt-in sub-block (see From annotated C to a test report and Directives and roles); its presence is the sole trigger that loads the
test_moduleextension for this document. Allowed keys, each optional individually but validated when present:doxygen_sourceId of a
kind: doxygendocument whose XML thetestmoduledirective parses. Must exist and must bekind: doxygen— otherwise a configure-time error naming this document, its field and the bad id.api_referenceSame existence/kind requirement as
doxygen_source, for where@seecross-references resolve. A symbol Doxygen resolved through another document’s tag file links into that document instead (Doxygen records the tag file on the reference);api_referencetakes only the references no registry document’s tag file accounts for. A@seetarget that no Doxygen project documents has no reference. It shows as a literal in the “See also” line, without a link. The line has the targets of all@seelines of the test, in order.specId of the sphinx document whose exported needs a
testreportdocument correlates against. Must exist, and must carryneeds: {source: json}— aspec:pointing at a document with no needs export, or asource: inventoryone, is a configure-time error.
An unknown key anywhere in this block (a typo like
doxygen_src) is itself a configure-time error naming the allowed set, rather than being silently ignored — silently ignoring it would degrade to “no XML parsed” with no diagnostic at all.
The five kinds
|
What it produces |
Required fields |
|---|---|---|
|
A local Sphinx build; an |
|
|
A local Doxygen build; a |
— (no |
|
No build; a |
|
|
No local build; a real intersphinx fetch at build time |
|
|
No local build; the engine downloads the tag file itself |
|
A kind: doxygen document’s registry id is taken verbatim: it becomes the
target name, the default source folder and both deploy paths, with nothing
prepended or stripped —
add_docs_from_registry
passes the id straight through to
add_doxygen_target.
The engine has no naming convention of its own here; a consumer who wants
one writes it into the id. An id of widget gets a bare target, folder and
deploy path; an id of dox-widget gets a dox- prefix on all of them,
opaque to the engine either way.
Doxyfile keys the engine owns
A line in your Doxyfile.in setting one of these is read, then discarded —
the engine’s own value is appended to the generated doxyfile after your
template is expanded, and Doxygen keeps only the last value of a repeated key
(Cross-cutting concepts). Source:
cmake/doxygen.cmake.
Assigned outright (a consumer’s own line is fully overridden):
HTML_OUTPUT— forced to"."(Doxygen’s syntax for “no subfolder”), becauseOUTPUT_DIRECTORYis itself already the document’s final public HTML directory under the builder-first deploy layout.GENERATE_TAGFILE— forced to<html dir>/doxygen.tag.GENERATE_XML— forced toNO, orYESwhen the top-leveldoxygen_xml:opt-in is on. Either way, this overrides your own setting in both directions.XML_OUTPUT— forced to an absolute path outside the servable tree, only when XML is enabled.HTML_STYLESHEET,HTML_EXTRA_STYLESHEET,HTML_EXTRA_FILES— reset to empty first (so a template shipping its own copy of the vendored theme does not load two versions of it), then appended to as below.HTML_HEADER— the engine’s own header template.GENERATE_TREEVIEW,HTML_COLORSTYLE— fixed theme settings.HTML_FOOTER— set only when aREGISTRYis given (cross-document nav needs siblings to list).
Appended to with += (a consumer’s own entries survive, engine entries are
added):
TAGFILES— one entry per otherkind: doxygen/doxygen-externalpeer, derived from the registry.STRIP_FROM_PATH,STRIP_FROM_INC_PATH—ZDOCS_PROJECT_BASE,ZDOCS_WEST_TOPDIRandZDOCS_DOXYGEN_INC_ROOTS.HTML_EXTRA_STYLESHEET,HTML_EXTRA_FILES— the vendored theme, then (if set)ZDOCS_PROJECT_LOGO/ZDOCS_DOXYGEN_EXTRA_CSS, then the cross-document navigation widget last, so its overrides win over everything before it.
A separate, stage-1-only mechanic is not in this list because it is not part
of the generated doxyfile at all: the stage-1 build runs against a tiny
overlay file that @INCLUDEs the full doxyfile and then blanks
TAGFILES with a plain (non-appending) assignment, so a document’s own
first build never depends on a peer’s not-yet-built tag file.