|
| 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 | +""" |
| 16 | +This module is used to construct the config settings for selecting which distribution is used in the pip hub repository. |
| 17 | +
|
| 18 | +Bazel's selects work by selecting the most-specialized configuration setting |
| 19 | +that matches the target platform. We can leverage this fact to ensure that the |
| 20 | +most specialized wheels are used by default with the users being able to |
| 21 | +configure string_flag values to select the less specialized ones. |
| 22 | +
|
| 23 | +The list of specialization of the dists goes like follows: |
| 24 | +* sdist |
| 25 | +* -none-any.whl |
| 26 | +* -abi3-any.whl |
| 27 | +* -cpxy-any.whl |
| 28 | +* -none-plat.whl |
| 29 | +* -abi3-plat.whl |
| 30 | +* -cpxy-plat.whl |
| 31 | +
|
| 32 | +Note, that here the specialization of musl vs manylinux wheels is the same in |
| 33 | +order to not match an incompatible version in case the user specifies the version |
| 34 | +of the libc to match. By default we do not match against the libc version and pick |
| 35 | +some whl. |
| 36 | +
|
| 37 | +That should cover the majority of cases as only a single `whl`. |
| 38 | +
|
| 39 | +We also would like to ensure that we match the python version tag, it can be: |
| 40 | +- py2.py3 - all python 3 versions |
| 41 | +- py3 - all python 3 versions |
| 42 | +- cp38 - all CPython 3.8 and above |
| 43 | +- cp39 - all CPython 3.9 and above |
| 44 | +- cp310 - all CPython 3.10 and above |
| 45 | +
|
| 46 | +Use cases that this code strives to support: |
| 47 | +* sdist only - Setting the flag `:whl=no` should do the trick |
| 48 | +* whl only - Will be handled differently (i.e. filtering elsewhere). By default |
| 49 | + we will give preference to whls, so this should not be necessary most of the |
| 50 | + times. |
| 51 | +* select only pure python versions of wheels. This may not work all of the time |
| 52 | + unless building from `sdist` is disabled. |
| 53 | +* select to target a particular libc version |
| 54 | +* select to target a particular osx version |
| 55 | +""" |
| 56 | + |
| 57 | +load(":config_settings.bzl", "is_python_config_setting") |
| 58 | +load( |
| 59 | + ":pip_flags.bzl", |
| 60 | + "UniversalWhlFlag", |
| 61 | + "UseWhlAbi3Flag", |
| 62 | + "UseWhlAbiCpFlag", |
| 63 | + "UseWhlFlag", |
| 64 | + "UseWhlPlatformFlag", |
| 65 | + "WhlLibcFlag", |
| 66 | +) |
| 67 | + |
| 68 | +_flags = struct( |
| 69 | + **{ |
| 70 | + f: str(Label("//python/config_settings:" + f)) |
| 71 | + for f in [ |
| 72 | + "python_version", |
| 73 | + "pip_whl", |
| 74 | + "pip_whl_abi3", |
| 75 | + "pip_whl_cpxy", |
| 76 | + "pip_whl_glibc_version", |
| 77 | + "pip_whl_linux_libc", |
| 78 | + "pip_whl_muslc_version", |
| 79 | + "pip_whl_osx_arch", |
| 80 | + "pip_whl_osx_version", |
| 81 | + "pip_whl_plat", |
| 82 | + ] |
| 83 | + } |
| 84 | +) |
| 85 | + |
| 86 | +def pip_config_settings( |
| 87 | + name, |
| 88 | + python_versions, |
| 89 | + constraint_values, |
| 90 | + glibc_versions = None, |
| 91 | + muslc_versions = None, |
| 92 | + osx_versions = None, |
| 93 | + visibility = None): |
| 94 | + """Create string flags for dist configuration. |
| 95 | +
|
| 96 | + Args: |
| 97 | + name: Currently unused. |
| 98 | + python_versions: list[str] A list of python version to generate |
| 99 | + config_setting targets for. |
| 100 | + constraint_values: The dict[str, list[Label]] mapping platform labels |
| 101 | + to constraint_values passed to the `config_setting`. |
| 102 | + osx_versions: The list of osx versions that we need to support. |
| 103 | + glibc_versions: The list of glibc versions that we need to support. |
| 104 | + muslc_versions: The list of muslc versions that we need to support. |
| 105 | + visibility: The visibility of the flags that we create. |
| 106 | + """ |
| 107 | + presets = { |
| 108 | + name: { |
| 109 | + "constraint_values": cvs, |
| 110 | + "python_versions": python_versions, |
| 111 | + "visibility": visibility, |
| 112 | + } |
| 113 | + for name, cvs in ({"": None} | (constraint_values or {})).items() |
| 114 | + } |
| 115 | + |
| 116 | + # Creating config_setting values so that we can |
| 117 | + # * sdist - use if this exists or disable usage of it. |
| 118 | + # * none-any.whl - use if this exists or use only these in case we need pure python whls. |
| 119 | + # * abi3-any.whl - use if this exists and prefer it over none whls |
| 120 | + # * cp3x-any.whl - use if this exists and prefer it over abi3 and none whls |
| 121 | + for plat, config_settings_args in presets.items(): |
| 122 | + # A different way to model this would be to ask the user to explicitly allow sdists |
| 123 | + # - use only whls (do not register sdist, should be a flag to pip.parse) |
| 124 | + # - fallback to sdist (default) |
| 125 | + # - use only sdist (disable whls) (flag) |
| 126 | + for prefix, flag_values in { |
| 127 | + "": {}, |
| 128 | + "sdist": {_flags.pip_whl: UseWhlFlag.NO}, |
| 129 | + }.items(): |
| 130 | + _config_settings( |
| 131 | + name = [prefix, plat], |
| 132 | + flag_values = flag_values, |
| 133 | + # in theory this can only work with the host platform as we |
| 134 | + # don't support cross-building the wheels from sdist. If we did |
| 135 | + # support that, then it could be different. However, we need |
| 136 | + # the `constraint_values` on os and arch to support the case |
| 137 | + # where we have different versions on different platforms, |
| 138 | + # because we are supplying different requirement files. |
| 139 | + **config_settings_args |
| 140 | + ) |
| 141 | + |
| 142 | + for prefix, flag_values in { |
| 143 | + # All of these fallback to sdist |
| 144 | + "whl_none": {_flags.pip_whl: UseWhlFlag.AUTO}, |
| 145 | + "whl_abi3": {_flags.pip_whl: UseWhlFlag.AUTO, _flags.pip_whl_abi3: UseWhlAbi3Flag.AUTO}, |
| 146 | + "whl_cpxy": {_flags.pip_whl: UseWhlFlag.AUTO, _flags.pip_whl_abi3: UseWhlAbi3Flag.AUTO, _flags.pip_whl_cpxy: UseWhlAbiCpFlag.AUTO}, |
| 147 | + }.items(): # buildifier: disable=unsorted-dict-items as they have meaning |
| 148 | + # [none|abi3|cp]-any.whl |
| 149 | + # the platform suffix is for the cases when we have different dists |
| 150 | + # on different platforms. |
| 151 | + _config_settings( |
| 152 | + name = [prefix, "any", plat], |
| 153 | + flag_values = flag_values, |
| 154 | + **config_settings_args |
| 155 | + ) |
| 156 | + |
| 157 | + if plat == "": |
| 158 | + # We are dealing with platform-specific wheels, so the default |
| 159 | + # platform does not make sense here. |
| 160 | + continue |
| 161 | + elif "windows" in plat: |
| 162 | + # [none|abi3|cp]-plat.whl |
| 163 | + _config_settings( |
| 164 | + name = [prefix, plat], |
| 165 | + flag_values = { |
| 166 | + _flags.pip_whl_plat: UseWhlPlatformFlag.AUTO, |
| 167 | + } | flag_values, |
| 168 | + **config_settings_args |
| 169 | + ) |
| 170 | + elif "osx" in plat: |
| 171 | + # [none|abi3|cp]-macosx-arch.whl |
| 172 | + for name, whl_osx_arch in { |
| 173 | + plat: UniversalWhlFlag.ARCH, |
| 174 | + plat + "_universal2": UniversalWhlFlag.UNIVERSAL, |
| 175 | + }.items(): |
| 176 | + _config_settings( |
| 177 | + name = [prefix, name], |
| 178 | + flag_values = { |
| 179 | + _flags.pip_whl_osx_arch: whl_osx_arch, |
| 180 | + _flags.pip_whl_plat: UseWhlPlatformFlag.AUTO, |
| 181 | + } | flag_values, |
| 182 | + **config_settings_args |
| 183 | + ) |
| 184 | + elif "linux" in plat: |
| 185 | + # [none|abi3|cp]-[|many|musl]linux_arch.whl |
| 186 | + for name, whl_linux_libc in { |
| 187 | + plat: None, |
| 188 | + "many" + plat: WhlLibcFlag.GLIBC, |
| 189 | + "musl" + plat: WhlLibcFlag.MUSL, |
| 190 | + }.items(): |
| 191 | + _config_settings( |
| 192 | + name = [prefix, name], |
| 193 | + flag_values = { |
| 194 | + _flags.pip_whl_linux_libc: whl_linux_libc, |
| 195 | + _flags.pip_whl_plat: UseWhlPlatformFlag.AUTO, |
| 196 | + } | flag_values, |
| 197 | + **config_settings_args |
| 198 | + ) |
| 199 | + else: |
| 200 | + fail("unknown platform: '{}'".format(plat)) |
| 201 | + |
| 202 | + # If we select a whl that is with a platform tag including `1.1`, that |
| 203 | + # means that the whl will work on a platform where the libc is 1.1 or later |
| 204 | + # (https://peps.python.org/pep-0600/#core-definition). As such, if the user |
| 205 | + # wants to target a platform that is 1.3, then a whl with `1.1` version |
| 206 | + # should be selected if there is no higher version. This means that the |
| 207 | + # alias can be like: |
| 208 | + # |
| 209 | + # alias( |
| 210 | + # name = "whl_matching_1.2_or_lower" |
| 211 | + # actual = select( |
| 212 | + # { |
| 213 | + # "flag_eq_1.2_or_above": "actual_whl_1.1", |
| 214 | + # "flag_eq_1.1": "actual_whl_1.1", |
| 215 | + # "flag_eq_1.0": "actual_whl_1.0", |
| 216 | + # }, |
| 217 | + # no_match_error = "Could not match, the target supports versions 1.2, 1.1 and 1.0", but you have selected something outside this range. |
| 218 | + # ) |
| 219 | + |
| 220 | + for (version_name, flag, versions) in [ |
| 221 | + ("glibc", _flags.pip_whl_glibc_version, glibc_versions), |
| 222 | + ("muslc", _flags.pip_whl_muslc_version, muslc_versions), |
| 223 | + ("osx", _flags.pip_whl_osx_version, osx_versions), |
| 224 | + ]: |
| 225 | + if not versions: |
| 226 | + continue |
| 227 | + |
| 228 | + # Because we want to have a no-match error, this has to be separate from other constraints. |
| 229 | + native.config_setting( |
| 230 | + name = "is_{}_default".format(version_name), |
| 231 | + flag_values = {flag: ""}, |
| 232 | + visibility = visibility, |
| 233 | + ) |
| 234 | + for version in versions: |
| 235 | + native.config_setting( |
| 236 | + name = "is_{}_{}".format(version_name, version), |
| 237 | + flag_values = {flag: version}, |
| 238 | + visibility = visibility, |
| 239 | + ) |
| 240 | + |
| 241 | +def _config_settings( |
| 242 | + *, |
| 243 | + name, |
| 244 | + python_versions, |
| 245 | + flag_values, |
| 246 | + constraint_values = None, |
| 247 | + **kwargs): |
| 248 | + # TODO @aignas 2024-05-24: this "auto" may need to go |
| 249 | + name = "_".join(["is"] + [n for n in name if n and n != "auto"]) |
| 250 | + |
| 251 | + flag_values = { |
| 252 | + k: v |
| 253 | + for k, v in flag_values.items() |
| 254 | + if v != None |
| 255 | + } |
| 256 | + |
| 257 | + if flag_values or constraint_values: |
| 258 | + native.config_setting( |
| 259 | + name = name, |
| 260 | + # NOTE @aignas 2024-04-25: we need to add this so that we ensure that |
| 261 | + # all config settings have the same number of flag values so that |
| 262 | + # the different wheel arches are specializations of each other and |
| 263 | + # so that the modelling works fully. |
| 264 | + flag_values = flag_values | {_flags.python_version: ""}, |
| 265 | + constraint_values = constraint_values, |
| 266 | + **kwargs |
| 267 | + ) |
| 268 | + |
| 269 | + for python_version in python_versions: |
| 270 | + is_python_config_setting( |
| 271 | + name = "is_python_{}{}".format(python_version, name[len("is"):]), |
| 272 | + python_version = python_version, |
| 273 | + flag_values = flag_values, |
| 274 | + constraint_values = constraint_values, |
| 275 | + **kwargs |
| 276 | + ) |
0 commit comments