4.6. Documentation

Documentation is an essential aspect not only of programming and software development, but of science and many other aspects of life as well. In science, we rest on the work others have done prior to us. Hence, one constituting aspect of science is its independence from the individual person. This only works if we document in sufficient detail what we have done, in order for others to understand what we did. In scientific software development, we are bound to this reproducibility aspect of science, particularly in those cases where our software takes part in creating scientific knowledge.

However, there is another aspect of documentation, and a rather egoistic one, although pretty nicely phrased by Allesina and Wilmes:

Note that there is no way to email yourself in the past to ask for clarifications.

[Allesina and Wilmes, 2019], p. 2

You will not remember in sufficient detail what you have done after a (very) short time. Hence your only chance is to document what you have done. What does that mean in terms of software? While partly, the code should be (self-)documenting, actually telling you what happens, there are things that cannot be communicated in code. This is decisions (not) taken, the overall architecture, and the “big picture”, to name just a few aspects.

4.6.1. Typical problems with documentation

Documentation is usually considered an afterthought, once the program is written and works. However, this is a serious mistake, as it usually results in no (sufficient) documentation ever being written. Nevertheless, documentation is an additional task, and therefore, it should be as convenient and simple as possible.

Typical problems of documentation we usually encounter in context of software development:

  • No (sufficient) documentation

  • Outdated (and hence often wrong or misleading) documentation

  • Badly structured and hard to read documentation

Additionally, typical code is not very readable and expressive, hence not very self-documenting. The latter is a huge topic on its own, and there are entire books written about this topic, starting with “The elements of programming style” [Kernighan and Plauger, 1978] and not restricted to “Clean Code” [Martin, 2008]. The topic of self-documenting (clean) code is not covered here in detail, the interested reader is referred to the literature.

But how to overcome the mentioned typical problems with documentation of our software? A few ideas that will be detailed below:

  • Documentation needs to be as close to the code as possible.

  • Documentation external to the code should be focussed and as independent as possible from the actual implementation.

  • Well-formatted documentation should be generated with automatic tools from sources under version control and next to our code.

  • We need to make documentation part of our daily programming business. Writing code is considered incomplete until the relevant documentation is created/updated.

But before we dive into the more technical details, it is important to get an overview of the different types of documentation and their different audiences and intents.

4.6.2. Different types of documentation

Documentation comes in many different formats, for different audiences, and with different technical constraints. From the vast amount of different ways to look at and distinguish between types of documentation of software, we will focus here on two aspects: format and place, and intended readership.

4.6.2.1. Format and place

To be easily maintainable, and hence having a chance to be reasonably up to date, documentation should exist in formats that can be written easily by programmers and be located close to the actual code. While our goal as programmers should be to write code that is expressive and self-documenting, there are things that simply cannot be expressed in code itself. Hence, some additional documentation is always necessary. Three formats and places can be distinguished:

  • README

    Usually a plain text file using simple markup such as Markdown or reStructuredText, residing in the root directory of your project, and answering some very basic questions: What is it? What does it? Why might it be relevant for me? How do I install and use it? What is the license?

  • Documentation in code

    Usually block comments for modules, classes, methods, functions that can automatically be extracted using dedicated tools. This type of documentation, the API documentation, addresses mainly the developers and in part the users of your software.

  • External documentation

    Everything that does not fit in the README (due to space restrictions) nor in the documentation directly in the code. Typical examples are user manuals and conceptual considerations. While being external to the actual code, this kind of documentation is best created using an expressive markup language (such as reStructuredText), located next to the actual code, put under version control, and converted into a well-formatted output using robust and widely available tools.

Dividing the different types of documentation with respect to format and place where they reside is but just one option, and it is important to have a look at the intended readership as well.

4.6.2.2. Intended readership

A (new) user of your software is usually not interested in the API documentation, at least not in the beginning. People familiar with your software need more details, and developers are again interested in other aspects, such as a well-formatted and complete API documentation as well as all the necessary information on conventions for contributing.

