1# Copyright 2022 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"""Create the toolchain defs in a BUILD.bazel file.""" 16 17load("@bazel_skylib//lib:selects.bzl", "selects") 18load("//python/private:text_util.bzl", "render") 19load( 20 ":toolchain_types.bzl", 21 "EXEC_TOOLS_TOOLCHAIN_TYPE", 22 "PY_CC_TOOLCHAIN_TYPE", 23 "TARGET_TOOLCHAIN_TYPE", 24) 25 26_IS_EXEC_TOOLCHAIN_ENABLED = Label("//python/config_settings:is_exec_tools_toolchain_enabled") 27 28# buildifier: disable=unnamed-macro 29def py_toolchain_suite( 30 *, 31 prefix, 32 user_repository_name, 33 python_version, 34 set_python_version_constraint, 35 flag_values, 36 target_compatible_with = []): 37 """For internal use only. 38 39 Args: 40 prefix: Prefix for toolchain target names. 41 user_repository_name: The name of the user repository. 42 python_version: The full (X.Y.Z) version of the interpreter. 43 set_python_version_constraint: True or False as a string. 44 flag_values: Extra flag values to match for this toolchain. 45 target_compatible_with: list constraints the toolchains are compatible with. 46 """ 47 48 # We have to use a String value here because bzlmod is passing in a 49 # string as we cannot have list of bools in build rule attributes. 50 # This if statement does not appear to work unless it is in the 51 # toolchain file. 52 if set_python_version_constraint in ["True", "False"]: 53 major_minor, _, _ = python_version.rpartition(".") 54 python_versions = [major_minor, python_version] 55 if set_python_version_constraint == "False": 56 python_versions.append("") 57 58 match_any = [] 59 for i, v in enumerate(python_versions): 60 name = "{prefix}_{python_version}_{i}".format( 61 prefix = prefix, 62 python_version = python_version, 63 i = i, 64 ) 65 match_any.append(name) 66 native.config_setting( 67 name = name, 68 flag_values = flag_values | { 69 Label("@rules_python//python/config_settings:python_version"): v, 70 }, 71 visibility = ["//visibility:private"], 72 ) 73 74 name = "{prefix}_version_setting_{python_version}".format( 75 prefix = prefix, 76 python_version = python_version, 77 visibility = ["//visibility:private"], 78 ) 79 selects.config_setting_group( 80 name = name, 81 match_any = match_any, 82 visibility = ["//visibility:private"], 83 ) 84 target_settings = [name] 85 else: 86 fail(("Invalid set_python_version_constraint value: got {} {}, wanted " + 87 "either the string 'True' or the string 'False'; " + 88 "(did you convert bool to string?)").format( 89 type(set_python_version_constraint), 90 repr(set_python_version_constraint), 91 )) 92 93 _internal_toolchain_suite( 94 prefix = prefix, 95 runtime_repo_name = user_repository_name, 96 target_settings = target_settings, 97 target_compatible_with = target_compatible_with, 98 ) 99 100def _internal_toolchain_suite(prefix, runtime_repo_name, target_compatible_with, target_settings): 101 native.toolchain( 102 name = "{prefix}_toolchain".format(prefix = prefix), 103 toolchain = "@{runtime_repo_name}//:python_runtimes".format( 104 runtime_repo_name = runtime_repo_name, 105 ), 106 toolchain_type = TARGET_TOOLCHAIN_TYPE, 107 target_settings = target_settings, 108 target_compatible_with = target_compatible_with, 109 ) 110 111 native.toolchain( 112 name = "{prefix}_py_cc_toolchain".format(prefix = prefix), 113 toolchain = "@{runtime_repo_name}//:py_cc_toolchain".format( 114 runtime_repo_name = runtime_repo_name, 115 ), 116 toolchain_type = PY_CC_TOOLCHAIN_TYPE, 117 target_settings = target_settings, 118 target_compatible_with = target_compatible_with, 119 ) 120 121 native.toolchain( 122 name = "{prefix}_py_exec_tools_toolchain".format(prefix = prefix), 123 toolchain = "@{runtime_repo_name}//:py_exec_tools_toolchain".format( 124 runtime_repo_name = runtime_repo_name, 125 ), 126 toolchain_type = EXEC_TOOLS_TOOLCHAIN_TYPE, 127 target_settings = select({ 128 _IS_EXEC_TOOLCHAIN_ENABLED: target_settings, 129 # Whatever the default is, it has to map to a `config_setting` 130 # that will never match. Since the default branch is only taken if 131 # _IS_EXEC_TOOLCHAIN_ENABLED is false, then it will never match 132 # when later evaluated during toolchain resolution. 133 # Note that @platforms//:incompatible can't be used here because 134 # the RHS must be a `config_setting`. 135 "//conditions:default": [_IS_EXEC_TOOLCHAIN_ENABLED], 136 }), 137 exec_compatible_with = target_compatible_with, 138 ) 139 140 # NOTE: When adding a new toolchain, for WORKSPACE builds to see the 141 # toolchain, the name must be added to the native.register_toolchains() 142 # call in python/repositories.bzl. Bzlmod doesn't need anything; it will 143 # register `:all`. 144 145def define_local_toolchain_suites(name, version_aware_repo_names, version_unaware_repo_names): 146 """Define toolchains for `local_runtime_repo` backed toolchains. 147 148 This generates `toolchain` targets that can be registered using `:all`. The 149 specific names of the toolchain targets are not defined. The priority order 150 of the toolchains is the order that is passed in, with version-aware having 151 higher priority than version-unaware. 152 153 Args: 154 name: `str` Unused; only present to satisfy tooling. 155 version_aware_repo_names: `list[str]` of the repo names that will have 156 version-aware toolchains defined. 157 version_unaware_repo_names: `list[str]` of the repo names that will have 158 version-unaware toolchains defined. 159 """ 160 i = 0 161 for i, repo in enumerate(version_aware_repo_names, start = i): 162 prefix = render.left_pad_zero(i, 4) 163 _internal_toolchain_suite( 164 prefix = prefix, 165 runtime_repo_name = repo, 166 target_compatible_with = ["@{}//:os".format(repo)], 167 target_settings = ["@{}//:is_matching_python_version".format(repo)], 168 ) 169 170 # The version unaware entries must go last because they will match any Python 171 # version. 172 for i, repo in enumerate(version_unaware_repo_names, start = i + 1): 173 prefix = render.left_pad_zero(i, 4) 174 _internal_toolchain_suite( 175 prefix = prefix, 176 runtime_repo_name = repo, 177 target_settings = [], 178 target_compatible_with = ["@{}//:os".format(repo)], 179 ) 180