|
| 1 | +:::{default-domain} bzl |
| 2 | +::: |
| 3 | + |
| 4 | +# Read the Docs integration |
| 5 | + |
| 6 | +The {obj}`readthedocs_install` rule provides support for making it easy |
| 7 | +to build for, and deploy to, Read the Docs. It does this by having Bazel do |
| 8 | +all the work of building, and then the outputs are copied to where Read the Docs |
| 9 | +expects served content to be placed. By having Bazel do the majority of work, |
| 10 | +you have more certainty that the docs you generate locally will match what |
| 11 | +is created in the Read the Docs build environment. |
| 12 | + |
| 13 | +Setting this up is conceptually simple: make the Read the Docs build call `bazel |
| 14 | +run` with the appropriate args. To do this, it requires gluing a couple things |
| 15 | +together, most of which can be copy/pasted from the examples below. |
| 16 | + |
| 17 | +## `.readthedocs.yaml` config |
| 18 | + |
| 19 | +In order for Read the Docs to call our custom commands, we have to use the |
| 20 | +advanced `build.commands` setting of the config file. This needs to do two key |
| 21 | +things: |
| 22 | +1. Install Bazel |
| 23 | +2. Call `bazel run` with the appropriate args. |
| 24 | + |
| 25 | +In the example below, `npm` is used to install Bazelisk and a helper shell |
| 26 | +script, `readthedocs_build.sh` is used to construct the Bazel invocation. |
| 27 | + |
| 28 | +The key purpose of the shell script it to set the |
| 29 | +`--@rules_python//sphinxdocs:extra_env` and |
| 30 | +`--@rules_python//sphinxdocs:extra_defines` flags. These are used to communicate |
| 31 | +`READTHEDOCS*` environment variables and settings to the Bazel invocation. |
| 32 | + |
| 33 | +## BUILD config |
| 34 | + |
| 35 | +In your build file, the {obj}`readthedocs_install` rule handles building the |
| 36 | +docs and copying the output to the Read the Docs output directory |
| 37 | +(`$READTHEDOCS_OUTPUT` environment variable). As input, it takes a `sphinx_docs` |
| 38 | +target (the generated docs). |
| 39 | + |
| 40 | +## conf.py config |
| 41 | + |
| 42 | +Normally, readthedocs will inject extra content into your `conf.py` file |
| 43 | +to make certain integration available (e.g. the version selection flyout). |
| 44 | +However, because our yaml config uses the advanced `build.commands` feature, |
| 45 | +those config injections are disabled and we have to manually re-enable them. |
| 46 | + |
| 47 | +To do this, we modify `conf.py` to detect `READTHEDOCS=True` in the environment |
| 48 | +and perform some additional logic. See the example code below for the |
| 49 | +modifications. |
| 50 | + |
| 51 | +Depending on your theme, you may have to tweak the conf.py; the example is |
| 52 | +based on using the sphinx_rtd_theme. |
| 53 | + |
| 54 | +## Example |
| 55 | + |
| 56 | +``` |
| 57 | +# File: .readthedocs.yaml |
| 58 | +version: 2 |
| 59 | +
|
| 60 | +build: |
| 61 | + os: "ubuntu-22.04" |
| 62 | + tools: |
| 63 | + nodejs: "19" |
| 64 | + commands: |
| 65 | + - env |
| 66 | + - npm install -g @bazel/bazelisk |
| 67 | + - bazel version |
| 68 | + # Put the actual action behind a shell script because it's |
| 69 | + # easier to modify than the yaml config. |
| 70 | + - docs/readthedocs_build.sh |
| 71 | +``` |
| 72 | + |
| 73 | +``` |
| 74 | +# File: docs/BUILD |
| 75 | +
|
| 76 | +load("@rules_python//sphinxdocs:readthedocs.bzl.bzl", "readthedocs_install") |
| 77 | +readthedocs_install( |
| 78 | + name = "readthedocs_install", |
| 79 | + docs = [":docs"], |
| 80 | +) |
| 81 | +``` |
| 82 | + |
| 83 | +``` |
| 84 | +# File: docs/readthedocs_build.sh |
| 85 | +
|
| 86 | +#!/bin/bash |
| 87 | +
|
| 88 | +set -eou pipefail |
| 89 | +
|
| 90 | +declare -a extra_env |
| 91 | +while IFS='=' read -r -d '' name value; do |
| 92 | + if [[ "$name" == READTHEDOCS* ]]; then |
| 93 | + extra_env+=("--@rules_python//sphinxdocs:extra_env=$name=$value") |
| 94 | + fi |
| 95 | +done < <(env -0) |
| 96 | +
|
| 97 | +# In order to get the build number, we extract it from the host name |
| 98 | +extra_env+=("--@rules_python//sphinxdocs:extra_env=HOSTNAME=$HOSTNAME") |
| 99 | +
|
| 100 | +set -x |
| 101 | +bazel run \ |
| 102 | + --stamp \ |
| 103 | + "--@rules_python//sphinxdocs:extra_defines=version=$READTHEDOCS_VERSION" \ |
| 104 | + "${extra_env[@]}" \ |
| 105 | + //docs:readthedocs_install |
| 106 | +``` |
| 107 | + |
| 108 | +``` |
| 109 | +# File: docs/conf.py |
| 110 | +
|
| 111 | +# Adapted from the template code: |
| 112 | +# https://github.com/readthedocs/readthedocs.org/blob/main/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl |
| 113 | +if os.environ.get("READTHEDOCS") == "True": |
| 114 | + # Must come first because it can interfere with other extensions, according |
| 115 | + # to the original conf.py template comments |
| 116 | + extensions.insert(0, "readthedocs_ext.readthedocs") |
| 117 | +
|
| 118 | + if os.environ.get("READTHEDOCS_VERSION_TYPE") == "external": |
| 119 | + # Insert after the main extension |
| 120 | + extensions.insert(1, "readthedocs_ext.external_version_warning") |
| 121 | + readthedocs_vcs_url = ( |
| 122 | + "http://github.com/bazelbuild/rules_python/pull/{}".format( |
| 123 | + os.environ.get("READTHEDOCS_VERSION", "") |
| 124 | + ) |
| 125 | + ) |
| 126 | + # The build id isn't directly available, but it appears to be encoded |
| 127 | + # into the host name, so we can parse it from that. The format appears |
| 128 | + # to be `build-X-project-Y-Z`, where: |
| 129 | + # * X is an integer build id |
| 130 | + # * Y is an integer project id |
| 131 | + # * Z is the project name |
| 132 | + _build_id = os.environ.get("HOSTNAME", "build-0-project-0-rules-python") |
| 133 | + _build_id = _build_id.split("-")[1] |
| 134 | + readthedocs_build_url = ( |
| 135 | + f"https://readthedocs.org/projects/rules-python/builds/{_build_id}" |
| 136 | + ) |
| 137 | +
|
| 138 | +html_context = { |
| 139 | + # This controls whether the flyout menu is shown. It is always false |
| 140 | + # because: |
| 141 | + # * For local builds, the flyout menu is empty and doesn't show in the |
| 142 | + # same place as for RTD builds. No point in showing it locally. |
| 143 | + # * For RTD builds, the flyout menu is always automatically injected, |
| 144 | + # so having it be True makes the flyout show up twice. |
| 145 | + "READTHEDOCS": False, |
| 146 | + "github_version": os.environ.get("READTHEDOCS_GIT_IDENTIFIER", ""), |
| 147 | + # For local builds, the github link won't work. Disabling it replaces |
| 148 | + # it with a "view source" link to view the source Sphinx saw, which |
| 149 | + # is useful for local development. |
| 150 | + "display_github": os.environ.get("READTHEDOCS") == "True", |
| 151 | + "commit": os.environ.get("READTHEDOCS_GIT_COMMIT_HASH", "unknown commit"), |
| 152 | + # Used by readthedocs_ext.external_version_warning extension |
| 153 | + # This is the PR number being built |
| 154 | + "current_version": os.environ.get("READTHEDOCS_VERSION", ""), |
| 155 | +} |
| 156 | +``` |
0 commit comments