Basic Usage Patterns
MrDocs reads a C++ project and produces documentation. You drive it from a YAML configuration file or from the command line; both forms are covered on the Configuration page. The project shapes below cover most cases. Each one has a complete starter project under examples/getting-started/; copy any of them as the seed for your own.
MrDocs drives Clang with a compilation database: the per-file list of compile flags. The database is what lets MrDocs see the code the way the compiler does, with the right include paths, preprocessor definitions, and language standard. Every pattern on this page ends up with one. The difference is where it comes from. Three sources cover almost every project:
| Source of the database | When to use it | What MrDocs does |
|---|---|---|
You have a |
Reads the file from disk. |
|
You use CMake and want MrDocs to invoke it. |
Configures the project itself, then reads the |
|
No |
Walks the |
Compilation database
A compile_commands.json file already exists and MrDocs reads it directly. This mode uses the compile flags recorded in the file verbatim. The build system usually produces this file:
-
CMake’s
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON -
Bazel’s
bazel-compile-commands-extractor -
Meson by default
You can also write the file by hand. The starter project at examples/getting-started/compilation-database/ ships a hand-written one and has the following layout:
compilation-database/
├── include/geo/point.hpp public header
├── src/point.cpp source file
└── docs/
├── compile_commands.json hand-written compilation database
└── mrdocs.yml MrDocs configuration
The header and source file are the project’s actual code; the two files under docs/ are the MrDocs side of the example. The database:
docs/compile_commands.json[
{
"directory": "${MRDOCS_SOURCE_ROOT}",
"command": "clang++ -std=c++20 -I${MRDOCS_SOURCE_ROOT}/include -c ${MRDOCS_SOURCE_ROOT}/src/point.cpp -o ${MRDOCS_SOURCE_ROOT}/point.o",
"file": "${MRDOCS_SOURCE_ROOT}/src/point.cpp"
}
]
|
The |
And the MrDocs configuration:
docs/mrdocs.yml## Pattern: handwritten compilation database, no build system.
source-root: ..
compilation-database: compile_commands.json
Run:
mrdocs --config=docs/mrdocs.yml
CMake
Hand MrDocs the CMakeLists.txt. MrDocs finds CMake, configures the project in a temporary build directory, and reads the compile_commands.json CMake produces. The two examples below cover compiled libraries and header-only libraries.
|
MrDocs checks the |
Regular CMake project
For projects with both headers and source files. The starter project at examples/getting-started/cmake/ has the following layout:
cmake/
├── CMakeLists.txt project's build system
├── include/geo/point.hpp public header
├── src/point.cpp source file
└── docs/mrdocs.yml MrDocs configuration
The MrDocs configuration:
docs/mrdocs.yml## Pattern: MrDocs drives CMake itself. No need to pre-build the project.
source-root: ..
compilation-database: ../CMakeLists.txt
## For extra CMake variables:
# cmake: '-D BOOST_ROOT=/path/to/boost'
compilation-database points at the CMakeLists.txt.
The CMakeLists.txt checks MRDOCS_BUILD, the variable MrDocs sets when it invokes CMake. You can use it to skip targets that aren’t part of the public surface (test runners, tools) and to guard doc-only targets:
CMakeLists.txtcmake_minimum_required(VERSION 3.20)
project(geo LANGUAGES CXX)
add_library(geo src/point.cpp)
target_include_directories(geo PUBLIC include)
## MRDOCS_BUILD is set by MrDocs when it invokes CMake.
## You can use it to skip targets we don't need for the documentation.
if(MRDOCS_BUILD)
return()
endif()
if (BUILD_TESTING)
add_subdirectory(tests)
endif()
MrDocs configures the project, reads the resulting compile_commands.json, and parses both the headers and the .cpp files.
Header-only CMake library
A header-only library has no translation units to compile. An INTERFACE target contributes nothing to compile_commands.json and MrDocs parses an empty corpus. The fix is the MrDocs Builds pattern: gate a doc-only target on MRDOCS_BUILD (which MrDocs sets automatically when it invokes CMake), and have that target compile one aggregate translation unit that #includes every public header.
The starter project at examples/getting-started/cmake-header-only/ has the following layout:
cmake-header-only/
├── CMakeLists.txt project's build system
├── include/geo/point.hpp public header (no .cpp; the library is header-only)
└── docs/mrdocs.yml MrDocs configuration
CMakeLists.txtcmake_minimum_required(VERSION 3.20)
project(geo LANGUAGES CXX)
## The library is INTERFACE-only
## It contributes nothing to `compile_commands.json`
add_library(geo INTERFACE)
target_include_directories(geo INTERFACE include)
## MrDocs documentation build.
## Have a gated target compile a single aggregate TU
## that `#include`s every public header.
if (MRDOCS_BUILD)
# Glob all header files
set(MAIN_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include")
file(GLOB_RECURSE MRDOCS_PUBLIC_HEADERS "${MAIN_INCLUDE_DIR}/*.hpp")
set(MRDOCS_AGGREGATE_TU "${CMAKE_CURRENT_BINARY_DIR}/geo_mrdocs.cpp")
# Create a source file that includes all header files
file(WRITE ${MRDOCS_AGGREGATE_TU} "// Generated by CMake for MrDocs\n\n")
foreach(PUBLIC_HEADER ${MRDOCS_PUBLIC_HEADERS})
file(APPEND ${MRDOCS_AGGREGATE_TU} "#include \"${PUBLIC_HEADER}\"\n")
endforeach()
# Create a custom target for MrDocs
add_library(geo_mrdocs ${MRDOCS_AGGREGATE_TU})
target_include_directories(geo_mrdocs PRIVATE ${MAIN_INCLUDE_DIR})
target_link_libraries(geo_mrdocs PRIVATE geo)
# Don't create any other targets
return()
endif()
if (BUILD_TESTING)
add_subdirectory(tests)
endif()
The branch guarded by MRDOCS_BUILD runs only when MrDocs configures the project. For normal CMake users (consumers, installation rules, packaging) the gate is off and the file declares the usual INTERFACE target.
docs/mrdocs.yml## Pattern: header-only library that lives inside a CMake project.
source-root: ..
compilation-database: ../CMakeLists.txt
compilation-database points at the CMakeLists.txt. MrDocs configures it with MRDOCS_BUILD=ON set automatically, the aggregate TU shows up in compile_commands.json, and every public header gets parsed.
|
This pattern is useful beyond header-only libraries. Use it whenever compiling the real source files would be expensive, or would pull in dependencies you’d rather not satisfy from the docs build: heavy template instantiation, third-party libraries you only link against at runtime, code that needs a configured build environment. Public declarations live in the headers; the bodies of the |
Header scan
The aggregate-headers pattern above is repetitive. Many projects end up writing the same if (MRDOCS_BUILD) … endif () block to glob headers into a synthetic TU. MrDocs handles that case directly. Point it at the include directory; it walks the headers, synthesizes a compilation database from a default set of compile flags, and treats each header as its own translation unit. Same result as the aggregate-headers pattern.
The starter project at examples/getting-started/scanned/ has the following layout:
scanned/
├── include/geo/point.hpp public header
└── docs/mrdocs.yml MrDocs configuration
There is no CMakeLists.txt, no compile_commands.json. Just the headers and the MrDocs configuration:
docs/mrdocs.yml## Pattern: MrDocs scans the `input` directories, synthesizes
## a compilation database from the headers it finds.
source-root: ..
## Which directories MrDocs walks looking for files to document.
input:
- ../include
## Where MrDocs looks for `#include` directives.
includes:
- ../include
The trade-off is that the synthesized compilation can’t learn project-specific include paths or preprocessor definitions. For a self-contained library, or one whose dependencies are header-only and already on the standard search path, that’s fine. For a project that needs find_package to locate its dependencies' headers, stay with the CMake patterns above so the dependencies reach the parser’s include path.
|
The same pattern can be used to document a single header or source file. Set This site’s landing page uses that pattern to render the panel snippets one file at a time; see |
Where to look next
-
For things that surprise people coming from Doxygen and similar tools (the code must compile, and the diagram showing how the corpus is built), see Migration Notes.
-
For how to write doc comments and which tags MrDocs understands, see Documenting the Code.
-
For a tour of the available options, see the Configuration page.