1# Copyright 2024 The Bazel Authors. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""This file contains repository rules and macros to support toolchain registration. 16""" 17 18load( 19 "//python:versions.bzl", 20 "DEFAULT_RELEASE_BASE_URL", 21 "MINOR_MAPPING", 22 "PLATFORMS", 23 "TOOL_VERSIONS", 24 "get_release_info", 25) 26load(":coverage_deps.bzl", "coverage_dep") 27load(":full_version.bzl", "full_version") 28load(":python_repository.bzl", "python_repository") 29load( 30 ":toolchains_repo.bzl", 31 "host_toolchain", 32 "toolchain_aliases", 33 "toolchains_repo", 34) 35 36# Wrapper macro around everything above, this is the primary API. 37def python_register_toolchains( 38 name, 39 python_version, 40 register_toolchains = True, 41 register_coverage_tool = False, 42 set_python_version_constraint = False, 43 tool_versions = None, 44 minor_mapping = None, 45 **kwargs): 46 """Convenience macro for users which does typical setup. 47 48 With `bzlmod` enabled, this function is not needed since `rules_python` is 49 handling everything. In order to override the default behaviour from the 50 root module one can see the docs for the {rule}`python` extension. 51 52 - Create a repository for each built-in platform like "python_3_8_linux_amd64" - 53 this repository is lazily fetched when Python is needed for that platform. 54 - Create a repository exposing toolchains for each platform like 55 "python_platforms". 56 - Register a toolchain pointing at each platform. 57 58 Users can avoid this macro and do these steps themselves, if they want more 59 control. 60 61 Args: 62 name: {type}`str` base name for all created repos, e.g. "python_3_8". 63 python_version: {type}`str` the Python version. 64 register_toolchains: {type}`bool` Whether or not to register the downloaded toolchains. 65 register_coverage_tool: {type}`bool` Whether or not to register the 66 downloaded coverage tool to the toolchains. 67 set_python_version_constraint: {type}`bool` When set to `True`, 68 `target_compatible_with` for the toolchains will include a version 69 constraint. 70 tool_versions: {type}`dict` contains a mapping of version with SHASUM 71 and platform info. If not supplied, the defaults in 72 python/versions.bzl will be used. 73 minor_mapping: {type}`dict[str, str]` contains a mapping from `X.Y` to `X.Y.Z` 74 version. 75 **kwargs: passed to each {obj}`python_repository` call. 76 77 Returns: 78 On bzlmod this returns the loaded platform labels. Otherwise None. 79 """ 80 bzlmod_toolchain_call = kwargs.pop("_internal_bzlmod_toolchain_call", False) 81 if bzlmod_toolchain_call: 82 register_toolchains = False 83 84 base_url = kwargs.pop("base_url", DEFAULT_RELEASE_BASE_URL) 85 tool_versions = tool_versions or TOOL_VERSIONS 86 minor_mapping = minor_mapping or MINOR_MAPPING 87 88 python_version = full_version(version = python_version, minor_mapping = minor_mapping) 89 90 toolchain_repo_name = "{name}_toolchains".format(name = name) 91 92 # When using unreleased Bazel versions, the version is an empty string 93 if native.bazel_version: 94 bazel_major = int(native.bazel_version.split(".")[0]) 95 if bazel_major < 6: 96 if register_coverage_tool: 97 # buildifier: disable=print 98 print(( 99 "WARNING: ignoring register_coverage_tool=True when " + 100 "registering @{name}: Bazel 6+ required, got {version}" 101 ).format( 102 name = name, 103 version = native.bazel_version, 104 )) 105 register_coverage_tool = False 106 107 loaded_platforms = [] 108 for platform in PLATFORMS.keys(): 109 sha256 = tool_versions[python_version]["sha256"].get(platform, None) 110 if not sha256: 111 continue 112 113 loaded_platforms.append(platform) 114 (release_filename, urls, strip_prefix, patches, patch_strip) = get_release_info(platform, python_version, base_url, tool_versions) 115 116 # allow passing in a tool version 117 coverage_tool = None 118 coverage_tool = tool_versions[python_version].get("coverage_tool", {}).get(platform, None) 119 if register_coverage_tool and coverage_tool == None: 120 coverage_tool = coverage_dep( 121 name = "{name}_{platform}_coverage".format( 122 name = name, 123 platform = platform, 124 ), 125 python_version = python_version, 126 platform = platform, 127 visibility = ["@{name}_{platform}//:__subpackages__".format( 128 name = name, 129 platform = platform, 130 )], 131 ) 132 133 python_repository( 134 name = "{name}_{platform}".format( 135 name = name, 136 platform = platform, 137 ), 138 sha256 = sha256, 139 patches = patches, 140 patch_strip = patch_strip, 141 platform = platform, 142 python_version = python_version, 143 release_filename = release_filename, 144 urls = urls, 145 strip_prefix = strip_prefix, 146 coverage_tool = coverage_tool, 147 **kwargs 148 ) 149 if register_toolchains: 150 native.register_toolchains("@{toolchain_repo_name}//:{platform}_toolchain".format( 151 toolchain_repo_name = toolchain_repo_name, 152 platform = platform, 153 )) 154 native.register_toolchains("@{toolchain_repo_name}//:{platform}_py_cc_toolchain".format( 155 toolchain_repo_name = toolchain_repo_name, 156 platform = platform, 157 )) 158 native.register_toolchains("@{toolchain_repo_name}//:{platform}_py_exec_tools_toolchain".format( 159 toolchain_repo_name = toolchain_repo_name, 160 platform = platform, 161 )) 162 163 host_toolchain( 164 name = name + "_host", 165 platforms = loaded_platforms, 166 python_version = python_version, 167 ) 168 169 toolchain_aliases( 170 name = name, 171 python_version = python_version, 172 user_repository_name = name, 173 platforms = loaded_platforms, 174 ) 175 176 # in bzlmod we write out our own toolchain repos 177 if bzlmod_toolchain_call: 178 return loaded_platforms 179 180 toolchains_repo( 181 name = toolchain_repo_name, 182 python_version = python_version, 183 set_python_version_constraint = set_python_version_constraint, 184 user_repository_name = name, 185 platforms = loaded_platforms, 186 ) 187 return None 188