refactor: move python/repositories.bzl implementation under python/private (#2083)
This is mostly for consistency with other code, but in doing so, it
looks like there
are several symbols that have public visibility that _probably_ weren't
meant to be
public. Cleaning that up is left for another PR in the future.
diff --git a/python/BUILD.bazel b/python/BUILD.bazel
index e83b79c..878d20b 100644
--- a/python/BUILD.bazel
+++ b/python/BUILD.bazel
@@ -211,16 +211,7 @@
name = "repositories_bzl",
srcs = ["repositories.bzl"],
deps = [
- ":versions_bzl",
- "//python/private:auth_bzl",
- "//python/private:bazel_tools_bzl",
- "//python/private:bzlmod_enabled_bzl",
- "//python/private:coverage_deps_bzl",
- "//python/private:full_version_bzl",
- "//python/private:internal_config_repo_bzl",
- "//python/private:repo_utils_bzl",
- "//python/private:toolchains_repo_bzl",
- "//python/private/pypi:deps_bzl",
+ "//python/private:python_repositories_bzl",
],
)
diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel
index c5da4e3..3b97a02 100644
--- a/python/private/BUILD.bazel
+++ b/python/private/BUILD.bazel
@@ -140,6 +140,23 @@
)
bzl_library(
+ name = "python_repositories_bzl",
+ srcs = ["python_repositories.bzl"],
+ deps = [
+ "//python:versions_bzl",
+ "//python/private:auth_bzl",
+ "//python/private:bazel_tools_bzl",
+ "//python/private:bzlmod_enabled_bzl",
+ "//python/private:coverage_deps_bzl",
+ "//python/private:full_version_bzl",
+ "//python/private:internal_config_repo_bzl",
+ "//python/private:repo_utils_bzl",
+ "//python/private:toolchains_repo_bzl",
+ "//python/private/pypi:deps_bzl",
+ ],
+)
+
+bzl_library(
name = "pythons_hub_bzl",
srcs = ["pythons_hub.bzl"],
deps = [
diff --git a/python/private/pypi/whl_library.bzl b/python/private/pypi/whl_library.bzl
index c4e1f76..777aadd 100644
--- a/python/private/pypi/whl_library.bzl
+++ b/python/private/pypi/whl_library.bzl
@@ -14,9 +14,9 @@
""
-load("//python:repositories.bzl", "is_standalone_interpreter")
load("//python/private:auth.bzl", "AUTH_ATTRS", "get_auth")
load("//python/private:envsubst.bzl", "envsubst")
+load("//python/private:python_repositories.bzl", "is_standalone_interpreter")
load("//python/private:repo_utils.bzl", "REPO_DEBUG_ENV_VAR", "repo_utils")
load(":attrs.bzl", "ATTRS", "use_isolated")
load(":deps.bzl", "all_repo_names")
diff --git a/python/private/python_repositories.bzl b/python/private/python_repositories.bzl
new file mode 100644
index 0000000..ea3dd35
--- /dev/null
+++ b/python/private/python_repositories.bzl
@@ -0,0 +1,733 @@
+# Copyright 2022 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""This file contains macros to be called during WORKSPACE evaluation.
+
+For historic reasons, pip_repositories() is defined in //python:pip.bzl.
+"""
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", _http_archive = "http_archive")
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+load(
+ "//python:versions.bzl",
+ "DEFAULT_RELEASE_BASE_URL",
+ "PLATFORMS",
+ "TOOL_VERSIONS",
+ "get_release_info",
+)
+load("//python/private/pypi:deps.bzl", "pypi_deps")
+load(":auth.bzl", "get_auth")
+load(":bzlmod_enabled.bzl", "BZLMOD_ENABLED")
+load(":coverage_deps.bzl", "coverage_dep")
+load(":full_version.bzl", "full_version")
+load(":internal_config_repo.bzl", "internal_config_repo")
+load(":repo_utils.bzl", "REPO_DEBUG_ENV_VAR", "repo_utils")
+load(
+ ":toolchains_repo.bzl",
+ "host_toolchain",
+ "multi_toolchain_aliases",
+ "toolchain_aliases",
+ "toolchains_repo",
+)
+
+def http_archive(**kwargs):
+ maybe(_http_archive, **kwargs)
+
+def py_repositories():
+ """Runtime dependencies that users must install.
+
+ This function should be loaded and called in the user's WORKSPACE.
+ With bzlmod enabled, this function is not needed since MODULE.bazel handles transitive deps.
+ """
+ maybe(
+ internal_config_repo,
+ name = "rules_python_internal",
+ )
+ http_archive(
+ name = "bazel_skylib",
+ sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
+ "https://github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
+ ],
+ )
+ http_archive(
+ name = "rules_cc",
+ urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.0.9/rules_cc-0.0.9.tar.gz"],
+ sha256 = "2037875b9a4456dce4a79d112a8ae885bbc4aad968e6587dca6e64f3a0900cdf",
+ strip_prefix = "rules_cc-0.0.9",
+ )
+ pypi_deps()
+
+########
+# Remaining content of the file is only used to support toolchains.
+########
+
+STANDALONE_INTERPRETER_FILENAME = "STANDALONE_INTERPRETER"
+
+def is_standalone_interpreter(rctx, python_interpreter_path, *, logger = None):
+ """Query a python interpreter target for whether or not it's a rules_rust provided toolchain
+
+ Args:
+ rctx (repository_ctx): The repository rule's context object.
+ python_interpreter_path (path): A path representing the interpreter.
+ logger: Optional logger to use for operations.
+
+ Returns:
+ bool: Whether or not the target is from a rules_python generated toolchain.
+ """
+
+ # Only update the location when using a hermetic toolchain.
+ if not python_interpreter_path:
+ return False
+
+ # This is a rules_python provided toolchain.
+ return repo_utils.execute_unchecked(
+ rctx,
+ op = "IsStandaloneInterpreter",
+ arguments = [
+ "ls",
+ "{}/{}".format(
+ python_interpreter_path.dirname,
+ STANDALONE_INTERPRETER_FILENAME,
+ ),
+ ],
+ logger = logger,
+ ).return_code == 0
+
+def _python_repository_impl(rctx):
+ if rctx.attr.distutils and rctx.attr.distutils_content:
+ fail("Only one of (distutils, distutils_content) should be set.")
+ if bool(rctx.attr.url) == bool(rctx.attr.urls):
+ fail("Exactly one of (url, urls) must be set.")
+
+ logger = repo_utils.logger(rctx)
+
+ platform = rctx.attr.platform
+ python_version = rctx.attr.python_version
+ python_version_info = python_version.split(".")
+ python_short_version = "{0}.{1}".format(*python_version_info)
+ release_filename = rctx.attr.release_filename
+ urls = rctx.attr.urls or [rctx.attr.url]
+ auth = get_auth(rctx, urls)
+
+ if release_filename.endswith(".zst"):
+ rctx.download(
+ url = urls,
+ sha256 = rctx.attr.sha256,
+ output = release_filename,
+ auth = auth,
+ )
+ unzstd = rctx.which("unzstd")
+ if not unzstd:
+ url = rctx.attr.zstd_url.format(version = rctx.attr.zstd_version)
+ rctx.download_and_extract(
+ url = url,
+ sha256 = rctx.attr.zstd_sha256,
+ auth = auth,
+ )
+ working_directory = "zstd-{version}".format(version = rctx.attr.zstd_version)
+
+ repo_utils.execute_checked(
+ rctx,
+ op = "python_repository.MakeZstd",
+ arguments = [
+ repo_utils.which_checked(rctx, "make"),
+ "--jobs=4",
+ ],
+ timeout = 600,
+ quiet = True,
+ working_directory = working_directory,
+ logger = logger,
+ )
+ zstd = "{working_directory}/zstd".format(working_directory = working_directory)
+ unzstd = "./unzstd"
+ rctx.symlink(zstd, unzstd)
+
+ repo_utils.execute_checked(
+ rctx,
+ op = "python_repository.ExtractRuntime",
+ arguments = [
+ repo_utils.which_checked(rctx, "tar"),
+ "--extract",
+ "--strip-components=2",
+ "--use-compress-program={unzstd}".format(unzstd = unzstd),
+ "--file={}".format(release_filename),
+ ],
+ logger = logger,
+ )
+ else:
+ rctx.download_and_extract(
+ url = urls,
+ sha256 = rctx.attr.sha256,
+ stripPrefix = rctx.attr.strip_prefix,
+ auth = auth,
+ )
+
+ patches = rctx.attr.patches
+ if patches:
+ for patch in patches:
+ # Should take the strip as an attr, but this is fine for the moment
+ rctx.patch(patch, strip = 1)
+
+ # Write distutils.cfg to the Python installation.
+ if "windows" in platform:
+ distutils_path = "Lib/distutils/distutils.cfg"
+ else:
+ distutils_path = "lib/python{}/distutils/distutils.cfg".format(python_short_version)
+ if rctx.attr.distutils:
+ rctx.file(distutils_path, rctx.read(rctx.attr.distutils))
+ elif rctx.attr.distutils_content:
+ rctx.file(distutils_path, rctx.attr.distutils_content)
+
+ # Make the Python installation read-only. This is to prevent issues due to
+ # pycs being generated at runtime:
+ # * The pycs are not deterministic (they contain timestamps)
+ # * Multiple processes trying to write the same pycs can result in errors.
+ if not rctx.attr.ignore_root_user_error:
+ if "windows" not in platform:
+ lib_dir = "lib" if "windows" not in platform else "Lib"
+
+ repo_utils.execute_checked(
+ rctx,
+ op = "python_repository.MakeReadOnly",
+ arguments = [repo_utils.which_checked(rctx, "chmod"), "-R", "ugo-w", lib_dir],
+ logger = logger,
+ )
+ exec_result = repo_utils.execute_unchecked(
+ rctx,
+ op = "python_repository.TestReadOnly",
+ arguments = [repo_utils.which_checked(rctx, "touch"), "{}/.test".format(lib_dir)],
+ logger = logger,
+ )
+
+ # The issue with running as root is the installation is no longer
+ # read-only, so the problems due to pyc can resurface.
+ if exec_result.return_code == 0:
+ stdout = repo_utils.execute_checked_stdout(
+ rctx,
+ op = "python_repository.GetUserId",
+ arguments = [repo_utils.which_checked(rctx, "id"), "-u"],
+ logger = logger,
+ )
+ uid = int(stdout.strip())
+ if uid == 0:
+ fail("The current user is root, please run as non-root when using the hermetic Python interpreter. See https://github.com/bazelbuild/rules_python/pull/713.")
+ else:
+ fail("The current user has CAP_DAC_OVERRIDE set, please drop this capability when using the hermetic Python interpreter. See https://github.com/bazelbuild/rules_python/pull/713.")
+
+ python_bin = "python.exe" if ("windows" in platform) else "bin/python3"
+
+ glob_include = []
+ glob_exclude = [
+ "**/* *", # Bazel does not support spaces in file names.
+ # Unused shared libraries. `python` executable and the `:libpython` target
+ # depend on `libpython{python_version}.so.1.0`.
+ "lib/libpython{python_version}.so".format(python_version = python_short_version),
+ # static libraries
+ "lib/**/*.a",
+ # tests for the standard libraries.
+ "lib/python{python_version}/**/test/**".format(python_version = python_short_version),
+ "lib/python{python_version}/**/tests/**".format(python_version = python_short_version),
+ "**/__pycache__/*.pyc.*", # During pyc creation, temp files named *.pyc.NNN are created
+ ]
+
+ if "linux" in platform:
+ # Workaround around https://github.com/indygreg/python-build-standalone/issues/231
+ for url in urls:
+ head_and_release, _, _ = url.rpartition("/")
+ _, _, release = head_and_release.rpartition("/")
+ if not release.isdigit():
+ # Maybe this is some custom toolchain, so skip this
+ break
+
+ if int(release) >= 20240224:
+ # Starting with this release the Linux toolchains have infinite symlink loop
+ # on host platforms that are not Linux. Delete the files no
+ # matter the host platform so that the cross-built artifacts
+ # are the same irrespective of the host platform we are
+ # building on.
+ #
+ # Link to the first affected release:
+ # https://github.com/indygreg/python-build-standalone/releases/tag/20240224
+ rctx.delete("share/terminfo")
+ break
+
+ if rctx.attr.ignore_root_user_error or "windows" in platform:
+ glob_exclude += [
+ # These pycache files are created on first use of the associated python files.
+ # Exclude them from the glob because otherwise between the first time and second time a python toolchain is used,"
+ # the definition of this filegroup will change, and depending rules will get invalidated."
+ # See https://github.com/bazelbuild/rules_python/issues/1008 for unconditionally adding these to toolchains so we can stop ignoring them."
+ "**/__pycache__/*.pyc",
+ "**/__pycache__/*.pyo",
+ ]
+
+ if "windows" in platform:
+ glob_include += [
+ "*.exe",
+ "*.dll",
+ "bin/**",
+ "DLLs/**",
+ "extensions/**",
+ "include/**",
+ "Lib/**",
+ "libs/**",
+ "Scripts/**",
+ "share/**",
+ "tcl/**",
+ ]
+ else:
+ glob_include += [
+ "bin/**",
+ "extensions/**",
+ "include/**",
+ "lib/**",
+ "libs/**",
+ "share/**",
+ ]
+
+ if rctx.attr.coverage_tool:
+ if "windows" in platform:
+ coverage_tool = None
+ else:
+ coverage_tool = '"{}"'.format(rctx.attr.coverage_tool)
+
+ coverage_attr_text = """\
+ coverage_tool = select({{
+ ":coverage_enabled": {coverage_tool},
+ "//conditions:default": None
+ }}),
+""".format(coverage_tool = coverage_tool)
+ else:
+ coverage_attr_text = " # coverage_tool attribute not supported by this Bazel version"
+
+ build_content = """\
+# Generated by python/repositories.bzl
+
+load("@rules_python//python:py_runtime.bzl", "py_runtime")
+load("@rules_python//python:py_runtime_pair.bzl", "py_runtime_pair")
+load("@rules_python//python/cc:py_cc_toolchain.bzl", "py_cc_toolchain")
+load("@rules_python//python/private:py_exec_tools_toolchain.bzl", "py_exec_tools_toolchain")
+
+package(default_visibility = ["//visibility:public"])
+
+filegroup(
+ name = "files",
+ srcs = glob(
+ include = {glob_include},
+ # Platform-agnostic filegroup can't match on all patterns.
+ allow_empty = True,
+ exclude = {glob_exclude},
+ ),
+)
+
+cc_import(
+ name = "interface",
+ interface_library = "libs/python{python_version_nodot}.lib",
+ system_provided = True,
+)
+
+filegroup(
+ name = "includes",
+ srcs = glob(["include/**/*.h"]),
+)
+
+cc_library(
+ name = "python_headers",
+ deps = select({{
+ "@bazel_tools//src/conditions:windows": [":interface"],
+ "//conditions:default": None,
+ }}),
+ hdrs = [":includes"],
+ includes = [
+ "include",
+ "include/python{python_version}",
+ "include/python{python_version}m",
+ ],
+)
+
+cc_library(
+ name = "libpython",
+ hdrs = [":includes"],
+ srcs = select({{
+ "@platforms//os:windows": ["python3.dll", "libs/python{python_version_nodot}.lib"],
+ "@platforms//os:macos": ["lib/libpython{python_version}.dylib"],
+ "@platforms//os:linux": ["lib/libpython{python_version}.so", "lib/libpython{python_version}.so.1.0"],
+ }}),
+)
+
+exports_files(["python", "{python_path}"])
+
+# Used to only download coverage toolchain when the coverage is collected by
+# bazel.
+config_setting(
+ name = "coverage_enabled",
+ values = {{"collect_code_coverage": "true"}},
+ visibility = ["//visibility:private"],
+)
+
+py_runtime(
+ name = "py3_runtime",
+ files = [":files"],
+{coverage_attr}
+ interpreter = "{python_path}",
+ interpreter_version_info = {{
+ "major": "{interpreter_version_info_major}",
+ "minor": "{interpreter_version_info_minor}",
+ "micro": "{interpreter_version_info_micro}",
+ }},
+ python_version = "PY3",
+ implementation_name = 'cpython',
+ pyc_tag = "cpython-{interpreter_version_info_major}{interpreter_version_info_minor}",
+)
+
+py_runtime_pair(
+ name = "python_runtimes",
+ py2_runtime = None,
+ py3_runtime = ":py3_runtime",
+)
+
+py_cc_toolchain(
+ name = "py_cc_toolchain",
+ headers = ":python_headers",
+ libs = ":libpython",
+ python_version = "{python_version}",
+)
+
+py_exec_tools_toolchain(
+ name = "py_exec_tools_toolchain",
+ precompiler = "@rules_python//tools/precompiler:precompiler",
+)
+""".format(
+ glob_exclude = repr(glob_exclude),
+ glob_include = repr(glob_include),
+ python_path = python_bin,
+ python_version = python_short_version,
+ python_version_nodot = python_short_version.replace(".", ""),
+ coverage_attr = coverage_attr_text,
+ interpreter_version_info_major = python_version_info[0],
+ interpreter_version_info_minor = python_version_info[1],
+ interpreter_version_info_micro = python_version_info[2],
+ )
+ rctx.delete("python")
+ rctx.symlink(python_bin, "python")
+ rctx.file(STANDALONE_INTERPRETER_FILENAME, "# File intentionally left blank. Indicates that this is an interpreter repo created by rules_python.")
+ rctx.file("BUILD.bazel", build_content)
+
+ attrs = {
+ "auth_patterns": rctx.attr.auth_patterns,
+ "coverage_tool": rctx.attr.coverage_tool,
+ "distutils": rctx.attr.distutils,
+ "distutils_content": rctx.attr.distutils_content,
+ "ignore_root_user_error": rctx.attr.ignore_root_user_error,
+ "name": rctx.attr.name,
+ "netrc": rctx.attr.netrc,
+ "patches": rctx.attr.patches,
+ "platform": platform,
+ "python_version": python_version,
+ "release_filename": release_filename,
+ "sha256": rctx.attr.sha256,
+ "strip_prefix": rctx.attr.strip_prefix,
+ }
+
+ if rctx.attr.url:
+ attrs["url"] = rctx.attr.url
+ else:
+ attrs["urls"] = urls
+
+ return attrs
+
+python_repository = repository_rule(
+ _python_repository_impl,
+ doc = "Fetches the external tools needed for the Python toolchain.",
+ attrs = {
+ "auth_patterns": attr.string_dict(
+ doc = "Override mapping of hostnames to authorization patterns; mirrors the eponymous attribute from http_archive",
+ ),
+ "coverage_tool": attr.string(
+ # Mirrors the definition at
+ # https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/python/py_runtime_rule.bzl
+ doc = """
+This is a target to use for collecting code coverage information from `py_binary`
+and `py_test` targets.
+
+If set, the target must either produce a single file or be an executable target.
+The path to the single file, or the executable if the target is executable,
+determines the entry point for the python coverage tool. The target and its
+runfiles will be added to the runfiles when coverage is enabled.
+
+The entry point for the tool must be loadable by a Python interpreter (e.g. a
+`.py` or `.pyc` file). It must accept the command line arguments
+of coverage.py (https://coverage.readthedocs.io), at least including
+the `run` and `lcov` subcommands.
+
+The target is accepted as a string by the python_repository and evaluated within
+the context of the toolchain repository.
+
+For more information see the official bazel docs
+(https://bazel.build/reference/be/python#py_runtime.coverage_tool).
+""",
+ ),
+ "distutils": attr.label(
+ allow_single_file = True,
+ doc = "A distutils.cfg file to be included in the Python installation. " +
+ "Either distutils or distutils_content can be specified, but not both.",
+ mandatory = False,
+ ),
+ "distutils_content": attr.string(
+ doc = "A distutils.cfg file content to be included in the Python installation. " +
+ "Either distutils or distutils_content can be specified, but not both.",
+ mandatory = False,
+ ),
+ "ignore_root_user_error": attr.bool(
+ default = False,
+ doc = "Whether the check for root should be ignored or not. This causes cache misses with .pyc files.",
+ mandatory = False,
+ ),
+ "netrc": attr.string(
+ doc = ".netrc file to use for authentication; mirrors the eponymous attribute from http_archive",
+ ),
+ "patches": attr.label_list(
+ doc = "A list of patch files to apply to the unpacked interpreter",
+ mandatory = False,
+ ),
+ "platform": attr.string(
+ doc = "The platform name for the Python interpreter tarball.",
+ mandatory = True,
+ values = PLATFORMS.keys(),
+ ),
+ "python_version": attr.string(
+ doc = "The Python version.",
+ mandatory = True,
+ ),
+ "release_filename": attr.string(
+ doc = "The filename of the interpreter to be downloaded",
+ mandatory = True,
+ ),
+ "sha256": attr.string(
+ doc = "The SHA256 integrity hash for the Python interpreter tarball.",
+ mandatory = True,
+ ),
+ "strip_prefix": attr.string(
+ doc = "A directory prefix to strip from the extracted files.",
+ ),
+ "url": attr.string(
+ doc = "The URL of the interpreter to download. Exactly one of url and urls must be set.",
+ ),
+ "urls": attr.string_list(
+ doc = "The URL of the interpreter to download. Exactly one of url and urls must be set.",
+ ),
+ "zstd_sha256": attr.string(
+ default = "7c42d56fac126929a6a85dbc73ff1db2411d04f104fae9bdea51305663a83fd0",
+ ),
+ "zstd_url": attr.string(
+ default = "https://github.com/facebook/zstd/releases/download/v{version}/zstd-{version}.tar.gz",
+ ),
+ "zstd_version": attr.string(
+ default = "1.5.2",
+ ),
+ "_rule_name": attr.string(default = "python_repository"),
+ },
+ environ = [REPO_DEBUG_ENV_VAR],
+)
+
+# Wrapper macro around everything above, this is the primary API.
+def python_register_toolchains(
+ name,
+ python_version,
+ distutils = None,
+ distutils_content = None,
+ register_toolchains = True,
+ register_coverage_tool = False,
+ set_python_version_constraint = False,
+ tool_versions = TOOL_VERSIONS,
+ **kwargs):
+ """Convenience macro for users which does typical setup.
+
+ - Create a repository for each built-in platform like "python_linux_amd64" -
+ this repository is lazily fetched when Python is needed for that platform.
+ - Create a repository exposing toolchains for each platform like
+ "python_platforms".
+ - Register a toolchain pointing at each platform.
+ Users can avoid this macro and do these steps themselves, if they want more
+ control.
+ Args:
+ name: base name for all created repos, like "python38".
+ python_version: the Python version.
+ distutils: see the distutils attribute in the python_repository repository rule.
+ distutils_content: see the distutils_content attribute in the python_repository repository rule.
+ register_toolchains: Whether or not to register the downloaded toolchains.
+ register_coverage_tool: Whether or not to register the downloaded coverage tool to the toolchains.
+ NOTE: Coverage support using the toolchain is only supported in Bazel 6 and higher.
+
+ set_python_version_constraint: When set to true, target_compatible_with for the toolchains will include a version constraint.
+ tool_versions: a dict containing a mapping of version with SHASUM and platform info. If not supplied, the defaults
+ in python/versions.bzl will be used.
+ **kwargs: passed to each python_repositories call.
+ """
+
+ if BZLMOD_ENABLED:
+ # you cannot used native.register_toolchains when using bzlmod.
+ register_toolchains = False
+
+ base_url = kwargs.pop("base_url", DEFAULT_RELEASE_BASE_URL)
+
+ python_version = full_version(python_version)
+
+ toolchain_repo_name = "{name}_toolchains".format(name = name)
+
+ # When using unreleased Bazel versions, the version is an empty string
+ if native.bazel_version:
+ bazel_major = int(native.bazel_version.split(".")[0])
+ if bazel_major < 6:
+ if register_coverage_tool:
+ # buildifier: disable=print
+ print((
+ "WARNING: ignoring register_coverage_tool=True when " +
+ "registering @{name}: Bazel 6+ required, got {version}"
+ ).format(
+ name = name,
+ version = native.bazel_version,
+ ))
+ register_coverage_tool = False
+
+ loaded_platforms = []
+ for platform in PLATFORMS.keys():
+ sha256 = tool_versions[python_version]["sha256"].get(platform, None)
+ if not sha256:
+ continue
+
+ loaded_platforms.append(platform)
+ (release_filename, urls, strip_prefix, patches) = get_release_info(platform, python_version, base_url, tool_versions)
+
+ # allow passing in a tool version
+ coverage_tool = None
+ coverage_tool = tool_versions[python_version].get("coverage_tool", {}).get(platform, None)
+ if register_coverage_tool and coverage_tool == None:
+ coverage_tool = coverage_dep(
+ name = "{name}_{platform}_coverage".format(
+ name = name,
+ platform = platform,
+ ),
+ python_version = python_version,
+ platform = platform,
+ visibility = ["@{name}_{platform}//:__subpackages__".format(
+ name = name,
+ platform = platform,
+ )],
+ )
+
+ python_repository(
+ name = "{name}_{platform}".format(
+ name = name,
+ platform = platform,
+ ),
+ sha256 = sha256,
+ patches = patches,
+ platform = platform,
+ python_version = python_version,
+ release_filename = release_filename,
+ urls = urls,
+ distutils = distutils,
+ distutils_content = distutils_content,
+ strip_prefix = strip_prefix,
+ coverage_tool = coverage_tool,
+ **kwargs
+ )
+ if register_toolchains:
+ native.register_toolchains("@{toolchain_repo_name}//:{platform}_toolchain".format(
+ toolchain_repo_name = toolchain_repo_name,
+ platform = platform,
+ ))
+ native.register_toolchains("@{toolchain_repo_name}//:{platform}_py_cc_toolchain".format(
+ toolchain_repo_name = toolchain_repo_name,
+ platform = platform,
+ ))
+ native.register_toolchains("@{toolchain_repo_name}//:{platform}_py_exec_tools_toolchain".format(
+ toolchain_repo_name = toolchain_repo_name,
+ platform = platform,
+ ))
+
+ host_toolchain(
+ name = name + "_host",
+ python_version = python_version,
+ user_repository_name = name,
+ platforms = loaded_platforms,
+ )
+
+ toolchain_aliases(
+ name = name,
+ python_version = python_version,
+ user_repository_name = name,
+ platforms = loaded_platforms,
+ )
+
+ # in bzlmod we write out our own toolchain repos
+ if BZLMOD_ENABLED:
+ return
+
+ toolchains_repo(
+ name = toolchain_repo_name,
+ python_version = python_version,
+ set_python_version_constraint = set_python_version_constraint,
+ user_repository_name = name,
+ )
+
+def python_register_multi_toolchains(
+ name,
+ python_versions,
+ default_version = None,
+ **kwargs):
+ """Convenience macro for registering multiple Python toolchains.
+
+ Args:
+ name: base name for each name in python_register_toolchains call.
+ python_versions: the Python version.
+ default_version: the default Python version. If not set, the first version in
+ python_versions is used.
+ **kwargs: passed to each python_register_toolchains call.
+ """
+ if len(python_versions) == 0:
+ fail("python_versions must not be empty")
+
+ if not default_version:
+ default_version = python_versions.pop(0)
+ for python_version in python_versions:
+ if python_version == default_version:
+ # We register the default version lastly so that it's not picked first when --platforms
+ # is set with a constraint during toolchain resolution. This is due to the fact that
+ # Bazel will match the unconstrained toolchain if we register it before the constrained
+ # ones.
+ continue
+ python_register_toolchains(
+ name = name + "_" + python_version.replace(".", "_"),
+ python_version = python_version,
+ set_python_version_constraint = True,
+ **kwargs
+ )
+ python_register_toolchains(
+ name = name + "_" + default_version.replace(".", "_"),
+ python_version = default_version,
+ set_python_version_constraint = False,
+ **kwargs
+ )
+
+ multi_toolchain_aliases(
+ name = name,
+ python_versions = {
+ python_version: name + "_" + python_version.replace(".", "_")
+ for python_version in (python_versions + [default_version])
+ },
+ )
diff --git a/python/repositories.bzl b/python/repositories.bzl
index baa5f5b..cf87234 100644
--- a/python/repositories.bzl
+++ b/python/repositories.bzl
@@ -1,4 +1,4 @@
-# Copyright 2022 The Bazel Authors. All rights reserved.
+# Copyright 2024 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,721 +13,26 @@
# limitations under the License.
"""This file contains macros to be called during WORKSPACE evaluation.
-
-For historic reasons, pip_repositories() is defined in //python:pip.bzl.
"""
-load("@bazel_tools//tools/build_defs/repo:http.bzl", _http_archive = "http_archive")
-load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
-load("//python/private:auth.bzl", "get_auth")
-load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
-load("//python/private:coverage_deps.bzl", "coverage_dep")
-load("//python/private:full_version.bzl", "full_version")
-load("//python/private:internal_config_repo.bzl", "internal_config_repo")
-load("//python/private:repo_utils.bzl", "REPO_DEBUG_ENV_VAR", "repo_utils")
load(
- "//python/private:toolchains_repo.bzl",
- "host_toolchain",
- "multi_toolchain_aliases",
- "toolchain_aliases",
- "toolchains_repo",
-)
-load("//python/private/pypi:deps.bzl", "pypi_deps")
-load(
- ":versions.bzl",
- "DEFAULT_RELEASE_BASE_URL",
- "PLATFORMS",
- "TOOL_VERSIONS",
- "get_release_info",
+ "//python/private:python_repositories.bzl",
+ _STANDALONE_INTERPRETER_FILENAME = "STANDALONE_INTERPRETER_FILENAME",
+ _http_archive = "http_archive",
+ _is_standalone_interpreter = "is_standalone_interpreter",
+ _py_repositories = "py_repositories",
+ _python_register_multi_toolchains = "python_register_multi_toolchains",
+ _python_register_toolchains = "python_register_toolchains",
+ _python_repository = "python_repository",
)
-def http_archive(**kwargs):
- maybe(_http_archive, **kwargs)
+py_repositories = _py_repositories
+python_register_multi_toolchains = _python_register_multi_toolchains
+python_register_toolchains = _python_register_toolchains
-def py_repositories():
- """Runtime dependencies that users must install.
-
- This function should be loaded and called in the user's WORKSPACE.
- With bzlmod enabled, this function is not needed since MODULE.bazel handles transitive deps.
- """
- maybe(
- internal_config_repo,
- name = "rules_python_internal",
- )
- http_archive(
- name = "bazel_skylib",
- sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506",
- urls = [
- "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
- "https://github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
- ],
- )
- http_archive(
- name = "rules_cc",
- urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.0.9/rules_cc-0.0.9.tar.gz"],
- sha256 = "2037875b9a4456dce4a79d112a8ae885bbc4aad968e6587dca6e64f3a0900cdf",
- strip_prefix = "rules_cc-0.0.9",
- )
- pypi_deps()
-
-########
-# Remaining content of the file is only used to support toolchains.
-########
-
-STANDALONE_INTERPRETER_FILENAME = "STANDALONE_INTERPRETER"
-
-def is_standalone_interpreter(rctx, python_interpreter_path, *, logger = None):
- """Query a python interpreter target for whether or not it's a rules_rust provided toolchain
-
- Args:
- rctx (repository_ctx): The repository rule's context object.
- python_interpreter_path (path): A path representing the interpreter.
- logger: Optional logger to use for operations.
-
- Returns:
- bool: Whether or not the target is from a rules_python generated toolchain.
- """
-
- # Only update the location when using a hermetic toolchain.
- if not python_interpreter_path:
- return False
-
- # This is a rules_python provided toolchain.
- return repo_utils.execute_unchecked(
- rctx,
- op = "IsStandaloneInterpreter",
- arguments = [
- "ls",
- "{}/{}".format(
- python_interpreter_path.dirname,
- STANDALONE_INTERPRETER_FILENAME,
- ),
- ],
- logger = logger,
- ).return_code == 0
-
-def _python_repository_impl(rctx):
- if rctx.attr.distutils and rctx.attr.distutils_content:
- fail("Only one of (distutils, distutils_content) should be set.")
- if bool(rctx.attr.url) == bool(rctx.attr.urls):
- fail("Exactly one of (url, urls) must be set.")
-
- logger = repo_utils.logger(rctx)
-
- platform = rctx.attr.platform
- python_version = rctx.attr.python_version
- python_version_info = python_version.split(".")
- python_short_version = "{0}.{1}".format(*python_version_info)
- release_filename = rctx.attr.release_filename
- urls = rctx.attr.urls or [rctx.attr.url]
- auth = get_auth(rctx, urls)
-
- if release_filename.endswith(".zst"):
- rctx.download(
- url = urls,
- sha256 = rctx.attr.sha256,
- output = release_filename,
- auth = auth,
- )
- unzstd = rctx.which("unzstd")
- if not unzstd:
- url = rctx.attr.zstd_url.format(version = rctx.attr.zstd_version)
- rctx.download_and_extract(
- url = url,
- sha256 = rctx.attr.zstd_sha256,
- auth = auth,
- )
- working_directory = "zstd-{version}".format(version = rctx.attr.zstd_version)
-
- repo_utils.execute_checked(
- rctx,
- op = "python_repository.MakeZstd",
- arguments = [
- repo_utils.which_checked(rctx, "make"),
- "--jobs=4",
- ],
- timeout = 600,
- quiet = True,
- working_directory = working_directory,
- logger = logger,
- )
- zstd = "{working_directory}/zstd".format(working_directory = working_directory)
- unzstd = "./unzstd"
- rctx.symlink(zstd, unzstd)
-
- repo_utils.execute_checked(
- rctx,
- op = "python_repository.ExtractRuntime",
- arguments = [
- repo_utils.which_checked(rctx, "tar"),
- "--extract",
- "--strip-components=2",
- "--use-compress-program={unzstd}".format(unzstd = unzstd),
- "--file={}".format(release_filename),
- ],
- logger = logger,
- )
- else:
- rctx.download_and_extract(
- url = urls,
- sha256 = rctx.attr.sha256,
- stripPrefix = rctx.attr.strip_prefix,
- auth = auth,
- )
-
- patches = rctx.attr.patches
- if patches:
- for patch in patches:
- # Should take the strip as an attr, but this is fine for the moment
- rctx.patch(patch, strip = 1)
-
- # Write distutils.cfg to the Python installation.
- if "windows" in platform:
- distutils_path = "Lib/distutils/distutils.cfg"
- else:
- distutils_path = "lib/python{}/distutils/distutils.cfg".format(python_short_version)
- if rctx.attr.distutils:
- rctx.file(distutils_path, rctx.read(rctx.attr.distutils))
- elif rctx.attr.distutils_content:
- rctx.file(distutils_path, rctx.attr.distutils_content)
-
- # Make the Python installation read-only. This is to prevent issues due to
- # pycs being generated at runtime:
- # * The pycs are not deterministic (they contain timestamps)
- # * Multiple processes trying to write the same pycs can result in errors.
- if not rctx.attr.ignore_root_user_error:
- if "windows" not in platform:
- lib_dir = "lib" if "windows" not in platform else "Lib"
-
- repo_utils.execute_checked(
- rctx,
- op = "python_repository.MakeReadOnly",
- arguments = [repo_utils.which_checked(rctx, "chmod"), "-R", "ugo-w", lib_dir],
- logger = logger,
- )
- exec_result = repo_utils.execute_unchecked(
- rctx,
- op = "python_repository.TestReadOnly",
- arguments = [repo_utils.which_checked(rctx, "touch"), "{}/.test".format(lib_dir)],
- logger = logger,
- )
-
- # The issue with running as root is the installation is no longer
- # read-only, so the problems due to pyc can resurface.
- if exec_result.return_code == 0:
- stdout = repo_utils.execute_checked_stdout(
- rctx,
- op = "python_repository.GetUserId",
- arguments = [repo_utils.which_checked(rctx, "id"), "-u"],
- logger = logger,
- )
- uid = int(stdout.strip())
- if uid == 0:
- fail("The current user is root, please run as non-root when using the hermetic Python interpreter. See https://github.com/bazelbuild/rules_python/pull/713.")
- else:
- fail("The current user has CAP_DAC_OVERRIDE set, please drop this capability when using the hermetic Python interpreter. See https://github.com/bazelbuild/rules_python/pull/713.")
-
- python_bin = "python.exe" if ("windows" in platform) else "bin/python3"
-
- glob_include = []
- glob_exclude = [
- "**/* *", # Bazel does not support spaces in file names.
- # Unused shared libraries. `python` executable and the `:libpython` target
- # depend on `libpython{python_version}.so.1.0`.
- "lib/libpython{python_version}.so".format(python_version = python_short_version),
- # static libraries
- "lib/**/*.a",
- # tests for the standard libraries.
- "lib/python{python_version}/**/test/**".format(python_version = python_short_version),
- "lib/python{python_version}/**/tests/**".format(python_version = python_short_version),
- "**/__pycache__/*.pyc.*", # During pyc creation, temp files named *.pyc.NNN are created
- ]
-
- if "linux" in platform:
- # Workaround around https://github.com/indygreg/python-build-standalone/issues/231
- for url in urls:
- head_and_release, _, _ = url.rpartition("/")
- _, _, release = head_and_release.rpartition("/")
- if not release.isdigit():
- # Maybe this is some custom toolchain, so skip this
- break
-
- if int(release) >= 20240224:
- # Starting with this release the Linux toolchains have infinite symlink loop
- # on host platforms that are not Linux. Delete the files no
- # matter the host platform so that the cross-built artifacts
- # are the same irrespective of the host platform we are
- # building on.
- #
- # Link to the first affected release:
- # https://github.com/indygreg/python-build-standalone/releases/tag/20240224
- rctx.delete("share/terminfo")
- break
-
- if rctx.attr.ignore_root_user_error or "windows" in platform:
- glob_exclude += [
- # These pycache files are created on first use of the associated python files.
- # Exclude them from the glob because otherwise between the first time and second time a python toolchain is used,"
- # the definition of this filegroup will change, and depending rules will get invalidated."
- # See https://github.com/bazelbuild/rules_python/issues/1008 for unconditionally adding these to toolchains so we can stop ignoring them."
- "**/__pycache__/*.pyc",
- "**/__pycache__/*.pyo",
- ]
-
- if "windows" in platform:
- glob_include += [
- "*.exe",
- "*.dll",
- "bin/**",
- "DLLs/**",
- "extensions/**",
- "include/**",
- "Lib/**",
- "libs/**",
- "Scripts/**",
- "share/**",
- "tcl/**",
- ]
- else:
- glob_include += [
- "bin/**",
- "extensions/**",
- "include/**",
- "lib/**",
- "libs/**",
- "share/**",
- ]
-
- if rctx.attr.coverage_tool:
- if "windows" in platform:
- coverage_tool = None
- else:
- coverage_tool = '"{}"'.format(rctx.attr.coverage_tool)
-
- coverage_attr_text = """\
- coverage_tool = select({{
- ":coverage_enabled": {coverage_tool},
- "//conditions:default": None
- }}),
-""".format(coverage_tool = coverage_tool)
- else:
- coverage_attr_text = " # coverage_tool attribute not supported by this Bazel version"
-
- build_content = """\
-# Generated by python/repositories.bzl
-
-load("@rules_python//python:py_runtime.bzl", "py_runtime")
-load("@rules_python//python:py_runtime_pair.bzl", "py_runtime_pair")
-load("@rules_python//python/cc:py_cc_toolchain.bzl", "py_cc_toolchain")
-load("@rules_python//python/private:py_exec_tools_toolchain.bzl", "py_exec_tools_toolchain")
-
-package(default_visibility = ["//visibility:public"])
-
-filegroup(
- name = "files",
- srcs = glob(
- include = {glob_include},
- # Platform-agnostic filegroup can't match on all patterns.
- allow_empty = True,
- exclude = {glob_exclude},
- ),
-)
-
-cc_import(
- name = "interface",
- interface_library = "libs/python{python_version_nodot}.lib",
- system_provided = True,
-)
-
-filegroup(
- name = "includes",
- srcs = glob(["include/**/*.h"]),
-)
-
-cc_library(
- name = "python_headers",
- deps = select({{
- "@bazel_tools//src/conditions:windows": [":interface"],
- "//conditions:default": None,
- }}),
- hdrs = [":includes"],
- includes = [
- "include",
- "include/python{python_version}",
- "include/python{python_version}m",
- ],
-)
-
-cc_library(
- name = "libpython",
- hdrs = [":includes"],
- srcs = select({{
- "@platforms//os:windows": ["python3.dll", "libs/python{python_version_nodot}.lib"],
- "@platforms//os:macos": ["lib/libpython{python_version}.dylib"],
- "@platforms//os:linux": ["lib/libpython{python_version}.so", "lib/libpython{python_version}.so.1.0"],
- }}),
-)
-
-exports_files(["python", "{python_path}"])
-
-# Used to only download coverage toolchain when the coverage is collected by
-# bazel.
-config_setting(
- name = "coverage_enabled",
- values = {{"collect_code_coverage": "true"}},
- visibility = ["//visibility:private"],
-)
-
-py_runtime(
- name = "py3_runtime",
- files = [":files"],
-{coverage_attr}
- interpreter = "{python_path}",
- interpreter_version_info = {{
- "major": "{interpreter_version_info_major}",
- "minor": "{interpreter_version_info_minor}",
- "micro": "{interpreter_version_info_micro}",
- }},
- python_version = "PY3",
- implementation_name = 'cpython',
- pyc_tag = "cpython-{interpreter_version_info_major}{interpreter_version_info_minor}",
-)
-
-py_runtime_pair(
- name = "python_runtimes",
- py2_runtime = None,
- py3_runtime = ":py3_runtime",
-)
-
-py_cc_toolchain(
- name = "py_cc_toolchain",
- headers = ":python_headers",
- libs = ":libpython",
- python_version = "{python_version}",
-)
-
-py_exec_tools_toolchain(
- name = "py_exec_tools_toolchain",
- precompiler = "@rules_python//tools/precompiler:precompiler",
-)
-""".format(
- glob_exclude = repr(glob_exclude),
- glob_include = repr(glob_include),
- python_path = python_bin,
- python_version = python_short_version,
- python_version_nodot = python_short_version.replace(".", ""),
- coverage_attr = coverage_attr_text,
- interpreter_version_info_major = python_version_info[0],
- interpreter_version_info_minor = python_version_info[1],
- interpreter_version_info_micro = python_version_info[2],
- )
- rctx.delete("python")
- rctx.symlink(python_bin, "python")
- rctx.file(STANDALONE_INTERPRETER_FILENAME, "# File intentionally left blank. Indicates that this is an interpreter repo created by rules_python.")
- rctx.file("BUILD.bazel", build_content)
-
- attrs = {
- "auth_patterns": rctx.attr.auth_patterns,
- "coverage_tool": rctx.attr.coverage_tool,
- "distutils": rctx.attr.distutils,
- "distutils_content": rctx.attr.distutils_content,
- "ignore_root_user_error": rctx.attr.ignore_root_user_error,
- "name": rctx.attr.name,
- "netrc": rctx.attr.netrc,
- "patches": rctx.attr.patches,
- "platform": platform,
- "python_version": python_version,
- "release_filename": release_filename,
- "sha256": rctx.attr.sha256,
- "strip_prefix": rctx.attr.strip_prefix,
- }
-
- if rctx.attr.url:
- attrs["url"] = rctx.attr.url
- else:
- attrs["urls"] = urls
-
- return attrs
-
-python_repository = repository_rule(
- _python_repository_impl,
- doc = "Fetches the external tools needed for the Python toolchain.",
- attrs = {
- "auth_patterns": attr.string_dict(
- doc = "Override mapping of hostnames to authorization patterns; mirrors the eponymous attribute from http_archive",
- ),
- "coverage_tool": attr.string(
- # Mirrors the definition at
- # https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/python/py_runtime_rule.bzl
- doc = """
-This is a target to use for collecting code coverage information from `py_binary`
-and `py_test` targets.
-
-If set, the target must either produce a single file or be an executable target.
-The path to the single file, or the executable if the target is executable,
-determines the entry point for the python coverage tool. The target and its
-runfiles will be added to the runfiles when coverage is enabled.
-
-The entry point for the tool must be loadable by a Python interpreter (e.g. a
-`.py` or `.pyc` file). It must accept the command line arguments
-of coverage.py (https://coverage.readthedocs.io), at least including
-the `run` and `lcov` subcommands.
-
-The target is accepted as a string by the python_repository and evaluated within
-the context of the toolchain repository.
-
-For more information see the official bazel docs
-(https://bazel.build/reference/be/python#py_runtime.coverage_tool).
-""",
- ),
- "distutils": attr.label(
- allow_single_file = True,
- doc = "A distutils.cfg file to be included in the Python installation. " +
- "Either distutils or distutils_content can be specified, but not both.",
- mandatory = False,
- ),
- "distutils_content": attr.string(
- doc = "A distutils.cfg file content to be included in the Python installation. " +
- "Either distutils or distutils_content can be specified, but not both.",
- mandatory = False,
- ),
- "ignore_root_user_error": attr.bool(
- default = False,
- doc = "Whether the check for root should be ignored or not. This causes cache misses with .pyc files.",
- mandatory = False,
- ),
- "netrc": attr.string(
- doc = ".netrc file to use for authentication; mirrors the eponymous attribute from http_archive",
- ),
- "patches": attr.label_list(
- doc = "A list of patch files to apply to the unpacked interpreter",
- mandatory = False,
- ),
- "platform": attr.string(
- doc = "The platform name for the Python interpreter tarball.",
- mandatory = True,
- values = PLATFORMS.keys(),
- ),
- "python_version": attr.string(
- doc = "The Python version.",
- mandatory = True,
- ),
- "release_filename": attr.string(
- doc = "The filename of the interpreter to be downloaded",
- mandatory = True,
- ),
- "sha256": attr.string(
- doc = "The SHA256 integrity hash for the Python interpreter tarball.",
- mandatory = True,
- ),
- "strip_prefix": attr.string(
- doc = "A directory prefix to strip from the extracted files.",
- ),
- "url": attr.string(
- doc = "The URL of the interpreter to download. Exactly one of url and urls must be set.",
- ),
- "urls": attr.string_list(
- doc = "The URL of the interpreter to download. Exactly one of url and urls must be set.",
- ),
- "zstd_sha256": attr.string(
- default = "7c42d56fac126929a6a85dbc73ff1db2411d04f104fae9bdea51305663a83fd0",
- ),
- "zstd_url": attr.string(
- default = "https://github.com/facebook/zstd/releases/download/v{version}/zstd-{version}.tar.gz",
- ),
- "zstd_version": attr.string(
- default = "1.5.2",
- ),
- "_rule_name": attr.string(default = "python_repository"),
- },
- environ = [REPO_DEBUG_ENV_VAR],
-)
-
-# Wrapper macro around everything above, this is the primary API.
-def python_register_toolchains(
- name,
- python_version,
- distutils = None,
- distutils_content = None,
- register_toolchains = True,
- register_coverage_tool = False,
- set_python_version_constraint = False,
- tool_versions = TOOL_VERSIONS,
- **kwargs):
- """Convenience macro for users which does typical setup.
-
- - Create a repository for each built-in platform like "python_linux_amd64" -
- this repository is lazily fetched when Python is needed for that platform.
- - Create a repository exposing toolchains for each platform like
- "python_platforms".
- - Register a toolchain pointing at each platform.
- Users can avoid this macro and do these steps themselves, if they want more
- control.
- Args:
- name: base name for all created repos, like "python38".
- python_version: the Python version.
- distutils: see the distutils attribute in the python_repository repository rule.
- distutils_content: see the distutils_content attribute in the python_repository repository rule.
- register_toolchains: Whether or not to register the downloaded toolchains.
- register_coverage_tool: Whether or not to register the downloaded coverage tool to the toolchains.
- NOTE: Coverage support using the toolchain is only supported in Bazel 6 and higher.
-
- set_python_version_constraint: When set to true, target_compatible_with for the toolchains will include a version constraint.
- tool_versions: a dict containing a mapping of version with SHASUM and platform info. If not supplied, the defaults
- in python/versions.bzl will be used.
- **kwargs: passed to each python_repositories call.
- """
-
- if BZLMOD_ENABLED:
- # you cannot used native.register_toolchains when using bzlmod.
- register_toolchains = False
-
- base_url = kwargs.pop("base_url", DEFAULT_RELEASE_BASE_URL)
-
- python_version = full_version(python_version)
-
- toolchain_repo_name = "{name}_toolchains".format(name = name)
-
- # When using unreleased Bazel versions, the version is an empty string
- if native.bazel_version:
- bazel_major = int(native.bazel_version.split(".")[0])
- if bazel_major < 6:
- if register_coverage_tool:
- # buildifier: disable=print
- print((
- "WARNING: ignoring register_coverage_tool=True when " +
- "registering @{name}: Bazel 6+ required, got {version}"
- ).format(
- name = name,
- version = native.bazel_version,
- ))
- register_coverage_tool = False
-
- loaded_platforms = []
- for platform in PLATFORMS.keys():
- sha256 = tool_versions[python_version]["sha256"].get(platform, None)
- if not sha256:
- continue
-
- loaded_platforms.append(platform)
- (release_filename, urls, strip_prefix, patches) = get_release_info(platform, python_version, base_url, tool_versions)
-
- # allow passing in a tool version
- coverage_tool = None
- coverage_tool = tool_versions[python_version].get("coverage_tool", {}).get(platform, None)
- if register_coverage_tool and coverage_tool == None:
- coverage_tool = coverage_dep(
- name = "{name}_{platform}_coverage".format(
- name = name,
- platform = platform,
- ),
- python_version = python_version,
- platform = platform,
- visibility = ["@{name}_{platform}//:__subpackages__".format(
- name = name,
- platform = platform,
- )],
- )
-
- python_repository(
- name = "{name}_{platform}".format(
- name = name,
- platform = platform,
- ),
- sha256 = sha256,
- patches = patches,
- platform = platform,
- python_version = python_version,
- release_filename = release_filename,
- urls = urls,
- distutils = distutils,
- distutils_content = distutils_content,
- strip_prefix = strip_prefix,
- coverage_tool = coverage_tool,
- **kwargs
- )
- if register_toolchains:
- native.register_toolchains("@{toolchain_repo_name}//:{platform}_toolchain".format(
- toolchain_repo_name = toolchain_repo_name,
- platform = platform,
- ))
- native.register_toolchains("@{toolchain_repo_name}//:{platform}_py_cc_toolchain".format(
- toolchain_repo_name = toolchain_repo_name,
- platform = platform,
- ))
- native.register_toolchains("@{toolchain_repo_name}//:{platform}_py_exec_tools_toolchain".format(
- toolchain_repo_name = toolchain_repo_name,
- platform = platform,
- ))
-
- host_toolchain(
- name = name + "_host",
- python_version = python_version,
- user_repository_name = name,
- platforms = loaded_platforms,
- )
-
- toolchain_aliases(
- name = name,
- python_version = python_version,
- user_repository_name = name,
- platforms = loaded_platforms,
- )
-
- # in bzlmod we write out our own toolchain repos
- if BZLMOD_ENABLED:
- return
-
- toolchains_repo(
- name = toolchain_repo_name,
- python_version = python_version,
- set_python_version_constraint = set_python_version_constraint,
- user_repository_name = name,
- )
-
-def python_register_multi_toolchains(
- name,
- python_versions,
- default_version = None,
- **kwargs):
- """Convenience macro for registering multiple Python toolchains.
-
- Args:
- name: base name for each name in python_register_toolchains call.
- python_versions: the Python version.
- default_version: the default Python version. If not set, the first version in
- python_versions is used.
- **kwargs: passed to each python_register_toolchains call.
- """
- if len(python_versions) == 0:
- fail("python_versions must not be empty")
-
- if not default_version:
- default_version = python_versions.pop(0)
- for python_version in python_versions:
- if python_version == default_version:
- # We register the default version lastly so that it's not picked first when --platforms
- # is set with a constraint during toolchain resolution. This is due to the fact that
- # Bazel will match the unconstrained toolchain if we register it before the constrained
- # ones.
- continue
- python_register_toolchains(
- name = name + "_" + python_version.replace(".", "_"),
- python_version = python_version,
- set_python_version_constraint = True,
- **kwargs
- )
- python_register_toolchains(
- name = name + "_" + default_version.replace(".", "_"),
- python_version = default_version,
- set_python_version_constraint = False,
- **kwargs
- )
-
- multi_toolchain_aliases(
- name = name,
- python_versions = {
- python_version: name + "_" + python_version.replace(".", "_")
- for python_version in (python_versions + [default_version])
- },
- )
+# These symbols are of questionable public visibility. They were probably
+# not intended to be actually public.
+STANDALONE_INTERPRETER_FILENAME = _STANDALONE_INTERPRETER_FILENAME
+http_archive = _http_archive
+is_standalone_interpreter = _is_standalone_interpreter
+python_repository = _python_repository