With renewed activity around rosdoc2 (my PR got merged after 498 days!), and ongoing discussion about the state of documentation in ROS 2, I decided to look at ROS 2 package docs once again. This is also a followup to my older post on releasing a ROS package.

rosdoc2#

The tool responsible for generating ROS 2 package docs is rosdoc2. It is what runs on the ROS build farm whenever you update your package and have enabled the appropriate options in rosdistro/<ROS DISTRO>/distribution.yaml:

  rviz_2d_overlay_plugins:
    doc:
      type: git
      url: https://github.com/teamspatzenhirn/rviz_2d_overlay_plugins.git
      version: main
    release:
    [...]

While somewhat intimidating at first, rosdoc2 really is just a very convenient wrapper around the commonly used Sphinx documentation framework including its C++ integration with doxygen via breathe + exhale. It creates a somewhat sensible default configuration for packages with no documentation or configuration at all, applies some options for a uniform theme and integration with other packages.

Custom Sphinx project#

But where do we go once the default is not sufficient anymore (which is immediately once you start writing docs which go beyond API-docs)? Luckily, it is possible to setup a completely standard sphinx project, which rosdoc2 will pick up on. Using the sphinx-quickstart tool in the <package-root>/doc directory creates an empty sphinx project that rosdoc2 will find. This does not require any custom rosdoc2.yaml! Now, you can write any free-form documentation, tutorials and more just as you would with sphinx, and build and preview them using:

rosdoc2 build --package-path ./<package-path>

Including the generated (default) pages in the custom project#

I wanted to still include the generated API docs in a subdirectory, and link to it from my new, custom index page. Looking at the default index page, all it does is link to generated/index. I added a page cpp_api_docs.rst besides index.rst, with the following content:

C++ API Docs
============

These are the autogenerated docs for the internal implementation.

.. toctree::
   :maxdepth: 3
   :caption: Contents:

   rviz_2d_overlay_plugins <generated/index>

Which is included from index.rst:

.. toctree::
   :maxdepth: 2
   :caption: Contents:

   self
   cpp_api_docs

This results in the generated docs to appear in a sub-category “C++ API Docs” without cluttering the index page or table of contents!

Including an existing README.md#

I already had a README.md with a few screenshots, which I wanted to use as the index page for the documentation, without copying it. After some fiddling around with sphinx, I found a satisfying solution which doesn’t require changing the readme to rst and doesn’t break relative paths to images included in the markdown:

First, create a proxy-file readme_include.md next to index.rst. This is a markdown file which just includes the original README.md, but preserves the relative image paths, which would otherwise break in the next step:

```{include} ../README.md
:relative-images:
```

Then, include the contents of this file from index.rst using myst to include markdown from rst:

.. include:: readme_include.md
   :parser: myst_parser.sphinx_

This also requires adding myst_parser to the extensions in conf.py:

extensions = ["myst_parser"]

What now?#

I will now try to include this in the README of rosdoc2, i guess…

Up next: finding out how to automate more of the process, such as eliminating options in config.py that could be generated from package.xml and generating docs from ROS message definitions!