Build C++ user documentation
Generated C++ projects publish one documentation site with two rendering layers:
ProperDocs -> site root and prose/information architecture
Doxygen XML -> Breathe -> Sphinx -> versioned C/C++ API reference
ProperDocs is always canonical. The Sphinx/Breathe output is mounted below
reference/api/<version>/ and every API page contains links back to the main ProperDocs site and the
API-version landing page.
Enable the documentation feature
cmake -S . -B build -DPROJECT_FEATURES=user-docs
The documentation toolchain requires properdocs, doxygen, dot, sphinx-build, Breathe, and
sphinx-multiversion. The Doxygen executable must be built with libclang support
(-Duse_libclang=ON) because generated projects enable CLANG_ASSISTED_PARSING.
Author the main site
The generated project keeps the two source trees separate:
properdocs.yml
docs/ # ProperDocs Markdown
├── index.md
└── reference/
└── index.md
api-docs/ # API renderer only
├── conf.py
├── Doxyfile.in
├── index.rst
└── _templates/
└── project-links.html
Because properdocs.yml is checked in at the project root, normal ProperDocs authoring works without
CMake:
properdocs serve
The Reference page contains the Versioned API entry point. During serving/building, BESA publishes
the available API refs below reference/api/ and records them in generated versions.json.
Build the complete publication site
Commit the API documentation files before a multiversion build, then run:
cmake --build build --target user.docs
user.docs performs three steps:
- ProperDocs builds the main site.
- sphinx-multiversion builds the API reference for
mainplus the selected historical refs; each Sphinx build runs Doxygen atbuilder-inited, and Breathe consumes that checkout's Doxygen XML. - BESA assembles the API trees below the Versioned API section of the ProperDocs Reference page.
CMake configures api-docs/Doxyfile.in into <build>/api-docs/Doxyfile. The generated file points
CLANG_DATABASE_PATH at that exact CMake build tree, so Clang-assisted Doxygen parsing consumes the
same compile_commands.json as the project build. Historical refs receive their own private CMake
build and therefore their own compilation database.
Before Doxygen runs for each checkout, api-docs/conf.py constructs a temporary documented-header
tree. It configures that checkout when necessary, then merges checked-in src/*/include/ trees,
developer-facing test/base/*/include/ trees, and every configured
generated/<generator>/include/ tree. The meta generator therefore contributes version.hpp
automatically, and future generators are discovered by the same convention. Doxygen sees only this
staged tree. As a result, the Sphinx/Exhale Files section starts directly at the documented header
namespaces rather than repository-internal paths.
The installed public headers still appear under myproject/, while test support appears under its
testmyproject/ include namespace. Paths such as src/cpp/include/ and test/base/cpp/include/ are
not exposed in the generated file hierarchy.
The final deployable directory is:
build/doc/site/
├── index.html
├── reference/
│ └── api/
│ ├── index.html # ProperDocs landing page
│ ├── versions.json # generated by BESA
│ ├── main/
│ ├── v0.1.0/
│ └── v0.2.0/
└── .nojekyll
Upload the contents of build/doc/site/ as the GitHub Pages artifact. ProperDocs' own index.html
remains the site root; no redirect wrapper is required.
Cross-reference ProperDocs and the API
The two documentation surfaces share one assembled static tree, so BESA uses relative semantic cross-references rather than deployment hostnames.
From Doxygen comments, link back to the ProperDocs root or to a logical ProperDocs path:
/**
* See @projectdocs for the main project documentation.
* See @projectdocs{reference/testing,the testing reference} for test conventions.
*/
The one- and two-argument forms are Doxygen aliases generated by api-docs/conf.py; source comments
do not encode how many directories separate an API page from the site root.
From ProperDocs Markdown, link to an unambiguous C/C++ symbol with:
@apidocs::myproject::meta::build
Each Sphinx API build publishes stable _symbols/... redirect URLs from the C++ domain. The
extra.besa_api_version value in properdocs.yml selects which API version these links target, so
project prose can point to main during development or to a release/tag for published documentation.
During properdocs serve, an unresolved @apidocs::... reference is treated as an error.
Build only the API renderer
The current checkout can be rendered independently with:
cmake --build build --target user.docs.api
Build the selected historical refs without assembling the ProperDocs site with:
cmake --build build --target user.docs.multiversion
By default BESA builds every tag plus main. Set extra.besa_api_versions in properdocs.yml to
change the persistent selection, or use BESA_API_VERSIONS to override one invocation:
all
latest:4
range:>=0.2,<0.6
refs:v0.2.0,v0.4.0,v0.5.1
For example:
BESA_API_VERSIONS=latest:4 properdocs serve
BESA_API_VERSIONS='range:>=0.2,<0.6' cmake --build build --target user.docs.multiversion
latest:N and range:... operate on parseable version tags and sort/compare them as versions, not
by Git date or lexicographic string order. refs:... selects an exact set of tags or local branches.
main is included independently as the development API for every selector.
The raw multiversion API output is:
build/doc/api/multiversion/
├── versions.json
├── main/
├── v0.1.0/
└── v0.2.0/
This directory is an API component, not the final website.
Navigation back to ProperDocs
api-docs/_templates/versioning.html adds two persistent entries to the Sphinx sidebar:
- Main documentation returns to the ProperDocs site root.
- API versions returns to the ProperDocs
reference/api/landing page.
BESA computes the number of path levels from the configured API mount point and passes it to Sphinx. The links are therefore relative and do not depend on a GitHub Pages repository prefix, hostname, or custom domain.
CI and available refs
BESA selects from local tags and local branch heads. all, latest:N, and range:... are tag-oriented;
refs:... can explicitly add another local branch. In CI, fetch the tags and branches needed by the
configured selector before building the documentation.
Install built documentation
After building user.docs, a normal CMake install includes the complete assembled site:
cmake --install build --prefix <prefix>
The site is installed below share/doc/<project>/. The install source is optional, so installing a
library/binary build that never built documentation still succeeds.
Develop documentation from the Spack environment
Generated C++ projects include an independent Spack environment whose root package is
<project>-dev-env. Its variants select development dependencies; they are intentionally independent
of BESA/CMake feature selection for now.
The generated manifest starts with documentation and test tooling enabled:
spack env activate .
spack concretize
spack install
After installation and activation, properdocs, sphinx-build, sphinx-multiversion, Doxygen, and
the other documentation tools are available from the Spack environment view. No project-local Python
pyproject.toml or virtual environment is required.
Change the dependency selection with ordinary Spack variants, for example:
spack change <project>-dev-env~docs+coverage+cuda
spack concretize -f
spack install
Select the corresponding BESA/CMake features and devtools separately. BESA does not synchronize the Spack and CMake selections automatically.
With the +docs Spack variant installed, run from the project root:
properdocs serve
The generated ProperDocs hook is active only for serve. It watches src/, test/base/, and
api-docs/, builds
the documentation CMake tree in ../build/properdocs/cmake, and mounts the current API beneath
reference/api/main/. ProperDocs writes its live site to ../build/properdocs/site. Sphinx works
from a staged copy of api-docs/ below that external CMake tree, so Exhale does not create
api-docs/generated/ in the checkout. A normal properdocs build remains a plain ProperDocs build;
the CMake user.docs target continues to own the full multiversion publication assembly.