Version switcher#

In many software packages used as libraries, it is useful to provide documentation for both the current release of the software as well as for other versions (such most recent development build or notable older versions).

There is now a Python package [1] which makes it easy to provide several documentation versions, even for projects that do not [2] use ReadTheDocs (RTD) [3] to host their documentation.

Versioning#

This project uses semantic versioning. [4] Builds of the documentation should include the current version, including build information if not a tagged release. The most popular packages to automate this task are setuptools-scm [5] (used in this package) and the older python-versioneer. [6]

The version of the current documentation being built is defined in the Sphinx configuration (conf.py). First, the switcher.json file is read into a Python dictionary:

switcher_version_list = [
    v["version"]  # to match with ``release`` (above)
    for v in json.load(open(switcher_file))
]

Then, the current version is compared against all "version" keys in switcher_version_list. If it matches a key, then use it. (This would match a tag.) If it does not match, then name this version "dev" for the purposes of the version switcher dropdown. These lines of code provide that configuration:

"switcher": {
    "json_url": switcher_json_url,
    "version_match": release if release in switcher_version_list else "dev"
}

Cache of documentation versions#

While the pydata-sphinx-theme [7] documentation provides detailed instructions to add a dropdown menu to a project’s html site to switch between documentation versions, it does not describe where to keep the cache of prior versions of the built documentation. For many projects, this task is automated by the RTD service. Projects that do not use RTD should provide their own scheme.

This project keeps the cache of previous documentation versions in the GitHub repository’s gh-pages branch. [8] The documentation’s continuous integration (CI) workflow builds the current documentation into a temporary directory, then downloads the gh-pages branch and copies only those documentation versions listed now in the switcher.json file. The workflow also makes soft links to the development (dev) and latest (latest) versions, then pushes that temporary directory back to the repository’s gh-pages branch. [9]

The cache will grow as each new tag creates a new version of the documentation. While this is probably acceptable for small projects, larger projects (such as those which include many graphics files) may require 100 MB or more for each version. Quickly, the gh-pages branch can grow too large and the project should identify other ways to capture and host the docs for each tagged version. Suggestions are welcomed for other schemes to capture and serve the cache.

the switcher.json file#

The project’s switcher.json file [10] describes [7] the documentation versions available. Here is an example:

[
    {
        "name": "development (main branch)",
        "version": "dev",
        "url": "https://prjemian.github.io/demo2301/dev/"
    },
    {
        "name": "1.0.2 (latest)",
        "version": "1.0.2",
        "url": "https://prjemian.github.io/demo2301/1.0.2/"
    },
    {
        "name": "1.0.0",
        "version": "1.0.0",
        "url": "https://prjemian.github.io/demo2301/1.0.0/"
    }
]

Location#

Per the docs, [7] The JSON file needs to be at a stable, persistent, fully-resolved URL.

A convenient place to keep this file is in the main branch of the project’s GitHub repository. One logical place is in the Sphinx documentation’s _static directory. We name this file switcher.json, as suggested by the docs. [7] Since this file will be read directly from the GitHub repository main branch, we must use the raw content URL, to avoid all the additional GitHub controls from the regular web page. So, this file: https://raw.githubusercontent.com/prjemian/demo2301/main/docs/source/_static/switcher.json

Content#

Each version to be published will have its own JSON dictionary in the list. Here is an example:

{
    "version": "1.0.0",
    "url": "https://prjemian.github.io/demo2301/1.0.0/"
}

Here are some of the conventions used by this project:

  • If name will be same as version, then omit name.

  • Append (latest) to the name of the most recent release.

  • If size of the documentation cache is a concern (such as when hosting in the project’s GitHub repository gh-pages branch) consider keeping this list between 5 to 10 versions, so the cache does not grow too large.

Editing#

For now, edit the switcher.json file manually just before making a new tag, as described below in the Checklist for a new tag section.

Mark only one version as (latest)

Tip

After editing and pushing a revised switcher.json file, your web browser cache may still retain the old version. You might need to clear the browser’s cache, force a refresh, or wait a few minutes for the new revision to be used.

Styling#

Certain items in the version dropdown are styled (background color is changed to advise selection) using custom CSS (file _static/css/custom.css, as suggested in the docs. [7] The CSS matches text content in the JSON file to apply custom styling. See the conventions described in the section Content. Here is an example:

/* Style the link marked: latest */
.version-switcher__container a[data-version-name*="(latest)"] {
  background-color: lightgreen;
}

/* Style the link marked: dev */
.version-switcher__container a[data-version="dev"] {
  background-color: var(--pst-color-secondary);
}

versions in docs CI workflow#

For now, the list of versions (includes old versions and possible future versions) is defined in .github/workflows/pages.yml (the docs CI workflow). It makes sense to move this list to a separate file, making it easier to find and update without disturbing the code in the CI workflow. Here’s an example (bash code within the .yml file):

# List of documentation versions to keep.
# (should include all versions in switcher.json)
# Adding future versions will capture that version
# once it appears in the downloaded gh-pages branch.
versions=
versions+=" 0.0.4"
versions+=" 0.0.5"
versions+=" 0.0.6"
versions+=" 1.0.0"
versions+=" 1.0.2"
versions+=" 1.0.3"
versions+=" 1.0.4"

When a new tag appears that matches an item in this list, then the docs will be built with the new tag, rather than "dev", indicating a development version. By this technique, only tags matching in the list will be differentiated from development versions, including release candidate tags.

Checklist for a new tag#

  • complete all issues related to the new tag

  • merge all open pull requests

  • update the CHANGES.rst file for the new tag

  • ensure all CI workflows pass with no errors

  • make sure the new version appears in the list in the docs CI workflow file pages.yml

  • consider using a release candidate sequence [11] to test before applying the new tag

  • only update next version in the switcher.json file just before creating the new tag

  • be certain to push that commit before the tag and wait until the docs CI finishes

  • Once the docs CI finishes, tag and push the new tag; this will create the new version of the docs


Footnotes#