| # 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 module is used to construct the config settings for selecting which distribution is used in the pip hub repository. | |
| Bazel's selects work by selecting the most-specialized configuration setting | |
| that matches the target platform. We can leverage this fact to ensure that the | |
| most specialized wheels are used by default with the users being able to | |
| configure string_flag values to select the less specialized ones. | |
| The list of specialization of the dists goes like follows: | |
| * sdist | |
| * py*-none-any.whl | |
| * py*-abi3-any.whl | |
| * py*-cpxy-any.whl | |
| * cp*-none-any.whl | |
| * cp*-abi3-any.whl | |
| * cp*-cpxy-plat.whl | |
| * py*-none-plat.whl | |
| * py*-abi3-plat.whl | |
| * py*-cpxy-plat.whl | |
| * cp*-none-plat.whl | |
| * cp*-abi3-plat.whl | |
| * cp*-cpxy-plat.whl | |
| Note, that here the specialization of musl vs manylinux wheels is the same in | |
| order to ensure that the matching fails if the user requests for `musl` and we don't have it or vice versa. | |
| """ | |
| load(":flags.bzl", "INTERNAL_FLAGS", "UniversalWhlFlag", "WhlLibcFlag") | |
| FLAGS = struct( | |
| **{ | |
| f: str(Label("//python/config_settings:" + f)) | |
| for f in [ | |
| "python_version", | |
| "pip_whl_glibc_version", | |
| "pip_whl_muslc_version", | |
| "pip_whl_osx_arch", | |
| "pip_whl_osx_version", | |
| "py_linux_libc", | |
| "is_pip_whl_no", | |
| "is_pip_whl_only", | |
| "is_pip_whl_auto", | |
| ] | |
| } | |
| ) | |
| # Here we create extra string flags that are just to work with the select | |
| # selecting the most specialized match. We don't allow the user to change | |
| # them. | |
| _flags = struct( | |
| **{ | |
| f: str(Label("//python/config_settings:_internal_pip_" + f)) | |
| for f in INTERNAL_FLAGS | |
| } | |
| ) | |
| def config_settings( | |
| *, | |
| python_versions = [], | |
| glibc_versions = [], | |
| muslc_versions = [], | |
| osx_versions = [], | |
| target_platforms = [], | |
| name = None, | |
| visibility = None, | |
| native = native): | |
| """Generate all of the pip config settings. | |
| Args: | |
| name (str): Currently unused. | |
| python_versions (list[str]): The list of python versions to configure | |
| config settings for. | |
| glibc_versions (list[str]): The list of glibc version of the wheels to | |
| configure config settings for. | |
| muslc_versions (list[str]): The list of musl version of the wheels to | |
| configure config settings for. | |
| osx_versions (list[str]): The list of OSX OS versions to configure | |
| config settings for. | |
| target_platforms (list[str]): The list of "{os}_{cpu}" for deriving | |
| constraint values for each condition. | |
| visibility (list[str], optional): The visibility to be passed to the | |
| exposed labels. All other labels will be private. | |
| native (struct): The struct containing alias and config_setting rules | |
| to use for creating the objects. Can be overridden for unit tests | |
| reasons. | |
| """ | |
| glibc_versions = [""] + glibc_versions | |
| muslc_versions = [""] + muslc_versions | |
| osx_versions = [""] + osx_versions | |
| target_platforms = [("", "")] + [ | |
| t.split("_", 1) | |
| for t in target_platforms | |
| ] | |
| for python_version in [""] + python_versions: | |
| is_python = "is_python_{}".format(python_version or "version_unset") | |
| native.alias( | |
| name = is_python, | |
| actual = Label("//python/config_settings:" + is_python), | |
| visibility = visibility, | |
| ) | |
| for os, cpu in target_platforms: | |
| constraint_values = [] | |
| suffix = "" | |
| if os: | |
| constraint_values.append("@platforms//os:" + os) | |
| suffix += "_" + os | |
| if cpu: | |
| constraint_values.append("@platforms//cpu:" + cpu) | |
| suffix += "_" + cpu | |
| _dist_config_settings( | |
| suffix = suffix, | |
| plat_flag_values = _plat_flag_values( | |
| os = os, | |
| cpu = cpu, | |
| osx_versions = osx_versions, | |
| glibc_versions = glibc_versions, | |
| muslc_versions = muslc_versions, | |
| ), | |
| constraint_values = constraint_values, | |
| python_version = python_version, | |
| is_python = is_python, | |
| visibility = visibility, | |
| native = native, | |
| ) | |
| def _dist_config_settings(*, suffix, plat_flag_values, **kwargs): | |
| flag_values = {_flags.dist: ""} | |
| # First create an sdist, we will be building upon the flag values, which | |
| # will ensure that each sdist config setting is the least specialized of | |
| # all. However, we need at least one flag value to cover the case where we | |
| # have `sdist` for any platform, hence we have a non-empty `flag_values` | |
| # here. | |
| _dist_config_setting( | |
| name = "sdist{}".format(suffix), | |
| flag_values = flag_values, | |
| is_pip_whl = FLAGS.is_pip_whl_no, | |
| **kwargs | |
| ) | |
| for name, f in [ | |
| ("py_none", _flags.whl_py2_py3), | |
| ("py3_none", _flags.whl_py3), | |
| ("py3_abi3", _flags.whl_py3_abi3), | |
| ("cp3x_none", _flags.whl_pycp3x), | |
| ("cp3x_abi3", _flags.whl_pycp3x_abi3), | |
| ("cp3x_cp", _flags.whl_pycp3x_abicp), | |
| ]: | |
| if f in flag_values: | |
| # This should never happen as all of the different whls should have | |
| # unique flag values. | |
| fail("BUG: the flag {} is attempted to be added twice to the list".format(f)) | |
| else: | |
| flag_values[f] = "" | |
| _dist_config_setting( | |
| name = "{}_any{}".format(name, suffix), | |
| flag_values = flag_values, | |
| is_pip_whl = FLAGS.is_pip_whl_only, | |
| **kwargs | |
| ) | |
| generic_flag_values = flag_values | |
| for (suffix, flag_values) in plat_flag_values: | |
| flag_values = flag_values | generic_flag_values | |
| for name, f in [ | |
| ("py_none", _flags.whl_plat), | |
| ("py3_none", _flags.whl_plat_py3), | |
| ("py3_abi3", _flags.whl_plat_py3_abi3), | |
| ("cp3x_none", _flags.whl_plat_pycp3x), | |
| ("cp3x_abi3", _flags.whl_plat_pycp3x_abi3), | |
| ("cp3x_cp", _flags.whl_plat_pycp3x_abicp), | |
| ]: | |
| if f in flag_values: | |
| # This should never happen as all of the different whls should have | |
| # unique flag values. | |
| fail("BUG: the flag {} is attempted to be added twice to the list".format(f)) | |
| else: | |
| flag_values[f] = "" | |
| _dist_config_setting( | |
| name = "{}_{}".format(name, suffix), | |
| flag_values = flag_values, | |
| is_pip_whl = FLAGS.is_pip_whl_only, | |
| **kwargs | |
| ) | |
| def _to_version_string(version, sep = "."): | |
| if not version: | |
| return "" | |
| return "{}{}{}".format(version[0], sep, version[1]) | |
| def _plat_flag_values(os, cpu, osx_versions, glibc_versions, muslc_versions): | |
| ret = [] | |
| if os == "": | |
| return [] | |
| elif os == "windows": | |
| ret.append(("{}_{}".format(os, cpu), {})) | |
| elif os == "osx": | |
| for cpu_, arch in { | |
| cpu: UniversalWhlFlag.ARCH, | |
| cpu + "_universal2": UniversalWhlFlag.UNIVERSAL, | |
| }.items(): | |
| for osx_version in osx_versions: | |
| flags = { | |
| FLAGS.pip_whl_osx_version: _to_version_string(osx_version), | |
| } | |
| if arch == UniversalWhlFlag.ARCH: | |
| flags[FLAGS.pip_whl_osx_arch] = arch | |
| if not osx_version: | |
| suffix = "{}_{}".format(os, cpu_) | |
| else: | |
| suffix = "{}_{}_{}".format(os, _to_version_string(osx_version, "_"), cpu_) | |
| ret.append((suffix, flags)) | |
| elif os == "linux": | |
| for os_prefix, linux_libc in { | |
| os: WhlLibcFlag.GLIBC, | |
| "many" + os: WhlLibcFlag.GLIBC, | |
| "musl" + os: WhlLibcFlag.MUSL, | |
| }.items(): | |
| if linux_libc == WhlLibcFlag.GLIBC: | |
| libc_versions = glibc_versions | |
| libc_flag = FLAGS.pip_whl_glibc_version | |
| elif linux_libc == WhlLibcFlag.MUSL: | |
| libc_versions = muslc_versions | |
| libc_flag = FLAGS.pip_whl_muslc_version | |
| else: | |
| fail("Unsupported libc type: {}".format(linux_libc)) | |
| for libc_version in libc_versions: | |
| if libc_version and os_prefix == os: | |
| continue | |
| elif libc_version: | |
| suffix = "{}_{}_{}".format(os_prefix, _to_version_string(libc_version, "_"), cpu) | |
| else: | |
| suffix = "{}_{}".format(os_prefix, cpu) | |
| ret.append(( | |
| suffix, | |
| { | |
| FLAGS.py_linux_libc: linux_libc, | |
| libc_flag: _to_version_string(libc_version), | |
| }, | |
| )) | |
| else: | |
| fail("Unsupported os: {}".format(os)) | |
| return ret | |
| def _dist_config_setting(*, name, is_pip_whl, is_python, python_version, native = native, **kwargs): | |
| """A macro to create a target that matches is_pip_whl_auto and one more value. | |
| Args: | |
| name: The name of the public target. | |
| is_pip_whl: The config setting to match in addition to | |
| `is_pip_whl_auto` when evaluating the config setting. | |
| is_python: The python version config_setting to match. | |
| python_version: The python version name. | |
| native (struct): The struct containing alias and config_setting rules | |
| to use for creating the objects. Can be overridden for unit tests | |
| reasons. | |
| **kwargs: The kwargs passed to the config_setting rule. Visibility of | |
| the main alias target is also taken from the kwargs. | |
| """ | |
| _name = "_is_" + name | |
| visibility = kwargs.get("visibility") | |
| native.alias( | |
| name = "is_cp{}_{}".format(python_version, name) if python_version else "is_{}".format(name), | |
| actual = select({ | |
| # First match by the python version | |
| is_python: _name, | |
| "//conditions:default": is_python, | |
| }), | |
| visibility = visibility, | |
| ) | |
| if python_version: | |
| # Reuse the config_setting targets that we use with the default | |
| # `python_version` setting. | |
| return | |
| config_setting_name = _name + "_setting" | |
| native.config_setting(name = config_setting_name, **kwargs) | |
| # Next match by the `pip_whl` flag value and then match by the flags that | |
| # are intrinsic to the distribution. | |
| native.alias( | |
| name = _name, | |
| actual = select({ | |
| "//conditions:default": FLAGS.is_pip_whl_auto, | |
| FLAGS.is_pip_whl_auto: config_setting_name, | |
| is_pip_whl: config_setting_name, | |
| }), | |
| visibility = visibility, | |
| ) |