Basic Usage

The diagram below illustrates the workflow of the MrDocs documentation tool. It shows how inputs are transformed step by step into the final documentation output. Each component in the diagram represents a distinct type of entity:

  • Inputs: Provide the initial parameters and settings required to configure and run the tool. These include the configuration file and command-line arguments.

  • Processes: Represent intermediary outputs or data generated during the workflow.

  • Outputs: The final product, in this case, the generated documentation.

graph TD %% Define styles for visual clarity classDef input fill:#D1E8FF,stroke:#005CFF,stroke-width:2; classDef artifact fill:#FFF5D1,stroke:#FFA500,stroke-width:2; classDef output fill:#D1FFD1,stroke:#008000,stroke-width:2; %% Define Inputs subgraph Inputs CF[Configuration File] CL[Command Line Arguments] end class CL,CF input %% Define Artifacts subgraph Processes P[Configuration Options] CD[Compilation Database] C[Corpus] G[Generator] end class P artifact class CD,C,G artifact %% Define Outputs subgraph Outputs D[Documentation] end class D output %% Relationships CF -->|Define| P CL -->|Define| P P -->|Defines| CD CD -->|Extract Symbols| C C -->|Feeds| G G -->|Produces| D %% Highlight dependencies for clarity P -->|Influences| G

In summary:

For more details on each component, refer to the corresponding sections of this guide.

MrDocs configuration file

The mrdocs.yml configuration file contains information about the project. If you’re used to Doxygen, this file is similar to the Doxyfile configuration file.

Here is an example of a mrdocs.yml file:

source-root: ../include
multipage: false
generate: adoc

In many projects, it is common to have the documentation in a docs directory, which can also contain this configuration file.

+ <project-directory>
  + docs
    + mrdocs.yml
    + ...
  + include
  + src
  + test
  + ...

The most important information is the source-root option, which determines the root of the source tree relative to the mrdocs.yml file.

The list of all available options can be found in the The Configuration File page.

MrDocs invocation

For consistency, these instructions assume you have the mrdocs executable in PATH.

Basic invocation

You can invoke MrDocs from the command line with the following command:

mrdocs path/to/mrdocs.yml

If you are at the path of the mrdocs.yml file, you can simply run:

mrdocs

You can also specify other commands to MrDocs directly from the command line to set or override options from the mrdocs.yml file (See all options in The Configuration File page).

mrdocs path/to/mrdocs.yml --output=../docs/reference
Except for the path to the mrdocs.yml file, all other relative paths are made absolute relative to the mrdocs.yml file.

Compilation databases

One way to simplify the documentation generation process is by using a compile_commands.json file generated by CMake to determine the source files to process and their compile options. This file is generated by the CMake configuration step when the CMAKE_EXPORT_COMPILE_COMMANDS option is set to ON.

cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

At this step, you can also add any other flags you want to pass to cmake, such as -DCMAKE_BUILD_TYPE=Release or -DCMAKE_CXX_COMPILER=clang++.

If you are using the Visual Studio generator, you might need to switch to the Ninja generator to generate the compile_commands.json file.

This will generate a compile_commands.json file in the build directory containing all commands needed to compile your project. This file can be used by MrDocs to determine the source files to process and their compile options.

mrdocs --compilation-database=../build/compile_commands.json --output=../docs/reference

MrDocs will go through all the source files listed in the compile_commands.json file and generate the documentation for them.

When MrDocs goes through the commands in the compilation database, it will go through the same targets as the compiler. Only symbols present in these targets will be documented. In the case of header-only libraries, the symbols will be documented only if they are included in one of these targets.

CMake integration

It’s common to have different configurations for the documentation generation than for the project build. This means CMake is often being run just to generate a custom compile_commands.json for the documentation. Also, the compile_commands.json file is a configuration artifact, which means it often lacks a stable location that can be referenced in the mrdocs.yml configuration file.

Thus, the pattern of using a compile_commands.json file generated by CMake is so common that MrDocs provides a CMake module to simplify the process. You can let MrDocs generate the compile_commands.json file for you by providing the path to the CMakeLists.txt file of your project.

mrdocs --compilation-database=../CMakeLists.txt --output=../docs/reference

By providing your CMakeLists.txt file as the reference for you compilation database, MrDocs will automatically run CMake to generate the compile_commands.json file in a temporary directory. Not only this simplifies the usage but also ensures that the stable compilation database file can be used in the mrdocs.yml configuration file.

MrDocs does not bundle CMake. If you want to use this feature, you need to have CMake installed on your system and available in PATH.

