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

Compilation database

You have a compile_commands.json already (from your build system, or hand-written).

Reads the file from disk.

CMake

You use CMake and want MrDocs to invoke it.

Configures the project itself, then reads the compile_commands.json CMake produces.

Header scan

No compile_commands.json and no build system that produces one.

Walks the input directories and synthesizes a database from the headers.

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:

Project files
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 ${MRDOCS_SOURCE_ROOT} token is a MrDocs extension. When MrDocs loads a compile_commands.json, it replaces the token with the absolute path of the project’s source-root. The standard format requires absolute paths in the directory and file fields, which makes a hand-written database impossible to commit (every collaborator’s working copy lives at a different absolute path). The placeholder lets the file move between machines unchanged.

And the MrDocs configuration:

docs/mrdocs.yml
## Pattern: handwritten compilation database, no build system.
source-root: ..
compilation-database: compile_commands.json

Run:

Running MrDocs
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 CMAKE_ROOT environment variable first. When it’s set, MrDocs looks under ${CMAKE_ROOT}/bin/cmake and ${CMAKE_ROOT}/cmake. Otherwise MrDocs falls back to the PATH. Set CMAKE_ROOT when you need to pin a specific CMake install (typical on systems with several side-by-side versions). If neither lookup succeeds, MrDocs aborts with CMake executable not found.

Regular CMake project

For projects with both headers and source files. The starter project at examples/getting-started/cmake/ has the following layout:

Project files
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.txt
cmake_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:

Project files
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.txt
cmake_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 .cpp files don’t contribute to the documentation. Gate the aggregate-headers target on MRDOCS_BUILD and MrDocs compiles a body-less translation unit that parses every declaration you want documented. The library’s normal CMake users still get the real targets when MRDOCS_BUILD is off.

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:

Project files
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 input to the file’s parent directory, file-patterns to the exact filename, and recursive to false so MrDocs doesn’t pick up identically named files in nested subdirectories.

This site’s landing page uses that pattern to render the panel snippets one file at a time; see docs/website/render.js.

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.