| # 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. | |
| # 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 repository rules and macros to support toolchain registration. | |
| """ | |
| load( | |
| "//python:versions.bzl", | |
| "DEFAULT_RELEASE_BASE_URLS", | |
| "MINOR_MAPPING", | |
| "PLATFORMS", | |
| "TOOL_VERSIONS", | |
| "get_release_info", | |
| ) | |
| load(":coverage_deps.bzl", "coverage_dep") | |
| load(":full_version.bzl", "full_version") | |
| load(":pbs_manifest.bzl", "split_pbs_distribution") | |
| load(":python_repository.bzl", "python_repository") | |
| load( | |
| ":toolchains_repo.bzl", | |
| "host_compatible_python_repo", | |
| "toolchain_aliases", | |
| "toolchains_repo", | |
| ) | |
| def _get_platform_setting(version_info, key, *, platform, default): | |
| value = version_info.get(key, {}) | |
| if type(value) != "dict": | |
| return value | |
| return value.get(platform, default) | |
| # Wrapper macro around everything above, this is the primary API. | |
| def python_register_toolchains( | |
| name, | |
| python_version, | |
| register_toolchains = True, | |
| register_coverage_tool = False, | |
| set_python_version_constraint = False, | |
| libpython = None, | |
| tool_versions = None, | |
| platforms = PLATFORMS, | |
| minor_mapping = None, | |
| **kwargs): | |
| """Convenience macro for users which does typical setup. | |
| With `bzlmod` enabled, this function is not needed since `rules_python` is | |
| handling everything. In order to override the default behaviour from the | |
| root module one can see the docs for the {obj}`python` extension. | |
| - Create a repository for each built-in platform like "python_3_8_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: {type}`str` base name for all created repos, e.g. "python_3_8". | |
| python_version: {type}`str` the Python version. | |
| register_toolchains: {type}`bool` Whether or not to register the downloaded toolchains. | |
| register_coverage_tool: {type}`bool` Whether or not to register the | |
| downloaded coverage tool to the toolchains. | |
| set_python_version_constraint: {type}`bool` When set to `True`, | |
| `target_compatible_with` for the toolchains will include a version | |
| constraint. | |
| libpython: {type}`str` controls shared libpython files: `auto`, | |
| `include`, or `exclude`. | |
| tool_versions: {type}`dict` contains a mapping of version with SHASUM | |
| and platform info. If not supplied, the defaults in | |
| python/versions.bzl will be used. | |
| platforms: {type}`dict[str, struct]` platforms to create toolchain | |
| repositories for. Keys are platform names, and values are platform_info | |
| structs. Note that only a subset is created, depending on what's | |
| available in `tool_versions`. | |
| minor_mapping: {type}`dict[str, str]` contains a mapping from `X.Y` to `X.Y.Z` | |
| version. | |
| **kwargs: passed to each {obj}`python_repository` call. | |
| Returns: | |
| On workspace, returns None. | |
| On bzlmod, returns a `dict[str, platform_info]`, which is the | |
| subset of `platforms` that it created repositories for. | |
| """ | |
| bzlmod_toolchain_call = kwargs.pop("_internal_bzlmod_toolchain_call", False) | |
| if bzlmod_toolchain_call: | |
| register_toolchains = False | |
| base_urls = kwargs.pop("base_urls", DEFAULT_RELEASE_BASE_URLS) | |
| tool_versions = tool_versions or TOOL_VERSIONS | |
| minor_mapping = minor_mapping or MINOR_MAPPING | |
| python_version = full_version(version = python_version, minor_mapping = minor_mapping) | |
| toolchain_repo_name = "{name}_toolchains".format(name = name) | |
| # list[str] of the platform names that were used | |
| loaded_platforms = [] | |
| # dict[str repo name, tuple[str, platform_info]] | |
| impl_repos = {} | |
| for platform, platform_info in platforms.items(): | |
| version_info = tool_versions[python_version] | |
| base_platform, _ = split_pbs_distribution(platform) | |
| # A runtime for a python-build-standalone distribution that isn't | |
| # available for this version and platform falls back to the archive of | |
| # the plain platform key. | |
| source_platform = platform | |
| if platform not in version_info["sha256"]: | |
| source_platform = base_platform | |
| sha256 = version_info["sha256"].get(source_platform, None) | |
| if not sha256: | |
| continue | |
| loaded_platforms.append(platform) | |
| ( | |
| release_filename, | |
| urls, | |
| strip_prefix, | |
| patches, | |
| patch_strip, | |
| ) = get_release_info( | |
| source_platform, | |
| python_version, | |
| base_urls = base_urls, | |
| tool_versions = tool_versions, | |
| ) | |
| if not patches and source_platform != base_platform: | |
| # Per-platform settings of the plain platform key also apply to | |
| # its distribution variants. | |
| patches = _get_platform_setting( | |
| version_info, | |
| "patches", | |
| platform = base_platform, | |
| default = [], | |
| ) | |
| patch_strip = _get_platform_setting( | |
| version_info, | |
| "patch_strip", | |
| platform = base_platform, | |
| default = patch_strip, | |
| ) | |
| # allow passing in a tool version | |
| coverage_tool = None | |
| coverage_tool = _get_platform_setting( | |
| version_info, | |
| "coverage_tool", | |
| platform = source_platform, | |
| default = _get_platform_setting( | |
| version_info, | |
| "coverage_tool", | |
| platform = base_platform, | |
| default = 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 = base_platform, | |
| visibility = ["@{name}_{platform}//:__subpackages__".format( | |
| name = name, | |
| platform = platform, | |
| )], | |
| ) | |
| impl_repo_name = "{}_{}".format(name, platform) | |
| impl_repos[impl_repo_name] = (platform, platform_info) | |
| python_repository( | |
| name = impl_repo_name, | |
| sha256 = sha256, | |
| patches = patches, | |
| patch_strip = patch_strip, | |
| platform = platform, | |
| python_version = python_version, | |
| release_filename = release_filename, | |
| urls = urls, | |
| strip_prefix = strip_prefix, | |
| coverage_tool = coverage_tool, | |
| libpython = libpython or _get_platform_setting( | |
| version_info, | |
| "libpython", | |
| platform = source_platform, | |
| default = _get_platform_setting( | |
| version_info, | |
| "libpython", | |
| platform = base_platform, | |
| default = "auto", | |
| ), | |
| ), | |
| **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, | |
| )) | |
| toolchain_aliases( | |
| name = name, | |
| python_version = python_version, | |
| user_repository_name = name, | |
| platforms = loaded_platforms, | |
| ) | |
| # in bzlmod we write out our own toolchain repos and host repos | |
| if bzlmod_toolchain_call: | |
| return struct( | |
| # dict[str name, tuple[str platform_name, platform_info]] | |
| impl_repos = impl_repos, | |
| ) | |
| host_compatible_python_repo( | |
| name = name + "_host", | |
| platforms = loaded_platforms, | |
| python_version = python_version, | |
| ) | |
| toolchains_repo( | |
| name = toolchain_repo_name, | |
| python_version = python_version, | |
| set_python_version_constraint = set_python_version_constraint, | |
| user_repository_name = name, | |
| platforms = loaded_platforms, | |
| ) | |
| return None |