Parameters for cmake, such as -D BUILDING_TEST=OFF -D MRDOCS_BUILD=ON can also be specified with the cmake option in configuration file. MrDocs will always append the CMAKE_EXPORT_COMPILE_COMMANDS=ON flag to the cmake command.

When the cmake option is provided, MrDocs will still adjust the CMake configure arguments to properly generate the compilation database file.

  • MrDocs will set the -S and -B options to the proper directories determined by the mrdocs.yml file. If the user explicitly sets -S or -B in the cmake option, these arguments are ignored.

  • If the user explicitly sets the generator with -G, the user option will typically be honored. However, if the user explicitly sets the generator to Visual Studio or the option is not set and Visual Studio is still the default generator, MrDocs will override it to -G Ninja to ensure a compile_commands.json file is generated.

  • MrDocs will typically ensure the CMAKE_EXPORT_COMPILE_COMMANDS flag is set to ON. If the user explicitly sets -D CMAKE_EXPORT_COMPILE_COMMANDS=OFF, the user flag will be honored. This can be used to manually determine the logic to generate the compile_commands.json file in the CMakeLists.txt file.

MrDocs Builds

In many projects, a common pattern is to define a special build configuration for the documentation generation such that:

  1. Tests, examples, and auxiliary targets excluded

  2. All header-only files are included in targets

  3. Unnecessary sources files are excluded from targets

This can usually be achieved by defining a custom target in the CMakeLists.txt with a single source file that includes all the necessary header files.

