July 6, 2021 by Olivier Goffart and Simon Hausmann
Doc Tools for C++ Libraries
Our GUI Toolkit, SixtyFPS, supports multiple programming languages with a shared runtime library. One of these is C++, with a modern API and documentation. To generate that documentation, we looked around for different tools that the C++ community provides. In this article we discuss three of the many tools that are out there: Hyde, Standardese, and the combination of Doxygen + Breathe + Exhale + Sphinx.
Objectives
Let's look at the following key features:
- There needs to be a way to write documentation for each part of a public C++ API (namespaces, classes, etc.) and integrate it into the reference.
- We need a way to create links, between items in the reference and to outside content. For example if a function returns a particular type, the user can click on that type to go straight to the documentation for it.
- It's necessary to be able to exclude private class members, selected types, or entire namespaces that are mere implementation details and not public API.
- The tool needs to be able to combine API documentation with conceptual content such as overviews, build instructions, or examples - into one cohesive package that can be served on the web.
Hyde
Hyde is hosted at https://github.com/adobe/hyde. Below is a screenshot of the API reference for the stlab libraries, powered by Hyde:
Hyde is written in C++ itself, and is typically built from source, using CMake, and runs on macOS and Linux. The input to the tool is your C++ API, for example a header file. The output is a structure of markdown files, each with a section of YAML meta-data at the top. A site generator like Jekyll consumes these files and prepares them for serving as static HTML.
Workflow
Unlike other tools, Hyde does not extract inline documentation from your C++ code. Instead, Hyde creates a set of files upon first run, to which developers need to add documentation. As the API changes, Hyde is run again, and updates the existing files, and adds new ones for new classes for example. You can also use the validation mode to verify that the documentation structure is in sync with the C++ API - a good feature for use with continuous integration systems.
Evaluation
So how does Hyde contribute to our objectives?
- Developers edit the generated output, making it quite easy to document every aspect of the C++ API: Markdown files can be edited with many tools, even from GitHub via the web interface.
- Hyde maintains a stable hierarchy of files, which makes it suitable for creating links in markdown.
- You can limit the view on the public API by excluding namespaces and hiding private/protected class members
using the
--namespace-blacklist=<string>
and the--access-filter-public
command line options. - The sub-set of the API reference that's co-maintained by Hyde is to be combined with the rest of your conceptual documentation written in markdown. The static site generator then turns that into one package of HTML/CSS/etc., for serving on the web.
Standardese
Next in line is Standardese, a tool that aims to be the "nextgen Doxygen": https://github.com/standardese/standardese. Its output is by default markdown. This is how it looks like when applied to the SixtyFPS C++ Interpreter API documentation:
Standardese is also written in C++. While it is straight-forward to build with CMake, there is also an official Docker image (standardese/standard) that makes it very convenient to run from anywhere. The input is your C++ API as a header file and the output are plain markdown files.
Workflow
Standardese follows the more traditional approach of parsing your C++ code and extracting the documentation from comments within. Inside the comments markdown is expected, no inline HTML is supported.
Evaluation
- The documentation lives right where the API is defined, which means that everything the tool sees can be documented. By default, the generated markdown files represent the input header file structure. There are commands influencing the way entities are grouped together.
- You can write links to other parts of the reference using a markdown link with an empty url:
[link-text](<> "unique-name")
. If the unique name is fully qualified (such assixtyfps::interpreter::Component
), Standardese will automatically create the link to the correct markdown file and anchor within. What's particularly neat though is that out of the box it turns links to types and functions within thestd::
to a link to https://en.cppreference.com/w/. - By default only public class members are visible, and the
--input.blacklist_namespace
command line option allows blending out namespaces. - The resulting markdown files can be served straight from GitHub pages, for example.👍
Doxygen + Breathe + Exhale + Sphinx
Last, but not least, we're going to take a brief look at a popular combination of tools that work together: Doxygen, Sphinx, with the Breathe and Exhale extensions. Doxygen is the the "Volkswagen Beetle" under the C++ documentation tools: It's been around for many years and it's very reliable. Sphinx is the documentation generator that's very popular in the vibrant Python 🐍 community. Combined with two extensions this is what we're using for SixtyFPS at the moment:
Workflow
The setup for this combination starts at Sphinx. It is driven by a central configuration file
(conf.py
), comes with many plugins and different themes. The input is reStructuredText. We use the Exhale
extension to automatically launch Doxygen, which parses the C++ API and produces a generic XML file
structure. These XML files are consumed by the Breathe extension to produce reStructuredText. The
sphinx-build
command is driving this entire process, and the output is a
directory structure of HTML (and CSS, etc.) that is suitable for direct serving on the web.
Doxygen does the heavy lifting of extracting the documentation from comments in the C++ code. It has its own configuration file with many ways of customizing its behavior.
Evaluation
The setup of these tools is fairly complex, many different projects need to play well together. However, so far, it is also the most flexible solution that ticks our boxes:
- The documentation is written in the C++ header files. Doxygen is well established for extracting annotations for all aspects of the API, from namespaces all the way to individual function parameters.
- Doxygen tries to automatically create links when you mention functions, classes or generally other types.
Sometimes it needs a little help with a
\ref
command though. - By default, private class members are excluded from the documentation. Similar to the other tools,
namespaces need to be explicitly excluded, in this case using the
EXCLUDE_SYMBOLS
directive in the configuration file. - The output of Sphinx is plain HTML and CSS, so it is again straight-forward to serve on the web. It comes with search built-in (👍). We also use the MyST extension to feed markdown into Sphinx and produce HTML.
Conclusion
All of these tools do a very good job at supporting the latest C++ standards, as they can all use clang for parsing. They are also actively developed by their communities and they are all Open Source. Their setup differs in complexity, as well as their ability to customize the look and feel of the output. What this means for C++ library developers is that they have a decent choice of different tools available to create the best, integrated documentation packages.
We recognize as well that there are more tools out there, the above is just a set of three that we looked at a bit closer. Join us in the GitHub discussion and let us know what your experience is with documenting your C++ library, what tools you prefer and what you might have found on the horizon of upcoming tools that we should all keep an eye on.
Slint is a Rust-based toolkit for creating reactive and fluent user interfaces across a range of targets, from embedded devices with limited resources to powerful mobile devices and desktop machines. Supporting Android, Windows, Mac, Linux, and bare-metal systems, Slint features an easy-to-learn domain-specific language (DSL) that compiles into native code, optimizing for the target device's capabilities. It facilitates collaboration between designers and developers on shared projects and supports business logic development in Rust, C++, JavaScript, or Python.