diff --git a/CHANGELOG.md b/CHANGELOG.md index 942c890384..a81b5de43c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,9 +52,7 @@ A brief description of the categories of changes: {#v0-0-0-fixed} ### Fixed -* (rules) Setting `--incompatible_python_disallow_native_rules` no longer - causes rules_python rules to fail. - ([#2326](https://github.com/bazelbuild/rules_python/issues/2326). +- Nothing yet {#v0-0-0-added} ### Added @@ -64,6 +62,17 @@ A brief description of the categories of changes: ### Removed - Nothing yet +{#v0-37-1} +## [0.37.1] - 2024-10-22 + +[0.37.1]: https://github.com/bazelbuild/rules_python/releases/tag/0.37.1 + +{#v0-37-1-fixed} +### Fixed +* (rules) Setting `--incompatible_python_disallow_native_rules` no longer + causes rules_python rules to fail + ([#2326](https://github.com/bazelbuild/rules_python/issues/2326)). + {#v0-37-0} ## [0.37.0] - 2024-10-18 diff --git a/python/private/local_runtime_repo_setup.bzl b/python/private/local_runtime_repo_setup.bzl index 23fa99dfa9..3fa484e7c7 100644 --- a/python/private/local_runtime_repo_setup.bzl +++ b/python/private/local_runtime_repo_setup.bzl @@ -61,7 +61,11 @@ def define_local_runtime_toolchain_impl( cc_library( name = "_python_headers", # NOTE: Keep in sync with watch_tree() called in local_runtime_repo - srcs = native.glob(["include/**/*.h"]), + srcs = native.glob( + ["include/**/*.h"], + # A Python install may not have C headers + allow_empty = True, + ), includes = ["include"], ) @@ -69,10 +73,14 @@ def define_local_runtime_toolchain_impl( name = "_libpython", # Don't use a recursive glob because the lib/ directory usually contains # a subdirectory of the stdlib -- lots of unrelated files - srcs = native.glob([ - "lib/*{}".format(lib_ext), # Match libpython*.so - "lib/*{}*".format(lib_ext), # Also match libpython*.so.1.0 - ]), + srcs = native.glob( + [ + "lib/*{}".format(lib_ext), # Match libpython*.so + "lib/*{}*".format(lib_ext), # Also match libpython*.so.1.0 + ], + # A Python install may not have shared libraries. + allow_empty = True, + ), hdrs = [":_python_headers"], ) diff --git a/sphinxdocs/docs/index.md b/sphinxdocs/docs/index.md index ac857d625b..bd6448ced9 100644 --- a/sphinxdocs/docs/index.md +++ b/sphinxdocs/docs/index.md @@ -17,4 +17,5 @@ is agnostic as to what is being documented. starlark-docgen sphinx-bzl +readthedocs ``` diff --git a/sphinxdocs/docs/readthedocs.md b/sphinxdocs/docs/readthedocs.md new file mode 100644 index 0000000000..66e4be82ea --- /dev/null +++ b/sphinxdocs/docs/readthedocs.md @@ -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", ""), +} +``` diff --git a/tests/integration/compile_pip_requirements/WORKSPACE b/tests/integration/compile_pip_requirements/WORKSPACE index 0eeab2067c..fdd6b1da75 100644 --- a/tests/integration/compile_pip_requirements/WORKSPACE +++ b/tests/integration/compile_pip_requirements/WORKSPACE @@ -1,3 +1,19 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "protobuf", + sha256 = "da288bf1daa6c04d03a9051781caa52aceb9163586bff9aa6cfb12f69b9395aa", + strip_prefix = "protobuf-27.0", + url = "https://github.com/protocolbuffers/protobuf/releases/download/v27.0/protobuf-27.0.tar.gz", +) + +http_archive( + name = "rules_cc", + sha256 = "d9bdd3ec66b6871456ec9c965809f43a0901e692d754885e89293807762d3d80", + strip_prefix = "rules_cc-0.0.13", + urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.0.13/rules_cc-0.0.13.tar.gz"], +) + local_repository( name = "rules_python", path = "../../..", diff --git a/tests/integration/ignore_root_user_error/WORKSPACE b/tests/integration/ignore_root_user_error/WORKSPACE index c21b01e1bc..a1aa5b5009 100644 --- a/tests/integration/ignore_root_user_error/WORKSPACE +++ b/tests/integration/ignore_root_user_error/WORKSPACE @@ -1,3 +1,19 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "protobuf", + sha256 = "da288bf1daa6c04d03a9051781caa52aceb9163586bff9aa6cfb12f69b9395aa", + strip_prefix = "protobuf-27.0", + url = "https://github.com/protocolbuffers/protobuf/releases/download/v27.0/protobuf-27.0.tar.gz", +) + +http_archive( + name = "rules_cc", + sha256 = "d9bdd3ec66b6871456ec9c965809f43a0901e692d754885e89293807762d3d80", + strip_prefix = "rules_cc-0.0.13", + urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.0.13/rules_cc-0.0.13.tar.gz"], +) + local_repository( name = "rules_python", path = "../../..", @@ -13,8 +29,6 @@ python_register_toolchains( python_version = "3.9", ) -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - http_archive( name = "bazel_skylib", sha256 = "c6966ec828da198c5d9adbaa94c05e3a1c7f21bd012a0b29ba8ddbccb2c93b0d", diff --git a/tests/integration/pip_parse/WORKSPACE b/tests/integration/pip_parse/WORKSPACE index db0cd0c7c8..e22fbedca2 100644 --- a/tests/integration/pip_parse/WORKSPACE +++ b/tests/integration/pip_parse/WORKSPACE @@ -1,3 +1,19 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "protobuf", + sha256 = "da288bf1daa6c04d03a9051781caa52aceb9163586bff9aa6cfb12f69b9395aa", + strip_prefix = "protobuf-27.0", + url = "https://github.com/protocolbuffers/protobuf/releases/download/v27.0/protobuf-27.0.tar.gz", +) + +http_archive( + name = "rules_cc", + sha256 = "d9bdd3ec66b6871456ec9c965809f43a0901e692d754885e89293807762d3d80", + strip_prefix = "rules_cc-0.0.13", + urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.0.13/rules_cc-0.0.13.tar.gz"], +) + local_repository( name = "rules_python", path = "../../..", diff --git a/tests/integration/py_cc_toolchain_registered/WORKSPACE b/tests/integration/py_cc_toolchain_registered/WORKSPACE index de908549c0..b9d9f0f560 100644 --- a/tests/integration/py_cc_toolchain_registered/WORKSPACE +++ b/tests/integration/py_cc_toolchain_registered/WORKSPACE @@ -1,3 +1,19 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "protobuf", + sha256 = "da288bf1daa6c04d03a9051781caa52aceb9163586bff9aa6cfb12f69b9395aa", + strip_prefix = "protobuf-27.0", + url = "https://github.com/protocolbuffers/protobuf/releases/download/v27.0/protobuf-27.0.tar.gz", +) + +http_archive( + name = "rules_cc", + sha256 = "d9bdd3ec66b6871456ec9c965809f43a0901e692d754885e89293807762d3d80", + strip_prefix = "rules_cc-0.0.13", + urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.0.13/rules_cc-0.0.13.tar.gz"], +) + local_repository( name = "rules_python", path = "../../..",