Without claiming completeness, we can distinguish the following distinct scenarios and respective roles of documentation:

  • quickstart/introduction

    The first encounter of a potential user with your software: Here, you should answer the most relevant questions in a concise and focussed way: What is it? What does it? Why might it be relevant for me? How do I install and use it? What is the license? Typically, this is the information contained in a README file, but the first page of your nicely formatted documentation available online should answer the same questions (and may well share text with the README file).

  • use cases and examples

    A very powerful way of getting an idea of the look and feel of a software is a series of well-chosen use cases and worked-through examples. Here, the focus is on potential users that know the problem domain but not your software and its internals. Strive for real-world problems with these use cases, and provide a series of examples with increasing level of complexity.

  • user manual

    Use cases and examples are more casual, the user manual is the place for a thorough introduction of the underlying concepts and the top reference for your users. Given that software develops and the user interface and particular the details will inevitably change over time, focus on the big picture and the more stable functionality and interfaces and refer to the (well written and fairly complete) API documentation for the actual details.

  • developer manual

    Developers are interested in other aspects than general users of your software. But beware that the developer manual is distinct from the API documentation. Here, place things such as coding conventions, how to contribute, and what (additional) software to install to contribute to the further development of your software.

  • API documentation

    The most detailed source of documentation regarding the actual functionality of your software. At least this part of the documentation needs to be automatically extracted from the source code itself. Using tools such as Sphinx allows to combine the API documentation with most of the other parts, such as a general introduction, use cases, a user and a developer manual. When writing docstrings for your modules, classes, methods and functions, have in mind that the ordinary user will read them to figure out how to actually use your software – not only developers interested in contributing.

In a certain sense, there is even another kind of documentation particularly for developers: the actual specification of what your software should do. While traditionally, this is the realm of much paper work, a decent suite of tests can account for a much better specification document. Tests are executable specification, and if you write your tests with readability and code expressivity in mind, even those not familiar with programming can make sense of it. Furthermore, due to being executable, tests are much more likely to be up to date and synchronised with your code base.

4.6.3. Documentation in the code

Probably there is a direct correlation between the correctness and being up-to-date of documentation and its distance from the actual code. Hence, documentation in code has a higher chance of being up-to-date than documentation external to the code. For details of the latter, see the next section.

