Skip to content

Commit

Permalink
docs: tell how to setup readthedocs integration with sphinxdocs (baze…
Browse files Browse the repository at this point in the history
…lbuild#2331)

This comes from setting up readthedocs integration with rules_testing.

It'd be nice if so much copy/paste wasn't necessary, but I'm not sure
how to best
reduce that, so at the least, document what one has to do.
  • Loading branch information
rickeylev authored Oct 23, 2024
1 parent b4cc7b7 commit 6babe59
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 0 deletions.
1 change: 1 addition & 0 deletions sphinxdocs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ is agnostic as to what is being documented.
starlark-docgen
sphinx-bzl
readthedocs
```
156 changes: 156 additions & 0 deletions sphinxdocs/docs/readthedocs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
:::{default-domain} bzl
:::

# Read the Docs integration

The {obj}`readthedocs_install` rule provides support for making it easy
to build for, and deploy to, Read the Docs. It does this by having Bazel do
all the work of building, and then the outputs are copied to where Read the Docs
expects served content to be placed. By having Bazel do the majority of work,
you have more certainty that the docs you generate locally will match what
is created in the Read the Docs build environment.

Setting this up is conceptually simple: make the Read the Docs build call `bazel
run` with the appropriate args. To do this, it requires gluing a couple things
together, most of which can be copy/pasted from the examples below.

## `.readthedocs.yaml` config

In order for Read the Docs to call our custom commands, we have to use the
advanced `build.commands` setting of the config file. This needs to do two key
things:
1. Install Bazel
2. Call `bazel run` with the appropriate args.

In the example below, `npm` is used to install Bazelisk and a helper shell
script, `readthedocs_build.sh` is used to construct the Bazel invocation.

The key purpose of the shell script it to set the
`--@rules_python//sphinxdocs:extra_env` and
`--@rules_python//sphinxdocs:extra_defines` flags. These are used to communicate
`READTHEDOCS*` environment variables and settings to the Bazel invocation.

## BUILD config

In your build file, the {obj}`readthedocs_install` rule handles building the
docs and copying the output to the Read the Docs output directory
(`$READTHEDOCS_OUTPUT` environment variable). As input, it takes a `sphinx_docs`
target (the generated docs).

## conf.py config

Normally, readthedocs will inject extra content into your `conf.py` file
to make certain integration available (e.g. the version selection flyout).
However, because our yaml config uses the advanced `build.commands` feature,
those config injections are disabled and we have to manually re-enable them.

To do this, we modify `conf.py` to detect `READTHEDOCS=True` in the environment
and perform some additional logic. See the example code below for the
modifications.

Depending on your theme, you may have to tweak the conf.py; the example is
based on using the sphinx_rtd_theme.

## Example

```
# File: .readthedocs.yaml
version: 2
build:
os: "ubuntu-22.04"
tools:
nodejs: "19"
commands:
- env
- npm install -g @bazel/bazelisk
- bazel version
# Put the actual action behind a shell script because it's
# easier to modify than the yaml config.
- docs/readthedocs_build.sh
```

```
# File: docs/BUILD
load("@rules_python//sphinxdocs:readthedocs.bzl.bzl", "readthedocs_install")
readthedocs_install(
name = "readthedocs_install",
docs = [":docs"],
)
```

```
# File: docs/readthedocs_build.sh
#!/bin/bash
set -eou pipefail
declare -a extra_env
while IFS='=' read -r -d '' name value; do
if [[ "$name" == READTHEDOCS* ]]; then
extra_env+=("--@rules_python//sphinxdocs:extra_env=$name=$value")
fi
done < <(env -0)
# In order to get the build number, we extract it from the host name
extra_env+=("--@rules_python//sphinxdocs:extra_env=HOSTNAME=$HOSTNAME")
set -x
bazel run \
--stamp \
"--@rules_python//sphinxdocs:extra_defines=version=$READTHEDOCS_VERSION" \
"${extra_env[@]}" \
//docs:readthedocs_install
```

```
# File: docs/conf.py
# Adapted from the template code:
# https://github.com/readthedocs/readthedocs.org/blob/main/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl
if os.environ.get("READTHEDOCS") == "True":
# Must come first because it can interfere with other extensions, according
# to the original conf.py template comments
extensions.insert(0, "readthedocs_ext.readthedocs")
if os.environ.get("READTHEDOCS_VERSION_TYPE") == "external":
# Insert after the main extension
extensions.insert(1, "readthedocs_ext.external_version_warning")
readthedocs_vcs_url = (
"http://github.com/bazelbuild/rules_python/pull/{}".format(
os.environ.get("READTHEDOCS_VERSION", "")
)
)
# The build id isn't directly available, but it appears to be encoded
# into the host name, so we can parse it from that. The format appears
# to be `build-X-project-Y-Z`, where:
# * X is an integer build id
# * Y is an integer project id
# * Z is the project name
_build_id = os.environ.get("HOSTNAME", "build-0-project-0-rules-python")
_build_id = _build_id.split("-")[1]
readthedocs_build_url = (
f"https://readthedocs.org/projects/rules-python/builds/{_build_id}"
)
html_context = {
# This controls whether the flyout menu is shown. It is always false
# because:
# * For local builds, the flyout menu is empty and doesn't show in the
# same place as for RTD builds. No point in showing it locally.
# * For RTD builds, the flyout menu is always automatically injected,
# so having it be True makes the flyout show up twice.
"READTHEDOCS": False,
"github_version": os.environ.get("READTHEDOCS_GIT_IDENTIFIER", ""),
# For local builds, the github link won't work. Disabling it replaces
# it with a "view source" link to view the source Sphinx saw, which
# is useful for local development.
"display_github": os.environ.get("READTHEDOCS") == "True",
"commit": os.environ.get("READTHEDOCS_GIT_COMMIT_HASH", "unknown commit"),
# Used by readthedocs_ext.external_version_warning extension
# This is the PR number being built
"current_version": os.environ.get("READTHEDOCS_VERSION", ""),
}
```

0 comments on commit 6babe59

Please sign in to comment.