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