The ZDOCS_* contract
Every value a consumer hands to the engine is a ZDOCS_-prefixed
CMake variable. They fall into three groups, and the group matters: where and
how you set one determines whether it is even possible to override later.
Required, set before
include(zdocs).Optional, set before
include(zdocs)— plainset()calls in yourCMakeLists.txt, read once when a document factory runs.Cache options, declared by the engine itself with
CACHE STRINGand meant to be overridden with-Don thecmakecommand line (or left at their default).
A document’s conf.py never reads any of these directly. It is handed a
separate, smaller set of environment variables by add_sphinx_target,
covered at the end of this page.
Required, before include(zdocs)
ZDOCS_PROJECT_BASEThe consuming repository’s root. Doxygen strips this prefix (and
ZDOCS_WEST_TOPDIR, if set) from every recorded path, so rendered output does not carry the build host’s directory layout. Missing this is a configure-timeFATAL_ERROR— raised twice, once byinclude(zdocs)itself and again, independently, the first timeadd_sphinx_targetruns, so the message still names the right cause even if a project reaches the second check through some path that skipped the first.
Optional, before include(zdocs)
Plain variables, read once per document factory call. Passing -D for one
of these on the command line has no effect if your own CMakeLists.txt also
calls set() on it (a plain variable set in your list file shadows a cache
entry of the same name) — these are meant to be computed from your own project
layout, not toggled from outside it.
ZDOCS_WEST_TOPDIRThe west workspace root, for sources that live in a sibling project rather than under
ZDOCS_PROJECT_BASEitself. Also stripped from Doxygen output.ZDOCS_DOXYGEN_INC_ROOTSYour project’s own
-Iroots, so a rendered#include <widget.h>line matches what the compiler actually sees. Doxygen strips the longest matching prefix, so an entry here always wins over theZDOCS_PROJECT_BASE/ZDOCS_WEST_TOPDIRfallback the engine appends unconditionally. There is no default derived from your layout (not even<project>/include): a project keeping headers elsewhere gets nothing from a guess, so the engine does not guess.ZDOCS_PROJECT_LOGOPath to an image, set as Doxygen’s
PROJECT_LOGO. The only place your project’s identity enters a Doxygen page.ZDOCS_DOXYGEN_EXTRA_CSSA list of stylesheet paths, appended to Doxygen’s
HTML_EXTRA_STYLESHEETafter the engine’s own theme — so your rules win.ZDOCS_DOXYGEN_WARN_FAIL_PATTERNSA list of regular expressions. After each Doxygen document’s stage-2 build, a warning line matching any of them fails that document’s target; the full warning log is still printed either way. Defaults to
Reference to unknown requirement— a\verifiesor\satisfiesnaming a UID that no\requirementdefines, which the XML cannot reveal (Doxygen synthesizes the link from the UID regardless). Set it to an empty string to gate nothing. Stage 1 is never gated: it runs withTAGFILEScleared, so its cross-document warnings are false.ZDOCS_SPHINX_EXTRA_ENVA list of
VAR=valuestrings, spliced verbatim into everysphinx-buildinvocation’s environment, for aconf.pythat needs something the engine’s own contract does not carry.ZDOCS_NEEDS_CONFIGPath to a sphinx-needs
needs_config.toml(need types, links and custom fields — see The registry schema’sneeds:field and Directives and roles). Everysphinx-buildthis document’sadd_sphinx_targetlaunches gets it in the environment, andzdocs_conf.configure()falls back to it whenever a document’s ownconf.pypasses noneeds_config=argument — which is the common case, since the methodology is normally project-scoped, not per-document (every document in a set is handed an import of every other’s needs, so they all have to agree on what a need type means).It must declare every need type, link and field the engine’s directives emit under the names you map their roles to: the test directives’ three types, three links and custom fields, and — for a document with a
symbol_needs:block — theimplementationtype andsatisfieslink (Directives and roles). Declaring thesatisfieslink in this project-wide file is also what lets a requirements document show the link’s incoming side. One field is optional:depends_on(the@kconfig_dependsconditions) is set only if declared here.A change to the file’s content makes every document that reads it re-read its sources on the next incremental build (the engine’s
needs_config_stateextension), because sphinx-needs itself rebuilds only the HTML when a type, link or field changes.This variable is real and load-bearing — every sample and fixture in this repository that uses sphinx-needs sets it — but it is not mentioned alongside the others in
cmake/zdocs.cmake’s own “Consumer configuration” comment block, which is an omission in the engine’s own documentation, not in this page.
Cache options (-D at configure time)
Declared with CACHE STRING in cmake/sphinx.cmake, so they show up in
CMakeCache.txt and in cmake -L, and are the intended override surface
from outside your CMakeLists.txt.
ZDOCS_SPHINXOPTSDefault
"-q -j auto". Passed to everysphinx-buildinvocation, stage 1 and stage 2 alike.ZDOCS_SPHINXOPTS_EXTRADefault empty. Appended after
ZDOCS_SPHINXOPTS, for options you want to add without restating the defaults.ZDOCS_DOC_TAGDefault
"development". Passed as a Sphinx tag (-t) on every build, for.. only:: tagblocks and the like.ZDOCS_DOC_BASE_URLDefault empty, meaning “use the registry’s own
base_url:” (The registry schema). Only output outsidedeploy/html/uses it — the HTML links its peers relatively — so set it when a PDF must point at a different host than the registry assumes; base URL is baked in at build time, so changing it later means rebuilding.ZDOCS_TWISTER_OUTDefault empty. Directory holding a twister run’s own output (
twister.json,twister_report.xml, per-scenariohandler.log), read by thetestreport/twisterinfodirectives — see From annotated C to a test report.Its wiring has a subtlety worth stating precisely, because the naive assumption (that an unset cache variable simply forwards as an empty string) is wrong.
cmake/sphinx.cmakeappendsZDOCS_TWISTER_OUT=<value>to thesphinx-buildenvironment only when the cache variable is non-empty:if(NOT ZDOCS_TWISTER_OUT STREQUAL "") list(APPEND SPHINX_ENV ZDOCS_TWISTER_OUT=${ZDOCS_TWISTER_OUT}) endif()
Every other entry in that environment list is unconditional, including ones that default empty —
cmake -E env VAR=setsVARto an empty string for the child process, which would permanently clobber a value the invoking environment had set for that onecmake --buildrun. Leaving this one entry conditional is what lets a build tree configured with no-Dat all still honour a per-invocationZDOCS_TWISTER_OUT=... cmake --build ...override: with nothing appended by CMake,zdocs_conf.pyfalls through to reading the variable straight from the ambient environment at build time (os.environ.get("ZDOCS_TWISTER_OUT", "")), which is where the override actually reaches it. Set it via-Dand every build applies it unconditionally instead, overriding any runtime environment.Leaving it unset entirely is a supported, ordinary state, not a degraded one: the two directives that read it render a short “not found” node and the build still succeeds — a documentation build legitimately outrunning its test run is normal.
ZDOCS_COVERAGE_OUTDefault empty. The directory of a per-test coverage run (
west twister --coverage-per-test), read by thetestcoveragedirective (Directives and roles). The directive reads three files from it:twister.json,coverage/test_matrix.jsonandzephyr.sha(the run commit). It is not the run ofZDOCS_TWISTER_OUT: a coverage run builds with instrumentation, usually on one board. The wiring is the same as forZDOCS_TWISTER_OUT: CMake passes it only when it is not empty, andzdocs_conf.pyreads it ascoverage_output_dir. If it is unset, the directive renders “no coverage run configured” and does not warn.ZDOCS_LATEXOPTSDefault
"-interaction=nonstopmode -halt-on-error". Passed toxelatexthrough thelatexmk-generatedlatexmkrc. Changing this is rarely useful; it exists mainly so the default is visible and overridable rather than buried in a generated file. Only read when a document declares thelatexbuilder.
What a document’s conf.py receives
These are not set by you. add_sphinx_target (The registry schema covers
what triggers it) computes them from the arguments above and the registry, and
passes them as plain environment variables to every sphinx-build it
launches. A conf.py shim reads them indirectly, by calling
zdocs_conf.configure(); nothing below is meant to
be read with a bare os.environ[...] in your own code, except
ZDOCS_CONF_DIR, which every shim needs before it can even import
zdocs_conf:
import os, sys
sys.path.insert(0, os.environ["ZDOCS_CONF_DIR"])
from zdocs_conf import configure
ZDOCS_CONF_DIRWhere the engine’s own
sphinx/directory lives — zdocs is a separate repository, checked out wherever the Zephyr module system put it, so no relative path from your tree can reach it.ZDOCS_DOC_IDThis document’s registry key.
ZDOCS_REGISTRYPath to
documents.yaml, or empty for a standalone document with no cross-references — a supported configuration, not a degraded one.ZDOCS_DOC_BUILD_DIR,ZDOCS_DOC_DEPLOY_DIRThe build tree and the
deploy/tree.ZDOCS_PROJECT_BASE,ZDOCS_WEST_TOPDIR,ZDOCS_DOC_BASE_URL,ZDOCS_NEEDS_CONFIGForwarded straight from the variables of the same name above.
LATEX_DOC,OUTPUT_DIRBuild-mechanics values (the
.texfilename thelatexbuilder must produce; the per-build output directory) thatdocrefs.pyalso reads directly, viadocrefs.build_root().