Sphinx and CMake: Beautiful Documentation For C++ Projects

- CMake Sphinx

Let’s face it, documentation for (most) developers is boring and more often than not this is reflected in the quality of a project’s documentation. This is often a barrier that prevents adoption of otherwise well crafted projects in wide-spread production use. In this article we’ll take a look at how to integrate a documentation generator called Sphinx into an existing CMake based project for documentation that is regenerated each time you build the source.

Sphinx is a handy Python based utility that can take plain text documents and generate beautiful documentation in rich formats such as HTML or PDF with just a minimal amount of markup to indicate things like headers or code blocks. The Restructured Text format, the simple markup format used in the plain text documents, is very simple to use and allows anyone, including non-developers to help contribute to documentation efforts. For an example of Sphinx generated documentation check out the SWGANH project documentation.

The CMake tool makes creating and building C/C++ applications across multiple platforms a snap. In this guide I assume that you already have an existing CMake based project where you want to integrate documentation.

Generating a Sphinx configuration

To get started lets create a directory to store the documentation at and an intial document for testing things out. From the root of your project create a docs folder with a test.rst file inside. Add the following to the test.rst file.

=============
Test Document
=============

Next open a command prompt and navigate to the docs directory that was just created. You can also shift+right click on the directory and choose “Open command window here”. The run the following to create the initial configuration and index file.

sphinx-quickstart

Choose all the default options and enter your project name and author when prompted. Once this has been completed you can remote the make.bat and Makefile that were generated, we will handle this process through CMake (optionally you can choose No for the last two questions to prevent these from being generated). You can also remove the “_” prefixed directories as these are not needed currently either.

Finally open the index.rst file that was generated and edit to add the test document previously created to the documentation index.

.. toctree::
    :maxdepth: 2

    test
    ...

Now lets get the CMake project ready to build the documentation.

Finding the Sphinx executable

In order to do anything with Sphinx your project build needs to know where to find it. In CMake terms, this means a call to find_package.

In order to properly find the package you need to create a script as there is no official support for this yet. Below is an example of what this script should look like.

find_program(SPHINX_EXECUTABLE NAMES sphinx-build
    HINTS
    $ENV{SPHINX_DIR}
    PATH_SUFFIXES bin
    DOC "Sphinx documentation generator"
)
 
include(FindPackageHandleStandardArgs)
 
find_package_handle_standard_args(Sphinx DEFAULT_MSG
    SPHINX_EXECUTABLE
)
 
mark_as_advanced(SPHINX_EXECUTABLE)

Save this to a file called FindSphinx.cmake in the same location as any other custom CMake scripts you may be using. If you are not using any custom CMake scripts then I suggest creating a cmake directory in the project root and adding the file there. You will then need to make CMake aware of this new directory when loading scripts by adding the following to your top level script (just after the project command).

set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")

You can now use the find_package command to find the Sphinx executable.

find_package(Sphinx REQUIRED)

Note that the REQUIRED flag is optional. For now though, do not add this command, we’ll take care of that in the next section.

Creating a Sphinx Target

Now its time to create a CMakeLists.txt in the docs directory that will direct CMake on how to use Sphinx to generate the documentation.

find_package(Sphinx REQUIRED)
 
if(NOT DEFINED SPHINX_THEME)
    set(SPHINX_THEME default)
endif()
 
if(NOT DEFINED SPHINX_THEME_DIR)
    set(SPHINX_THEME_DIR)
endif()
 
# configured documentation tools and intermediate build results
set(BINARY_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/_build")
 
# Sphinx cache with pickled ReST documents
set(SPHINX_CACHE_DIR "${CMAKE_CURRENT_BINARY_DIR}/_doctrees")
 
# HTML output directory
set(SPHINX_HTML_DIR "${CMAKE_CURRENT_BINARY_DIR}/html")
 
configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in"
    "${BINARY_BUILD_DIR}/conf.py"
    @ONLY)
 
add_custom_target(my_project_docs ALL
    ${SPHINX_EXECUTABLE}
        -q -b html
        -c "${BINARY_BUILD_DIR}"
        -d "${SPHINX_CACHE_DIR}"
        "${CMAKE_CURRENT_SOURCE_DIR}"
        "${SPHINX_HTML_DIR}"
    COMMENT "Building HTML documentation with Sphinx")

This script sets a few variables for optionally specifying a theme and the theme directory (more on this shortly). It also sets a few directories based on Sphinx conventions, you can change these to taste.

The real meat of the script is in the configure_file and add_custom_target commands. The add_custom_target command uses the Sphinx executable that was previously found to generate html documentation. This is simply invoking the command line Sphinx build tool, so you can update this command to suit your specific needs (such as also generating PDF documentation). Be sure to update the name of the target from my_project_docs to something appropriate for your project.

With the configure_file command CMake allows the ability of copying a file from the source directory to the binary directory, as well as allowing the replacement of CMake variables within the text by encasing the variable name in @@ (e.g. @variable_name@). We will use this capability to dynamically generate a conf.py for Sphinx, allowing users to configure “everything” via CMake.

Rename the the conf.py file that was generated previously to conf.py.in and update the following lines.

html_theme = '@SPHINX_THEME@'

...

html_theme_path = ['@SPHINX_THEME_DIR@']

...

htmlhelp_basename = 'YOUR_PROJECT_NAMEdoc'

Then remove the sections of the configuration for LaTeX and manual page output and save the file.

That’s it! You will now find a my_project_docs project (or whatever you have renamed the docs target to) that will generate documentation for you on each build whenever changes have been made.

You can navigate to (ROOT_BINARY_DIR)/html/index.html (or /html/Debug/index.html on Visual Studio platforms) to view the generated documentation.