There are two types of documentation in the code, and both have usually a substantially different and non-overlapping focus:

  • comment blocks

    Usually documentation headers, in Python language “docstrings”. Typically the document modules, classes, methods, and functions. The audience is both, the developer as well as the user interested in how to really use your code.

    For functions and methods, all input parameters and return values should be documented and ideally usage examples given. Quite importantly, these comment blocks should start with a single short line explaining what the documented piece of code is all about, followed by a more detailed explanation.

    How does it look like in Python? Docstrings are surrounded by triple quotes: """. Furthermore, the first line of a docstring should contain a concise description (and fit to a single line!). For further details, see PEP 257.

  • inline comments

    Inline comments are single comment lines in the code, and they should be kept to an absolute minimum. It is this type of comments Robert Martin refers to as failure of us as programmers to reasonably express ourselves in code. While hard to prevent entirely, often such inline comments can be removed by refactoring (extracting methods and alike) and renaming variables and functions/methods. Code should express its intent on its own, not needing our comments.

    How does it look like in Python? Inline comments start with a hash: #, followed by a single space, and preceded by two spaces. An inline comment may belong to the line it is appended to or to the line directly following it. The latter is typically the case for slightly longer comments or longer lines.

    Keep inline comments to a minimum and as short as possible. Multiline inline comments are a contradiction in terms and should be used only very rarely.

A problem with documentation mentioned above already: it is easily out of sync with the code. Why is this such a problem? Comments are usually easier to read for us than the actual code. Hence we have a tendency to believe the comment and not read the code. This is, however, a real mistake, as the code gets executed, not our comments. Particularly with inline comments, the problem can be dramatic: we tend to change the code and forget to change or remove these inline comments. The result: the comments are lying about our code and mislead us when trying to understand what the code actually does.

4.6.4. Documentation external to the code

If documentation in the code already suffers from easily being out of sync with the actual code, things get even worse with documentation that is external to the code. To cope with this, there is a number of strategies:

  • Place the documentation as close to the code as possible.

    For everything except the README, this usually means to create a dedicated directory next to your actual code and place it there.

  • Put the documentation under version control.

    Thus you can both, easily track the changes you made to the documentation as well as checking out both, code and documentation whenever you develop your software further – and so can do all your collaborators and contributors.

  • Restrict yourself to text formats for the documentation and use automatic build tools to produce nicely formatted output.

    In case of Python, use reStructuredText and Sphinx for everything from the user manual to the API documentation. Furthermore, build your documentation often and enjoy the results. This is highly motivating, as you “already can see something”.

  • Focus on the bigger picture and the stable interfaces.

    Software will always develop and change. The farther away your documentation is from the changing parts of your software, the higher the change that they will get out of sync. However, the overall ideas and the general usage are much less likely to change than details of the implementation. If you consciously introduce breaking changes in a software package that gets used already, you are usually about to release a major update, and this warrants a major update of your user documentation as well.

As for the types of documentation that is external to the code, they have been described in quite some detail above already. Hence it suffices here to just list the different parts:

  • README

  • Quickstart

  • user manual

  • use cases/examples

  • developer manual

  • API documentation (automatically extracted from docstrings)

Do I really need to create all these types of documentation for my project? Eventually yes. But if you start, you will usually start with a subset. Any project without a README file (with reasonable contents) is considered incomplete. Furthermore, as soon as you start writing functions, classes, and methods, do write docstrings and automatically create an API documentation. This helps you as much (if not much more) as your users.

4.6.5. Tools for creating documentation

Creating documentation does not mean that somebody writes the actual information contained in the different comments and documents for you. It rather means that there are tools available that take all the relevant available information and markup and generate well-formatted output in different formats. The closest you can come to having actual content being created is using your IDE or other tools to create comment header stubs for classes, methods, and functions for you, given you have already created their interface (i.e. signature). However, it is still up to you to provide the meaning of the parameters and the code.

Probably for every programming language there are tools available that automatically extract the comment headers (in Python: docstrings) for your API documentation. In the Python world, the tool of choice used by many projects and the Python core developers themselves is “Spinx”. For details, see the next section.

The most important aspect of using tools to create the documentation for you is that they generate well-formatted output (in different formats) that is nice and convenient to look at. Frequently building documentation makes writing documentation much more fun – as you “can always see something already”. It is similar to writing code test-first or generally with tests in place: running the tests makes you confident you did not break anything, and having a previously failing test pass tells you that you’ve achieved something. Similarly, regularly and frequently building your documentation and looking at the output (probably in your web browser if you generate HTML) prevents you from introducing too many errors and typos in your markup and motivates you to continue with documenting.

4.6.6. Documentation in Python: reStructuredText and Sphinx

While inline comments are always plain text (and do not get rendered into any other format), comment headers (docstrings) and all other parts of documentation require some more complex markup. While allowing for more complex formatting in the rendered output, markup should still be readable in plain text and not distract the reader too much. Hence, HTML/XML is usually not an option. Different markup languages have evolved in the last several years, and two are particularly important here: Markdown and reStructuredText. While Markdown is more widespread, reStructuredText is the default for Python and much more powerful than (standard) Markdown in terms of the possible formatting.

Furthermore, Sphinx is the de facto tool for generating well-formatted documentation in Python. It does two things: extracting docstrings from the code, and rendering arbitrary files written in reStructuredText for your documentation. What is more, both can be combined into one documentation, as often encountered for Python projects. But Sphinx is not restricted to documenting Python packages. This entire course is written in reStructuredText and rendered into HTML for the web using Sphinx (and yes, of course it is versioned using git).

Note

The following description assumes a certain basic project directory layout with all code contained in a directory of the same name as your project, and the documentation in a docs directory next to the source code. For convenience, here, we just show the overall directory structure assumed hereafter:

myproject/
├── docs
├── myproject
│   ├── __init__.py
│   └── module.py
└── setup.py

For details of the Python package directory layout, see the Packaging chapter.

4.6.6.1. Setting up the documentation build system

Sphinx itself is a Python program. Hence, you need to install Sphinx (in a Python virtual environment) to build/render your documentation with it. Furthermore, building/rendering requires using a terminal.

To install the necessary Python dependencies, create a virtual environment and activate it afterwards. Then install the dependencies using pip:

pip install sphinx

This will download a number of packages Sphinx depends on and hence may take a bit.

Next, to create the basic structure for your package documentation, the simplest way is to call the tool Sphinx comes with for this purpose: sphinx-quickstart. First, create a docs subdirectory in your project root directory (if not already done so), change into that directory, and afterwards issue the following command:

sphinx-quickstart

This will ask you a number of questions that are documented below:

Welcome to the Sphinx 7.1.1 quickstart utility.

Please enter values for the following settings (just press Enter to
accept a default value, if one is given in brackets).

Selected root path: .

You have two options for placing the build directory for Sphinx output.
Either, you use a directory "_build" within the root path, or you separate
"source" and "build" directories within the root path.
> Separate source and build directories (y/n) [n]:

The project name will occur in several places in the built documentation.
> Project name: myproject
> Author name(s): John Doe
> Project release []: 0.1.0

If the documents are to be written in a language other than English,
you can select a language here by its language code. Sphinx will then
translate text that it generates into that language.

For a list of supported codes, see
https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-language.
> Project language [en]:

Creating file /<path>/conf.py.
Creating file /<path>/index.rst.
Creating file /<path>/Makefile.
Creating file /<path>/make.bat.

Finished: An initial directory structure has been created.

You should now populate your master file /<path>/index.rst and create other documentation
source files. Use the Makefile to build the docs, like so:
   make builder
where "builder" is one of the supported builders, e.g. html, latex or linkcheck.

Hooray, the basic structure for documenting your project has been created. Can we see something already? Yes, we can. From within the docs directory (you are currently in anyway), and assuming you are working with a unixoid operating system, simply do a:

make html

This will print a lot of useful things on the command line and tell you afterwards the piece of information you are most interested in for now:

The HTML pages are in _build/html.

Hence, direct the browser of your liking to this directory, best to the file index.html contained therein, and have a look at the result. With a recent Linux distribution, you might even be able to use:

open _build/html/index.html

The open command will in this case default to your default web browser. If this does not work, simply replace it with the name of your preferred browser.

Are we done yet? Not really. This is just the basic structure for the external project documentation. Besides adjusting the Sphinx configuration (residing in the conf.py file in the docs directory), we need to add the API documentation. Here again, Sphinx provides you with the necessary tooling to make life a bit easier. The required steps are:

  • call sphinx-apidoc with the correct options

  • add the newly created document(s) to the main toc tree

Assuming you are in the docs directory of your project and adhere to the directory structure shown above (see Packaging for a more detailed description of the Python package directory layout), creating the prerequisites for the API documentation from within the docs directory is as simple as calling:

sphinx-apidoc ../myproject -o api -e -T

Here, we specified the source code directory (../myproject), the output directory (via -o api), told the tool to create separate pages for each module (-e) and to not create a separate table of contents file (-T). The result shown in the terminal will be something like:

Creating file api/myproject.rst.
Creating file api/myproject.mymodule.rst.

Here, I usually rename the file myproject.rst (i.e., the one with the name of your package) into index.rst and add this in turn to the main index of the documentation, i.e. to the file docs/index.rst. This is an excerpt of docs/index.rst with the reference to the API documentation added:

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

   api/index

Furthermore, we need to add an extension to the Sphinx configuration residing in docs/conf.py to make the documentation work. Here an excerpt of the docs/conf.py file with the extension added. Just search for the definition of extensions. As we are just about adding extensions, let’s add two other extensions as well that we need later on anyway, namely napoleon for properly formatted docstrings and intersphinx for cross-linking to (at least) the official Python documentation from within your API documentation:

extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.napoleon',
    'sphinx.ext.intersphinx',
]

To make the latter work appropriately, you need to add mappings, shown here for the Python documentation and a list of default packages commonly used in scientific software (NumPy, SciPy, Matplotlib). Note that the links to the latter may be subject to change. Simply add the following lines somewhere (perhaps towards the end) in your docs/conf.py file:

intersphinx_mapping = {
    'python': ('https://docs.python.org/3', None),
    'matplotlib': ('https://matplotlib.org/stable/', None),
    'numpy': ('https://numpy.org/doc/stable/', None),
    'scipy': ('https://docs.scipy.org/doc/scipy/reference/', None),
}

Are we done yet? Nearly. Just one thing is left: properly taking care of the version numbers. Remember that we stored the version number in exactly one place, namely the file VERSION in the project root directory? See the Version numbers (SemVer) chapter for further details. Hence, we need to make Sphinx read this information as well from the VERSION file to always have the correct version number appear in our documentation. As the Sphinx configuration conf.py is a Python file, that’s easy to achieve. Here is an excerpt of the conf.py for our project with the necessary adjustments:

# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

import os

with open(os.path.join(os.path.dirname(__file__), '..', 'VERSION')) as \
        version_file:
    release_ = version_file.read().strip()

project = 'myproject'
copyright = '2023, Till Biskup'
author = 'Till Biskup'

# The short X.Y version
version = ".".join(release_.split('.')[0:2])
# The full version, including alpha/beta/rc tags
release = release_

But that should be it with setting up the documentation build system. Now you may ask: Is it really necessary to follow all these steps for each of your projects? Aren’t there any shortcuts or tools available? Indeed, there are:

Tip

Tired of having to manually setup your documentation build system? Have a look at the pymetacode package that does not only help you setting up your Python project directory structure and the documentation, but keeps track of adding the module documentation as well, besides greatly facilitating you with maintaining your project.

4.6.6.2. Building the documentation

Once Sphinx is setup, actually building your documentation is fairly simple – and this means that you can and should build it regularly (at least every time you changed something). Open a terminal, activate your Python virtual environment with Sphinx and your project installed into, change to the docs directory of your project, and simply issue the command:

make html

Sphinx keeps track of which files have been changed, and while normally a perfectly valid approach, as this saves you quite some time, if you changed the table of contents, this might sometimes not work as expected. In such cases, you can remove the old build of your documentation, using make clean and safely create a new one. You can even combine both in one command:

make clean html

Why should you frequently build your documentation? For two reasons: (i) it is motivating, as you can always “already see something”, and motivation is helpful for creating good documentation, and (ii) you can check if something went wrong and easily fix any problem that might appear. Just as with programming and actually running your code to see that it works as expected, make it a habit for building your documentation as well. This includes you having changed anything in any docstring in your code. Take care of the warnings Sphinx issues, they are usually quite helpful.

4.6.6.3. Adding content: What and how to write?

The first sections of this chapter tried to give part of an answer to this question already. Generally, you should at least have a first page of your documentation telling the (potential) users all they need to know to decide whether they want to use your software and if so, how they get started. Furthermore, you should document the API of your code thoroughly, and if only for your future-me.

Some excellent information on how to write good documentation can be found online:

Of course, there is much more, but these may get you started.

4.6.6.4. Changing the way things look: themes

The standard theme (“alabaster”) used by Sphinx may not be your favourite. Fortunately, changing the way things look is fairly simple in Sphinx, as it supports themes, with “alabaster” being just the default theme. One theme that is particularly widespread in use for documenting software is the one provided by Read the Docs. See their documentation for an example. But there are many others. Have a look at https://sphinx-themes.org/ for an (incomplete) overview.

Generally, you can choose between a series of built-in themes, or install another theme as a Sphinx extension via pip and use that one. Assuming that you want to use the theme provided by Read the Docs, you first need to install the package via pip:

pip install sphinx-rtd-theme

Once done, tell Sphinx via its docs/conf.py file to use the theme:

# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_theme = 'sphinx_rtd_theme'

With that, after building your documentation it will appear in an entirely different layout. Most themes come with additional configuration settings, such as a logo. Just have a look at the respective documentation.

4.6.6.5. Docstring formatting and IDE support

Generally, Docstrings should follow the advice laid out in PEP 257. For a nice formatting of parameters and alike, two “standards” have emerged: “Google style docstrings” and “NumPy style docstrings”. For scientific software, the clear advice is to use NumPy style docstrings. Examples can be found online:

To make it more convenient for you, configure support for NumPy style docstrings within your IDE whenever possible. For PyCharm, the settings can be found in Preferences > Tools > Python Integrated Tools. Here, you find a section “Docstrings” where you can select the Docstring format from a number of different formats.

4.6.6.6. Adding modules to the API documentation

Whenever you add a new module to your package, you will want to add the necessary files for having Sphinx take care of creating the API documentation for you. This is a two-step process:

  • Create a file for your new module in the docs/api directory, usually with the naming scheme <package>.<module>.rst

  • Add the newly created file to the table of contents of the index file of the API documentation

A typical documentation stub for the API documentation of a new module may look as follows:

<project>.<module> module
=========================

.. automodule:: <project>.<module>
   :members:
   :undoc-members:
   :show-inheritance:

Of course, you need to replace <project> and <module> with the respective names. Afterwards, just add the newly created file to the ToC tree of the api/index.rst file:

.. toctree::
   :maxdepth: 4

   # your other files are here
   <project>.<module>

With that, building your documentation with make html should automatically build the API documentation of your newly added module as well.

4.6.6.7. Handling documentation for multiple versions

If you develop a piece of software and start releasing it (it doesn’t matter whether it is released internally or publicly), you will soon run into the situation of having several releases that each have different documentation. Given that you use Version control (git), this is not a problem at all, as Sphinx got you covered. The magic is handled by the sphinx multiversion extension.

To setup this extension, three steps are required:

  • Install the sphinx-multiversion Python package

  • Set it up in the Sphinx configuration

  • Extend the theme you’re using to support multiple versions

  • Build your documentation (and modifying the Sphinx Makefile for convenience)

Installing the sphinx-multiversion Python package is done as usual, of course within your Python virtual environment where your project and Sphinx are installed:

pip install sphinx-multiversion

Next is to add some bits of configuration to the docs/conf.py file:

import subprocess  # Should go on top, as usual

# ...

extensions = [
    # your other extensions go here
    'sphinx_multiversion',
]

# ...

# Multiversion configuration
smv_branch_whitelist = r'^master.*$'
smv_tag_whitelist = r'^v\d+\.\d+$'
smv_released_pattern = r'^refs/tags/v\d+\.\d+$'

tag = subprocess.run("git describe --tags `git rev-list --tags "
                     "--max-count=1`", shell=True, capture_output=True)
smv_latest_version = tag.stdout.decode().strip()

Not every theme comes with support for multiple versions built-in already. But thanks to the modular design of Sphinx and its themes, this is not a big deal. Here, we show what needs to be done to make multiple versions work with the “Read the Docs” theme, by adding the two files page.html and versions.html to the _templates subdirectory of your docs directory.

The contents the page.html file:

{% extends "!page.html" %}
{% block body %}
{% if current_version and latest_version and current_version != latest_version %}
<div class="admonition warning">
<!-- <p class="admonition-title">Warning</p> -->
  <p>
    {% if current_version.is_released %}
    You're reading <strong>an old version</strong> of this documentation.
    For up-to-date information, please have a look at <a href="{{ vpathto(latest_version.name) }}">{{latest_version.name}}</a>.
    {% else %}
    You're reading the documentation for a <strong>development version</strong>.
    For the latest released version, please have a look at <a href="{{ vpathto(latest_version.name) }}">{{latest_version.name}}</a>.
    {% endif %}
  </p>
</div>
{% endif %}
{{ super() }}
{% endblock %}

The contents the versions.html file:

{%- if current_version %}
<div class="rst-versions" data-toggle="rst-versions" role="note" aria-label="versions">
  <span class="rst-current-version" data-toggle="rst-current-version">
    <span class="fa fa-book"> Other Versions</span>
    {{ current_version.name }}
    <span class="fa fa-caret-down"></span>
  </span>
  <div class="rst-other-versions">
    {%- if versions.tags %}
    <dl>
      <dt>Tags</dt>
      {%- for item in versions.tags %}
      <dd><a href="{{ item.url }}">{{ item.name }}</a></dd>
      {%- endfor %}
    </dl>
    {%- endif %}
    {%- if versions.branches %}
    <dl>
      <dt>Branches</dt>
      {%- for item in versions.branches %}
      <dd><a href="{{ item.url }}">{{ item.name }}</a></dd>
      {%- endfor %}
    </dl>
    {%- endif %}
  </div>
</div>
{%- endif %}

For other themes, you may need to adapt these files slightly. Have a look at the sphinx-multiversion documentation.

And finally, add these lines to your Sphinx-generated Makefile:

# Use sphinx-multiversion to create documentation for multiple versions
multiversion:
    sphinx-multiversion "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) $(0)

This allows you to conveniently create documentation for all the different versions with a single command:

make multiversion

The documentation for the different versions will reside in different subdirectories of the _build/html directory. If you host your documentation online on a webserver, you may want to configure a redirection rule to the latest stable version of your documentation. However, this is beyond the scope of this course.