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 module is used to construct the config settings in the BUILD file in this same package. 16""" 17 18load("@bazel_skylib//lib:selects.bzl", "selects") 19load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") 20load(":semver.bzl", "semver") 21 22_PYTHON_VERSION_FLAG = Label("//python/config_settings:python_version") 23_PYTHON_VERSION_MAJOR_MINOR_FLAG = Label("//python/config_settings:_python_version_major_minor") 24 25def construct_config_settings(*, name, versions, minor_mapping): # buildifier: disable=function-docstring 26 """Create a 'python_version' config flag and construct all config settings used in rules_python. 27 28 This mainly includes the targets that are used in the toolchain and pip hub 29 repositories that only match on the 'python_version' flag values. 30 31 Args: 32 name: {type}`str` A dummy name value that is no-op for now. 33 versions: {type}`list[str]` A list of versions to build constraint settings for. 34 minor_mapping: {type}`dict[str, str]` A mapping from `X.Y` to `X.Y.Z` python versions. 35 """ 36 _ = name # @unused 37 _python_version_flag( 38 name = _PYTHON_VERSION_FLAG.name, 39 # TODO: The default here should somehow match the MODULE config. Until 40 # then, use the empty string to indicate an unknown version. This 41 # also prevents version-unaware targets from inadvertently matching 42 # a select condition when they shouldn't. 43 build_setting_default = "", 44 visibility = ["//visibility:public"], 45 ) 46 47 _python_version_major_minor_flag( 48 name = _PYTHON_VERSION_MAJOR_MINOR_FLAG.name, 49 build_setting_default = "", 50 visibility = ["//visibility:public"], 51 ) 52 53 native.config_setting( 54 name = "is_python_version_unset", 55 flag_values = {_PYTHON_VERSION_FLAG: ""}, 56 visibility = ["//visibility:public"], 57 ) 58 59 _reverse_minor_mapping = {full: minor for minor, full in minor_mapping.items()} 60 for version in versions: 61 minor_version = _reverse_minor_mapping.get(version) 62 if not minor_version: 63 native.config_setting( 64 name = "is_python_{}".format(version), 65 flag_values = {":python_version": version}, 66 visibility = ["//visibility:public"], 67 ) 68 continue 69 70 # Also need to match the minor version when using 71 name = "is_python_{}".format(version) 72 native.config_setting( 73 name = "_" + name, 74 flag_values = {":python_version": version}, 75 visibility = ["//visibility:public"], 76 ) 77 78 # An alias pointing to an underscore-prefixed config_setting_group 79 # is used because config_setting_group creates 80 # `is_{version}_N` targets, which are easily confused with the 81 # `is_{minor}.{micro}` (dot) targets. 82 selects.config_setting_group( 83 name = "_{}_group".format(name), 84 match_any = [ 85 ":_is_python_{}".format(version), 86 ":is_python_{}".format(minor_version), 87 ], 88 visibility = ["//visibility:private"], 89 ) 90 native.alias( 91 name = name, 92 actual = "_{}_group".format(name), 93 visibility = ["//visibility:public"], 94 ) 95 96 # This matches the raw flag value, e.g. --//python/config_settings:python_version=3.8 97 # It's private because matching the concept of e.g. "3.8" value is done 98 # using the `is_python_X.Y` config setting group, which is aware of the 99 # minor versions that could match instead. 100 for minor in minor_mapping.keys(): 101 native.config_setting( 102 name = "is_python_{}".format(minor), 103 flag_values = {_PYTHON_VERSION_MAJOR_MINOR_FLAG: minor}, 104 visibility = ["//visibility:public"], 105 ) 106 107def _python_version_flag_impl(ctx): 108 value = ctx.build_setting_value 109 return [ 110 # BuildSettingInfo is the original provider returned, so continue to 111 # return it for compatibility 112 BuildSettingInfo(value = value), 113 # FeatureFlagInfo is returned so that config_setting respects the value 114 # as returned by this rule instead of as originally seen on the command 115 # line. 116 # It is also for Google compatibility, which expects the FeatureFlagInfo 117 # provider. 118 config_common.FeatureFlagInfo(value = value), 119 ] 120 121_python_version_flag = rule( 122 implementation = _python_version_flag_impl, 123 build_setting = config.string(flag = True), 124 attrs = {}, 125) 126 127def _python_version_major_minor_flag_impl(ctx): 128 input = ctx.attr._python_version_flag[config_common.FeatureFlagInfo].value 129 if input: 130 version = semver(input) 131 value = "{}.{}".format(version.major, version.minor) 132 else: 133 value = "" 134 135 return [config_common.FeatureFlagInfo(value = value)] 136 137_python_version_major_minor_flag = rule( 138 implementation = _python_version_major_minor_flag_impl, 139 build_setting = config.string(flag = False), 140 attrs = { 141 "_python_version_flag": attr.label( 142 default = _PYTHON_VERSION_FLAG, 143 ), 144 }, 145) 146