This post outlines a basic workflow for C++ projects using Doxygen, Sphinx, and Exhale.

Background

My project proposal for documenting Symengine was recently accepted for the Google Summer of Docs initiative. In the past I have been more than happy to document C++ code using only Doxygen (with pretty fantastic results), while keeping example usage separate (d-SEAMS wiki). Though this is still a feasible method, a monolithic multi-project setup might benefit from Sphinx, which is what will be covered.

Series

This post is the first in a series based on best C++ documentation practices for Sphinx+Doxygen workflows.

  1. Documenting C++ with Doxygen and Sphinx - Exhale <– You are here!
  2. Publishing Doxygen and Sphinx with Nix and Rake
  3. Documenting C++ with Doxygen and Sphinx - doxyrest (TBD)
  4. Adding Tutorials to Sphinx Projects (TBD)

Goals

A couple of goals informed this approach:

  • We expect our documentation to link to the source files
  • We expect a lot of Python developers to contribute
    • Hence Sphinx
  • We would like to write .ipynb files into the docs
    • Another reason to use Sphinx (via MyST{NB})

Folder Structure

1tree -d $prj/ -L 2
.
├──docs
│  ├──Doxygen
│  └──Sphinx
├──nix
│  └──pkgs
├──projects
│  └──symengine
└──scripts
8directories

Essentially we have a scripts directory to store basic build scripts, and two kinds of documentation folders.

Basic Doxygen

The doxygen setup is beautifully simple:

1cd docs/Doxygen
2doxygen -g
3# Easier to edit
4mv Doxyfile Doxyfile.cfg

Since Doxyfile should be updated by subsequent versions of doxygen, it is best to separate the project settings. We will therefore modify some basic settings in a separate file

1touch Doxyfile-prj.cfg
2vim Doxyfile-prj.cfg # or whatever

Edit the file to be (minimally):

 1@INCLUDE                = "./Doxyfile.cfg"
 2GENERATE_HTML           = NO
 3GENERATE_XML            = YES
 4XML_PROGRAMLISTING = NO
 5
 6# Project Stuff
 7PROJECT_NAME           = "myProject"
 8PROJECT_BRIEF          = "Dev docs"
 9OUTPUT_DIRECTORY       = "./gen_docs"
10
11# Inputs
12INPUT                  = "./../../projects/symengine/symengine"
13RECURSIVE              = NO

With this we will now be able to obtain the xml files for the rest of this setup.

Exhale

For our first attempt, we will focus on the automation of Sphinx using the exhale tool.

1# Basic setup
2poetry init
3poetry add exhale breathe

Now we can generate the basic Sphinx structure.

1# Separate source and build
2sphinx-quickstart --sep --makefile docs/Sphinx \
3    --project "My Proj" \
4    --author "Juurj" \
5    --release "latest" \
6    --language "en"

This allows us to generate the Sphinx documentation we require, with some changes to the docs/Sphinx/source/config.py file (lifted from the exhale documentation):

 1extensions = [
 2    'breathe',
 3    'exhale',
 4]
 5
 6# -- Exhale configuration ---------------------------------------------------
 7# Setup the breathe extension
 8breathe_projects = {
 9    "My Proj": "./../../Doxygen/gen_docs/xml"
10}
11breathe_default_project = "My Proj"
12
13 # Setup the exhale extension
14exhale_args = {
15    # These arguments are required
16    "containmentFolder":     "./api",
17    "rootFileName":          "library_root.rst",
18    "rootFileTitle":         "Library API",
19    "doxygenStripFromPath":  "..",
20    # Suggested optional arguments
21    "createTreeView":        True,
22    # TIP: if using the sphinx-bootstrap-theme, you need
23    # "treeViewIsBootstrap": True,
24}
25
26# Tell sphinx what the primary language being documented is.
27primary_domain = 'cpp'
28
29# Tell sphinx what the pygments highlight language should be.
30highlight_language = 'cpp'

We also need to add the output to the index.rst use the following:

1.. toctree::
2   :maxdepth: 2
3   :caption: Contents:
4
5   api/library_root

At this point we are ready to manually build our documentation.

1cd docs/Doxygen
2doxygen Doxyfile-prj.cfg
3cd ../Sphinx
4make html

This is still pretty cumbersome though. We can view our documentation in a more pleasant manner with darkhttpd.

1darkhttpd docs/Sphinx/build/html

With this we now beautify the documentation (with the sphinx_book_theme):

1poetry add sphinx-book-theme

We need to set the theme as well (in the Sphinx config.py file):

1html_theme = 'sphinx_book_theme'

This leads to some pretty documentation.

Figure 1: Generated documentation (Exhale)

Figure 1: Generated documentation (Exhale)

Figure 2: Exhale does a great job with file-based hierarchy.

Figure 2: Exhale does a great job with file-based hierarchy.

Conclusions

At this point, we have a basic setup, which we can tweak with a bunch of themes, and/or different parsers, but this is still pretty rough around the edges. However, a caveat of this setup is that the actual contents of the source are not visible in the generated documentation. In the next post, we will look at automating this setup for deploying with Travis.