if (MY_PROJECT_MRDOCS_BUILD)
    # Glob all header files
    set(INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include")
    file(GLOB_RECURSE HEADER_FILES "${INCLUDE_DIR}/*.hpp")

    # Create a temporary source file that includes all header files
    set(TEMP_CPP_FILE "${CMAKE_CURRENT_BINARY_DIR}/all_headers.cpp")
    file(WRITE ${TEMP_CPP_FILE} "// This file is generated automatically by CMake\n\n")
    foreach(HEADER_FILE ${HEADER_FILES_LIST})
        file(APPEND ${TEMP_CPP_FILE} "#include \"${HEADER_FILE}\"\n")
    endforeach()

    # Create a custom target for MrDocs
    add_library(my_project_mrdocs_target ${TEMP_CPP_FILE})

    # Set any other target properties here
    target_include_directories(my_project_mrdocs_target PRIVATE ${INCLUDE_DIR})
    target_link_libraries(my_project_mrdocs_target PRIVATE an_external_library)

    # Don't create any other targets
    return()
endif()

To use this target, you can run CMake with the MY_PROJECT_MRDOCS_BUILD flag set to ON:

mrdocs --cmake="-D MY_PROJECT_MRDOCS_BUILD=ON" --compilation-database=../CMakeLists.txt --output=..\docs\reference

Because these paths and options are stable, you can specify them in the mrdocs.yml configuration file.

cmake: "-D MY_PROJECT_MRDOCS_BUILD=ON"
compilation-database: ../CMakeLists.txt
output: ../docs/reference

Extracting Documentation

Unlike other documentation generators, MrDocs only works with valid C++ code. Instead of partially preprocessing the source files and inferring symbols from potentially ill-formed code, MrDocs relies on the compilation database and Clang to preprocess the source files.

Thus, for each common C++ construct, MrDocs provides commands or options to identify and extract the relevant information as intended by the user. For instance, a common ill-formed Doxygen pattern to specify a class as an implementation detail is:

#ifdef DOCS
__implementation_defined__
#else
impl::f_return_t
#endif
f();

In this pattern, the user wants to document the function f as implementation_defined f(); because the library contract is that the user should not rely on a specific return type here. However, this ill-formed pattern is problematic:

  • __implementation_defined__ is not a valid symbol so the code is ill-formed

  • impl::f_return_t doesn’t express the intent of the user for the documentation

  • the developer has to effectively maintain two versions of the code

  • the original source code becomes more and more unreadable

Instead, when using MrDocs, while the \&lowbar;_MRDOCS__ macro is still available for conditional compilation, the same function could be directly documented as:

impl::f_return_t f();

And the user can specify that symbols in the impl namespace are implementation details in the configuration file:

# ...
implementation-detail: impl::**
# ...

The Commands and The Configuration File pages contain a list of all available commands and options to identify and extract the relevant information as intended by the user.

MrDocs provides multiple mechanisms are provided to specify special C++ patterns, such as the example above. For each common C++ construct that would require macros and two versions of the code, MrDocs provides commands to identify the construct and extract the relevant information as intented by the user.

Dependencies

Another consequence of relying on valid C++ code is that MrDocs needs to know about dependencies of the project for the code to be valid. In particular, it needs access to the header files of the project and its dependencies.

The includes option in the configuration file specifies the directories to search for header files. Suppose a library depends on an external library, such as Boost:

# A library that depends on an external library
includes:
  - /path/to/boost/include

Whatever is specified in the includes option is passed to Clang as include directories, regardless of the strategy used to generate the compilation database.

If a compile_commands.json file is used, these include directories are passed directly to Clang as -I flags.

If a CMakeLists.txt file is used, the cmake option in the configuration file can be used to provide the necessary parameters for CMake to find the appropriate header files.

cmake: '-D BOOST_ROOT=/path/to/boost'

Another option supported by CMake is to set the BOOST_ROOT environment variable as /path/to/boost before running MrDocs.

System dependencies

It’s also common for libraries to depend on the C++ standard library, the C standard library, or other system libraries. These dependencies are usually resolved by the compiler and are not explicitly specified in the source code:

  • The C++ standard library: The compiler will look for specific paths according to the -stdlib option and include them as implicit -isystem paths. For instance, Clang can use different implementations of the C++ standard library. By default, that’s Microsoft STL on Windows, libstdc++ on Linux and libc++ otherwise. This can be disabled with -nostdinc++ -nostdlib++ or, in MrDocs, with use-system-stdlib=false.

  • The C standard library (and system libraries): Unlike with libc++, LLVM+Clang does not provide an implementation of the C standard library. It always depends on the system for that. The compiler will not look for specific paths but implicitly include all system libraries. This can be disabled with -nostdinc or, in MrDocs, with use-system-libc=false.

That means unless -nostdinc is defined, all systems include paths are included. This is what allows the user to also use headers like <Windows.h> or <linux/version.h> without explicitly including anything else, even though they are not part of the C standard library. This is often seen as a convenience but can lead to portability issues.

In this context, MrDocs provides the use-system-stdlib and use-system-libc options. Both are set as false by default, meaning MrDocs will compile the code as if the -nostdinc++ -nostdlib++ and -nostdinc flags were passed to Clang. Additionally:

  • When use-system-stdlib is false, MrDocs will use the bundled libc++ headers available in <mrdocs-root>/share/mrdocs/headers/libcxx and <mrdocs-root>/share/mrdocs/headers/clang. These paths can be adjusted with the stdlib-includes option.

  • When use-system-libc is false, MrDocs will use the bundled libc stubs available in <mrdocs-root>/share/mrdocs/headers/libc-stubs. This path can be adjusted with the libc-includes option.

The rationale for that is reproducibility. You want to be able to build your documentation and don’t want it to stop working because the platform or some platform details have changed. These default values also help avoid conflicts where the same symbol or header is defined twice if the compilation database includes system paths relevant to one specific compiler. That can breaks things when MrDocs attempts to compile it with clang. In other words, MrDocs becomes a sandboxed environment where only the C and C++ standard libraries are available.

The default values described above work for most libraries and applications that only depend on the C and C++ standard libraries. When there are no dependencies outside the standard libraries, the user probably won’t even notice the difference between these options.

However, if you depend on other system libraries, that means you need to handle these dependencies explicitly. For instance, this is very common with networking libraries. There are a few solutions to this, and these solutions are in a continuum regarding the use of use-system-stdlib/use-system-libc and the design of the code:

  1. Depending on the design of your library, you can implement a different path for MrDocs where you don’t need these system headers. System headers are often guarded by macros to detect the platform: for instance, __linux__ for <linux/version.h> and _WIN32 for <Windows.h>. You can use the __MRDOCS__ macro to provide an implementation for MrDocs. This implementation would typically include stubs for the symbols you need in the documentation. Because symbols from system libraries are typically not exposed in the public API of your library, that gives you replacements that make sense for the documentation. However, this solution is not enough when other dependencies also depend on these system libraries.

  2. Handle it as an explicit dependency. Explicitly include the paths you need in your compilation database as if it’s a dependency described in the Dependencies section. For instance, you can get CMake to explicitly find <linux/version.h> or <Windows.h> and only include those directories with -isystem or -I in the compilation database.

  3. Enable use-system-libc. MrDocs will still use the bundled libc++ for the C++ standard library, and use the system headers for the C standard library, making all system paths available. That makes system headers such as <linux/version.h> or <Windows.h> available by default. The trade-off is losing the reproducibility guarantees described above.

The first option in this list provides the most control and the most reproducibility. The third option is the most convenient but also the most fragile.