xref: /aosp_15_r20/external/bazelbuild-rules_python/python/private/config_settings.bzl (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
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