Skip to content

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:

  1. ProperDocs builds the main site.
  2. sphinx-multiversion builds the API reference for main plus the selected historical refs; each Sphinx build runs Doxygen at builder-inited, and Breathe consumes that checkout's Doxygen XML.
  3. 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.

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.