Doxygen Integration
CMake provides a first-class FindDoxygen module with the doxygen_add_docs() function that creates a documentation target from your source files. This is the simplest way to integrate Doxygen into a CMake project.
doxygen_add_docs() function (added in CMake 3.9) handles Doxyfile generation internally — you set Doxygen options as CMake variables prefixed with DOXYGEN_, and CMake generates the Doxyfile for you. No manual Doxyfile needed for simple projects.
cmake_minimum_required(VERSION 3.21)
project(MyLibrary VERSION 2.1.0 LANGUAGES CXX)
# Find Doxygen — OPTIONAL so the build works without it
find_package(Doxygen OPTIONAL_COMPONENTS dot)
if(DOXYGEN_FOUND)
# Set Doxygen configuration via CMake variables
set(DOXYGEN_PROJECT_NAME "${PROJECT_NAME}")
set(DOXYGEN_PROJECT_NUMBER "${PROJECT_VERSION}")
set(DOXYGEN_PROJECT_BRIEF "A modern C++ library for data processing")
set(DOXYGEN_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/docs")
set(DOXYGEN_EXTRACT_ALL YES)
set(DOXYGEN_EXTRACT_PRIVATE NO)
set(DOXYGEN_GENERATE_HTML YES)
set(DOXYGEN_GENERATE_LATEX NO)
set(DOXYGEN_GENERATE_XML YES) # Needed for Breathe/Sphinx
set(DOXYGEN_HTML_COLORSTYLE "TOGGLE")
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md")
# Create the "docs" target
doxygen_add_docs(docs
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/README.md
COMMENT "Generating API documentation with Doxygen..."
)
message(STATUS "Doxygen found: build target 'docs' available")
else()
message(STATUS "Doxygen not found: documentation target disabled")
endif()
# Generate documentation
cmake -B build
cmake --build build --target docs
# Open the generated HTML
open build/docs/html/index.html
Doxygen Configuration
For complex projects that need full Doxyfile control, use configure_file() with a Doxyfile.in template:
# Doxyfile.in — Template with CMake variable substitution
PROJECT_NAME = "@PROJECT_NAME@"
PROJECT_NUMBER = "@PROJECT_VERSION@"
PROJECT_BRIEF = "@PROJECT_DESCRIPTION@"
OUTPUT_DIRECTORY = "@CMAKE_BINARY_DIR@/docs"
INPUT = @CMAKE_SOURCE_DIR@/include \
@CMAKE_SOURCE_DIR@/src \
@CMAKE_SOURCE_DIR@/README.md
RECURSIVE = YES
EXTRACT_ALL = YES
EXTRACT_PRIVATE = NO
EXTRACT_STATIC = YES
GENERATE_HTML = YES
HTML_OUTPUT = html
HTML_COLORSTYLE = TOGGLE
HTML_TIMESTAMP = YES
HTML_DYNAMIC_SECTIONS = YES
GENERATE_LATEX = NO
GENERATE_XML = YES
XML_OUTPUT = xml
USE_MDFILE_AS_MAINPAGE = @CMAKE_SOURCE_DIR@/README.md
FILE_PATTERNS = *.h *.hpp *.cpp *.md
# Graphviz diagrams
HAVE_DOT = @DOXYGEN_HAVE_DOT@
DOT_IMAGE_FORMAT = svg
CLASS_DIAGRAMS = YES
CALL_GRAPH = YES
CALLER_GRAPH = YES
COLLABORATION_GRAPH = YES
# CMakeLists.txt — Using Doxyfile.in template
find_package(Doxygen REQUIRED OPTIONAL_COMPONENTS dot)
# Check if dot (Graphviz) is available
if(DOXYGEN_DOT_FOUND)
set(DOXYGEN_HAVE_DOT "YES")
else()
set(DOXYGEN_HAVE_DOT "NO")
endif()
# Generate Doxyfile from template
configure_file(
${CMAKE_SOURCE_DIR}/docs/Doxyfile.in
${CMAKE_BINARY_DIR}/Doxyfile
@ONLY
)
# Create custom target using the generated Doxyfile
add_custom_target(docs
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_BINARY_DIR}/Doxyfile
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Generating Doxygen documentation..."
VERBATIM
)
Graphviz Diagrams
When Graphviz's dot tool is available, Doxygen automatically generates class hierarchy diagrams, call graphs, and collaboration diagrams. CMake's FindDoxygen detects dot via the OPTIONAL_COMPONENTS dot argument.
flowchart TD
A[Source Code] --> B[Doxygen]
A --> C[Comment Blocks]
D[Doxyfile.in] --> E[configure_file]
E --> F[Doxyfile]
F --> B
B --> G[HTML Output]
B --> H[XML Output]
B --> I[LaTeX Output]
H --> J[Breathe]
K[RST Files] --> L[Sphinx]
J --> L
L --> M[Sphinx HTML]
G --> N[Deploy]
M --> N
N --> O[GitHub Pages]
N --> P[ReadTheDocs]
# Enabling specific diagram types
set(DOXYGEN_HAVE_DOT YES)
set(DOXYGEN_DOT_IMAGE_FORMAT svg)
set(DOXYGEN_INTERACTIVE_SVG YES)
# Class diagrams: show inheritance hierarchies
set(DOXYGEN_CLASS_DIAGRAMS YES)
set(DOXYGEN_CLASS_GRAPH YES)
# Call graphs: show which functions call which
set(DOXYGEN_CALL_GRAPH YES)
set(DOXYGEN_CALLER_GRAPH YES)
# Collaboration diagrams: show class relationships
set(DOXYGEN_COLLABORATION_GRAPH YES)
# Include dependency graphs
set(DOXYGEN_INCLUDE_GRAPH YES)
set(DOXYGEN_INCLUDED_BY_GRAPH YES)
# Limit graph depth to keep diagrams readable
set(DOXYGEN_MAX_DOT_GRAPH_DEPTH 3)
set(DOXYGEN_DOT_GRAPH_MAX_NODES 50)
Try It: Doxygen with Graphviz
Create a small C++ project with 3-4 classes forming an inheritance hierarchy. Install Graphviz (sudo apt install graphviz or brew install graphviz), enable class diagrams and call graphs, then build the docs target. Inspect the SVG diagrams in the generated HTML output.
Sphinx Integration
Sphinx excels at narrative documentation — tutorials, guides, and architecture descriptions — using reStructuredText or Markdown. While CMake doesn't have a built-in FindSphinx module, creating one is straightforward:
# cmake/FindSphinx.cmake
find_program(SPHINX_EXECUTABLE
NAMES sphinx-build
DOC "Path to sphinx-build executable"
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Sphinx
REQUIRED_VARS SPHINX_EXECUTABLE
VERSION_VAR SPHINX_VERSION
)
# CMakeLists.txt — Sphinx documentation target
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
find_package(Sphinx)
if(SPHINX_EXECUTABLE)
set(SPHINX_SOURCE "${CMAKE_SOURCE_DIR}/docs")
set(SPHINX_BUILD "${CMAKE_BINARY_DIR}/docs/sphinx")
add_custom_target(sphinx-docs
COMMAND ${SPHINX_EXECUTABLE}
-b html
-d "${SPHINX_BUILD}/doctrees"
"${SPHINX_SOURCE}"
"${SPHINX_BUILD}/html"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Building Sphinx documentation..."
)
endif()
Breathe Bridge: Doxygen + Sphinx
Breathe is a Sphinx extension that reads Doxygen's XML output and exposes it as Sphinx directives. This lets you combine Doxygen's automatic API extraction with Sphinx's superior narrative documentation capabilities.
flowchart LR
subgraph Sources
A[C++ Headers]
B[Comment Blocks]
end
subgraph Doxygen
C[Parse & Extract]
D[XML Output]
end
subgraph Sphinx
E[RST Narrative Docs]
F[Breathe Directives]
G[Sphinx Builder]
end
A --> C
B --> C
C --> D
D --> F
E --> G
F --> G
G --> H[Combined HTML]
# docs/conf.py — Sphinx configuration with Breathe
project = '@PROJECT_NAME@'
version = '@PROJECT_VERSION@'
author = 'Your Name'
extensions = [
'breathe',
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.viewcode',
]
# Breathe configuration
breathe_projects = {
'@PROJECT_NAME@': '@CMAKE_BINARY_DIR@/docs/xml'
}
breathe_default_project = '@PROJECT_NAME@'
# Theme
html_theme = 'furo'
# Full Doxygen + Breathe + Sphinx pipeline
find_package(Doxygen REQUIRED)
find_package(Sphinx REQUIRED)
# Step 1: Generate Doxygen XML
set(DOXYGEN_GENERATE_HTML NO) # Sphinx produces the HTML
set(DOXYGEN_GENERATE_XML YES) # Breathe reads this
set(DOXYGEN_XML_OUTPUT "${CMAKE_BINARY_DIR}/docs/xml")
doxygen_add_docs(doxygen-xml
${CMAKE_SOURCE_DIR}/include
COMMENT "Generating Doxygen XML for Breathe..."
)
# Step 2: Configure Sphinx conf.py
configure_file(
${CMAKE_SOURCE_DIR}/docs/conf.py.in
${CMAKE_BINARY_DIR}/docs/conf.py
@ONLY
)
# Step 3: Build Sphinx (depends on Doxygen XML)
add_custom_target(docs
COMMAND ${SPHINX_EXECUTABLE}
-b html
-c "${CMAKE_BINARY_DIR}/docs"
"${CMAKE_SOURCE_DIR}/docs"
"${CMAKE_BINARY_DIR}/docs/html"
DEPENDS doxygen-xml
COMMENT "Building Sphinx + Breathe documentation..."
)
Using Breathe Directives in RST
# docs/api.rst — Example API documentation page
API Reference
=============
MyClass
-------
.. doxygenclass:: MyNamespace::MyClass
:members:
:protected-members:
Helper Functions
----------------
.. doxygenfunction:: MyNamespace::helper_function
.. doxygenfile:: utils.h
:sections: func
Documentation as a Custom Target
The "docs" Target Pattern
# Option to include docs in the default ALL target
option(BUILD_DOCS "Build documentation as part of ALL" OFF)
if(BUILD_DOCS)
# Adding ALL makes docs build with every 'cmake --build'
doxygen_add_docs(docs ALL
${CMAKE_SOURCE_DIR}/include
COMMENT "Building documentation..."
)
else()
# Without ALL, docs must be built explicitly
doxygen_add_docs(docs
${CMAKE_SOURCE_DIR}/include
COMMENT "Building documentation..."
)
endif()
configure_file for Documentation Templates
Use configure_file() to inject version numbers, project names, and build information into documentation templates:
# Substitute project info into docs
configure_file(
${CMAKE_SOURCE_DIR}/docs/version.rst.in
${CMAKE_BINARY_DIR}/docs/version.rst
@ONLY
)
configure_file(
${CMAKE_SOURCE_DIR}/docs/conf.py.in
${CMAKE_BINARY_DIR}/docs/conf.py
@ONLY
)
# docs/version.rst.in
Version Information
===================
:Project: @PROJECT_NAME@
:Version: @PROJECT_VERSION@
:Date: @BUILD_DATE@
:Git Hash: @GIT_HASH@
# Get build date and git hash for docs
string(TIMESTAMP BUILD_DATE "%Y-%m-%d")
execute_process(
COMMAND git rev-parse --short HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(NOT GIT_HASH)
set(GIT_HASH "unknown")
endif()
Markdown README Integration
Doxygen can use your project's README.md as the main page, keeping a single source of truth:
# Use README.md as the Doxygen main page
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md")
# Enable Markdown support in Doxygen
set(DOXYGEN_MARKDOWN_SUPPORT YES)
set(DOXYGEN_AUTOLINK_SUPPORT YES)
doxygen_add_docs(docs
${CMAKE_SOURCE_DIR}/README.md
${CMAKE_SOURCE_DIR}/CHANGELOG.md
${CMAKE_SOURCE_DIR}/include
COMMENT "Generating documentation..."
)
Try It: Breathe Pipeline
Set up a project with C++ source documented with Doxygen comment blocks and a Sphinx docs/ directory with narrative RST files. Configure the full Doxygen → XML → Breathe → Sphinx pipeline and verify that both the auto-extracted API docs and your handwritten guides appear in the same HTML output.
Documentation Installation
install(DIRECTORY) for Built Docs
# Install generated HTML documentation
install(DIRECTORY ${CMAKE_BINARY_DIR}/docs/html/
DESTINATION ${CMAKE_INSTALL_DOCDIR}
COMPONENT documentation
OPTIONAL # Don't fail if docs weren't built
)
# Also install raw markdown docs
install(FILES
${CMAKE_SOURCE_DIR}/README.md
${CMAKE_SOURCE_DIR}/CHANGELOG.md
${CMAKE_SOURCE_DIR}/LICENSE
DESTINATION ${CMAKE_INSTALL_DOCDIR}
COMPONENT documentation
)
CPack Inclusion
# Include documentation in packages
set(CPACK_COMPONENTS_ALL runtime development documentation)
set(CPACK_COMPONENT_DOCUMENTATION_DISPLAY_NAME "Documentation")
set(CPACK_COMPONENT_DOCUMENTATION_DESCRIPTION
"API reference and user guides")
set(CPACK_COMPONENT_DOCUMENTATION_GROUP "docs")
# Ensure docs are built before packaging
add_dependencies(package docs) # Not always reliable — see below
# Better: add a custom target that builds docs then packages
add_custom_target(package-with-docs
COMMAND ${CMAKE_COMMAND} --build . --target docs
COMMAND ${CMAKE_COMMAND} --build . --target package
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Building docs and creating package..."
)
CI Documentation Deployment
Deploying to GitHub Pages
# .github/workflows/docs.yml
name: Documentation
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
jobs:
build-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y doxygen graphviz
pip install sphinx breathe furo
- name: Configure and build docs
run: |
cmake -B build -DBUILD_DOCS=OFF
cmake --build build --target docs
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: build/docs/html
deploy:
needs: build-docs
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
ReadTheDocs Integration
# .readthedocs.yaml
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
apt_packages:
- doxygen
- graphviz
- cmake
sphinx:
configuration: docs/conf.py
builder: html
python:
install:
- requirements: docs/requirements.txt
# docs/requirements.txt
sphinx>=7.0
breathe>=4.35
furo
sphinx-copybutton
Try It: GitHub Pages Deployment
Fork a C++ project, add Doxygen documentation via doxygen_add_docs(), create the GitHub Actions workflow above, and push to the main branch. Verify that your API documentation is live at https://<user>.github.io/<repo>/ within minutes of the push.
Conclusion & Next Steps
Documentation is a first-class deliverable, not an afterthought. By integrating it into your CMake build, docs stay synchronized with code, build automatically in CI, and ship with your releases. Key takeaways:
- doxygen_add_docs() — The simplest path: set
DOXYGEN_*variables and get a docs target - Doxyfile.in + configure_file() — Full control over Doxygen configuration with CMake variable substitution
- Breathe + Sphinx — Combine auto-extracted API docs with handwritten narrative documentation
- Graphviz — Automatic class diagrams, call graphs, and collaboration graphs
- CI deployment — GitHub Pages and ReadTheDocs for always-up-to-date public documentation
- install() + CPack — Include documentation in release packages
DOXYGEN_* variables supported by doxygen_add_docs(), and the configure_file() command reference.