diff --git a/.readthedocs.yml b/.readthedocs.yml index 9d59380a8..f68ccc839 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -8,4 +8,7 @@ build: commands: - env - npm install -g @bazel/bazelisk - - bazel run --config=rtd --//sphinxdocs:extra_defines=version=$READTHEDOCS_VERSION //docs/sphinx:readthedocs_install + - bazel version + # Put the actual build behind a shell script because its easier to modify than + # the yaml config. + - docs/sphinx/readthedocs_build.sh diff --git a/docs/sphinx/conf.py b/docs/sphinx/conf.py index bfa440051..cfc819f61 100644 --- a/docs/sphinx/conf.py +++ b/docs/sphinx/conf.py @@ -1,5 +1,7 @@ # Configuration file for the Sphinx documentation builder. +import os + # -- Project information project = "rules_python" copyright = "2023, The Bazel Authors" @@ -27,6 +29,29 @@ "sphinx_rtd_theme", # Necessary to get jquery to make flyout work ] +# 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}" + exclude_patterns = ["_includes/*"] templates_path = ["_templates"] primary_domain = None # The default is 'py', which we don't make much use of @@ -69,6 +94,33 @@ html_theme = "sphinx_rtd_theme" html_theme_options = {} +# The html_context settings are part of the jinja context used by the themes. +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, + 'PRODUCTION_DOMAIN': "readthedocs.org", + # This is the path to a page's source (after the github user/repo/commit) + "conf_py_path": "/docs/sphinx/", + 'github_user': 'bazelbuild', + 'github_repo': 'rules_python', + # The git version that was checked out, e.g. the tag or branch name + '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", ""), +} + # Keep this in sync with the stardoc templates html_permalinks_icon = "ΒΆ" @@ -86,7 +138,6 @@ suppress_warnings = ["myst.header", "myst.xref_missing"] - def setup(app): # Pygments says it supports starlark, but it doesn't seem to actually # recognize `starlark` as a name. So just manually map it to python. diff --git a/docs/sphinx/readthedocs_build.sh b/docs/sphinx/readthedocs_build.sh new file mode 100755 index 000000000..e6908a3ca --- /dev/null +++ b/docs/sphinx/readthedocs_build.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -eou pipefail + +declare -a extra_env +while IFS='=' read -r -d '' name value; do + if [[ "$name" == READTHEDOCS* ]]; then + extra_env+=("--//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+=("--//sphinxdocs:extra_env=HOSTNAME=$HOSTNAME") + +set -x +bazel run \ + "--//sphinxdocs:extra_defines=version=$READTHEDOCS_VERSION" \ + "${extra_env[@]}" \ + //docs/sphinx:readthedocs_install diff --git a/sphinxdocs/BUILD.bazel b/sphinxdocs/BUILD.bazel index a47e7023b..cd1a1fbf6 100644 --- a/sphinxdocs/BUILD.bazel +++ b/sphinxdocs/BUILD.bazel @@ -13,7 +13,7 @@ # limitations under the License. load("@bazel_skylib//:bzl_library.bzl", "bzl_library") -load("//sphinxdocs/private:sphinx.bzl", "sphinx_defines_flag") +load("//sphinxdocs/private:sphinx.bzl", "repeated_string_list_flag") package( default_visibility = ["//:__subpackages__"], @@ -21,11 +21,16 @@ package( # Additional -D values to add to every Sphinx build. # This is usually used to override the version when building -sphinx_defines_flag( +repeated_string_list_flag( name = "extra_defines", build_setting_default = [], ) +repeated_string_list_flag( + name = "extra_env", + build_setting_default = [], +) + bzl_library( name = "sphinx_bzl", srcs = ["sphinx.bzl"], diff --git a/sphinxdocs/private/sphinx.bzl b/sphinxdocs/private/sphinx.bzl index bd082e03d..8b3244b60 100644 --- a/sphinxdocs/private/sphinx.bzl +++ b/sphinxdocs/private/sphinx.bzl @@ -155,6 +155,7 @@ _sphinx_docs = rule( ), "strip_prefix": attr.string(doc = "Prefix to remove from input file paths."), "_extra_defines_flag": attr.label(default = "//sphinxdocs:extra_defines"), + "_extra_env_flag": attr.label(default = "//sphinxdocs:extra_env"), }, ) @@ -201,10 +202,15 @@ def _run_sphinx(ctx, format, source_path, inputs, output_prefix): args.add("-E") # Don't try to use cache files. Bazel can't make use of them. args.add("-a") # Write all files; don't try to detect "changed" files args.add_all(ctx.attr.extra_opts) - args.add_all(ctx.attr._extra_defines_flag[_SphinxDefinesInfo].value, before_each = "-D") + args.add_all(ctx.attr._extra_defines_flag[_FlagInfo].value, before_each = "-D") args.add(source_path) args.add(output_dir.path) + env = dict([ + v.split("=", 1) + for v in ctx.attr._extra_env_flag[_FlagInfo].value + ]) + ctx.actions.run( executable = ctx.executable.sphinx, arguments = [args], @@ -212,19 +218,20 @@ def _run_sphinx(ctx, format, source_path, inputs, output_prefix): outputs = [output_dir], mnemonic = "SphinxBuildDocs", progress_message = "Sphinx building {} for %{{label}}".format(format), + env = env, ) return output_dir -_SphinxDefinesInfo = provider( - doc = "Provider for the extra_defines flag value", +_FlagInfo = provider( + doc = "Provider for a flag value", fields = ["value"], ) -def _sphinx_defines_flag_impl(ctx): - return _SphinxDefinesInfo(value = ctx.build_setting_value) +def _repeated_string_list_flag_impl(ctx): + return _FlagInfo(value = ctx.build_setting_value) -sphinx_defines_flag = rule( - implementation = _sphinx_defines_flag_impl, +repeated_string_list_flag = rule( + implementation = _repeated_string_list_flag_impl, build_setting = config.string_list(flag = True, repeatable = True), )