• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 file contains repository rules and macros to support toolchain registration.
16"""
17
18load(
19    "//python:versions.bzl",
20    "DEFAULT_RELEASE_BASE_URL",
21    "MINOR_MAPPING",
22    "PLATFORMS",
23    "TOOL_VERSIONS",
24    "get_release_info",
25)
26load(":coverage_deps.bzl", "coverage_dep")
27load(":full_version.bzl", "full_version")
28load(":python_repository.bzl", "python_repository")
29load(
30    ":toolchains_repo.bzl",
31    "host_toolchain",
32    "toolchain_aliases",
33    "toolchains_repo",
34)
35
36# Wrapper macro around everything above, this is the primary API.
37def python_register_toolchains(
38        name,
39        python_version,
40        register_toolchains = True,
41        register_coverage_tool = False,
42        set_python_version_constraint = False,
43        tool_versions = None,
44        minor_mapping = None,
45        **kwargs):
46    """Convenience macro for users which does typical setup.
47
48    With `bzlmod` enabled, this function is not needed since `rules_python` is
49    handling everything. In order to override the default behaviour from the
50    root module one can see the docs for the {rule}`python` extension.
51
52    - Create a repository for each built-in platform like "python_3_8_linux_amd64" -
53      this repository is lazily fetched when Python is needed for that platform.
54    - Create a repository exposing toolchains for each platform like
55      "python_platforms".
56    - Register a toolchain pointing at each platform.
57
58    Users can avoid this macro and do these steps themselves, if they want more
59    control.
60
61    Args:
62        name: {type}`str` base name for all created repos, e.g. "python_3_8".
63        python_version: {type}`str` the Python version.
64        register_toolchains: {type}`bool` Whether or not to register the downloaded toolchains.
65        register_coverage_tool: {type}`bool` Whether or not to register the
66            downloaded coverage tool to the toolchains.
67        set_python_version_constraint: {type}`bool` When set to `True`,
68            `target_compatible_with` for the toolchains will include a version
69            constraint.
70        tool_versions: {type}`dict` contains a mapping of version with SHASUM
71            and platform info. If not supplied, the defaults in
72            python/versions.bzl will be used.
73        minor_mapping: {type}`dict[str, str]` contains a mapping from `X.Y` to `X.Y.Z`
74            version.
75        **kwargs: passed to each {obj}`python_repository` call.
76
77    Returns:
78        On bzlmod this returns the loaded platform labels. Otherwise None.
79    """
80    bzlmod_toolchain_call = kwargs.pop("_internal_bzlmod_toolchain_call", False)
81    if bzlmod_toolchain_call:
82        register_toolchains = False
83
84    base_url = kwargs.pop("base_url", DEFAULT_RELEASE_BASE_URL)
85    tool_versions = tool_versions or TOOL_VERSIONS
86    minor_mapping = minor_mapping or MINOR_MAPPING
87
88    python_version = full_version(version = python_version, minor_mapping = minor_mapping)
89
90    toolchain_repo_name = "{name}_toolchains".format(name = name)
91
92    # When using unreleased Bazel versions, the version is an empty string
93    if native.bazel_version:
94        bazel_major = int(native.bazel_version.split(".")[0])
95        if bazel_major < 6:
96            if register_coverage_tool:
97                # buildifier: disable=print
98                print((
99                    "WARNING: ignoring register_coverage_tool=True when " +
100                    "registering @{name}: Bazel 6+ required, got {version}"
101                ).format(
102                    name = name,
103                    version = native.bazel_version,
104                ))
105            register_coverage_tool = False
106
107    loaded_platforms = []
108    for platform in PLATFORMS.keys():
109        sha256 = tool_versions[python_version]["sha256"].get(platform, None)
110        if not sha256:
111            continue
112
113        loaded_platforms.append(platform)
114        (release_filename, urls, strip_prefix, patches, patch_strip) = get_release_info(platform, python_version, base_url, tool_versions)
115
116        # allow passing in a tool version
117        coverage_tool = None
118        coverage_tool = tool_versions[python_version].get("coverage_tool", {}).get(platform, None)
119        if register_coverage_tool and coverage_tool == None:
120            coverage_tool = coverage_dep(
121                name = "{name}_{platform}_coverage".format(
122                    name = name,
123                    platform = platform,
124                ),
125                python_version = python_version,
126                platform = platform,
127                visibility = ["@{name}_{platform}//:__subpackages__".format(
128                    name = name,
129                    platform = platform,
130                )],
131            )
132
133        python_repository(
134            name = "{name}_{platform}".format(
135                name = name,
136                platform = platform,
137            ),
138            sha256 = sha256,
139            patches = patches,
140            patch_strip = patch_strip,
141            platform = platform,
142            python_version = python_version,
143            release_filename = release_filename,
144            urls = urls,
145            strip_prefix = strip_prefix,
146            coverage_tool = coverage_tool,
147            **kwargs
148        )
149        if register_toolchains:
150            native.register_toolchains("@{toolchain_repo_name}//:{platform}_toolchain".format(
151                toolchain_repo_name = toolchain_repo_name,
152                platform = platform,
153            ))
154            native.register_toolchains("@{toolchain_repo_name}//:{platform}_py_cc_toolchain".format(
155                toolchain_repo_name = toolchain_repo_name,
156                platform = platform,
157            ))
158            native.register_toolchains("@{toolchain_repo_name}//:{platform}_py_exec_tools_toolchain".format(
159                toolchain_repo_name = toolchain_repo_name,
160                platform = platform,
161            ))
162
163    host_toolchain(
164        name = name + "_host",
165        platforms = loaded_platforms,
166        python_version = python_version,
167    )
168
169    toolchain_aliases(
170        name = name,
171        python_version = python_version,
172        user_repository_name = name,
173        platforms = loaded_platforms,
174    )
175
176    # in bzlmod we write out our own toolchain repos
177    if bzlmod_toolchain_call:
178        return loaded_platforms
179
180    toolchains_repo(
181        name = toolchain_repo_name,
182        python_version = python_version,
183        set_python_version_constraint = set_python_version_constraint,
184        user_repository_name = name,
185        platforms = loaded_platforms,
186    )
187    return None
188