• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2023 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"""render_pkg_aliases is a function to generate BUILD.bazel contents used to create user-friendly aliases.
16
17This is used in bzlmod and non-bzlmod setups."""
18
19load("//python/private:normalize_name.bzl", "normalize_name")
20load(":text_util.bzl", "render")
21load(":version_label.bzl", "version_label")
22
23NO_MATCH_ERROR_MESSAGE_TEMPLATE = """\
24No matching wheel for current configuration's Python version.
25
26The current build configuration's Python version doesn't match any of the Python
27versions available for this wheel. This wheel supports the following Python versions:
28    {supported_versions}
29
30As matched by the `@{rules_python}//python/config_settings:is_python_<version>`
31configuration settings.
32
33To determine the current configuration's Python version, run:
34    `bazel config <config id>` (shown further below)
35and look for
36    {rules_python}//python/config_settings:python_version
37
38If the value is missing, then the "default" Python version is being used,
39which has a "null" version value and will not match version constraints.
40"""
41
42def _render_whl_library_alias(
43        *,
44        name,
45        repo_name,
46        dep,
47        target,
48        default_version,
49        versions,
50        rules_python):
51    """Render an alias for common targets
52
53    If the versions is passed, then the `rules_python` must be passed as well and
54    an alias with a select statement based on the python version is going to be
55    generated.
56    """
57    if versions == None:
58        return render.alias(
59            name = name,
60            actual = repr("@{repo_name}_{dep}//:{target}".format(
61                repo_name = repo_name,
62                dep = dep,
63                target = target,
64            )),
65        )
66
67    # Create the alias repositories which contains different select
68    # statements  These select statements point to the different pip
69    # whls that are based on a specific version of Python.
70    selects = {}
71    for full_version in versions:
72        condition = "@@{rules_python}//python/config_settings:is_python_{full_python_version}".format(
73            rules_python = rules_python,
74            full_python_version = full_version,
75        )
76        actual = "@{repo_name}_{version}_{dep}//:{target}".format(
77            repo_name = repo_name,
78            version = version_label(full_version),
79            dep = dep,
80            target = target,
81        )
82        selects[condition] = actual
83
84    if default_version:
85        no_match_error = None
86        default_actual = "@{repo_name}_{version}_{dep}//:{target}".format(
87            repo_name = repo_name,
88            version = version_label(default_version),
89            dep = dep,
90            target = target,
91        )
92        selects["//conditions:default"] = default_actual
93    else:
94        no_match_error = "_NO_MATCH_ERROR"
95
96    return render.alias(
97        name = name,
98        actual = render.select(
99            selects,
100            no_match_error = no_match_error,
101        ),
102    )
103
104def _render_common_aliases(repo_name, name, versions = None, default_version = None, rules_python = None):
105    lines = [
106        """package(default_visibility = ["//visibility:public"])""",
107    ]
108
109    if versions:
110        versions = sorted(versions)
111
112    if versions and not default_version:
113        error_msg = NO_MATCH_ERROR_MESSAGE_TEMPLATE.format(
114            supported_versions = ", ".join(versions),
115            rules_python = rules_python,
116        )
117
118        lines.append("_NO_MATCH_ERROR = \"\"\"\\\n{error_msg}\"\"\"".format(
119            error_msg = error_msg,
120        ))
121
122    lines.append(
123        render.alias(
124            name = name,
125            actual = repr(":pkg"),
126        ),
127    )
128    lines.extend(
129        [
130            _render_whl_library_alias(
131                name = target,
132                repo_name = repo_name,
133                dep = name,
134                target = target,
135                versions = versions,
136                default_version = default_version,
137                rules_python = rules_python,
138            )
139            for target in ["pkg", "whl", "data", "dist_info"]
140        ],
141    )
142
143    return "\n\n".join(lines)
144
145def render_pkg_aliases(*, repo_name, bzl_packages = None, whl_map = None, rules_python = None, default_version = None):
146    """Create alias declarations for each PyPI package.
147
148    The aliases should be appended to the pip_repository BUILD.bazel file. These aliases
149    allow users to use requirement() without needed a corresponding `use_repo()` for each dep
150    when using bzlmod.
151
152    Args:
153        repo_name: the repository name of the hub repository that is visible to the users that is
154            also used as the prefix for the spoke repo names (e.g. "pip", "pypi").
155        bzl_packages: the list of packages to setup, if not specified, whl_map.keys() will be used instead.
156        whl_map: the whl_map for generating Python version aware aliases.
157        default_version: the default version to be used for the aliases.
158        rules_python: the name of the rules_python workspace.
159
160    Returns:
161        A dict of file paths and their contents.
162    """
163    if not bzl_packages and whl_map:
164        bzl_packages = list(whl_map.keys())
165
166    contents = {}
167    for name in bzl_packages:
168        versions = None
169        if whl_map != None:
170            versions = whl_map[name]
171        name = normalize_name(name)
172
173        filename = "{}/BUILD.bazel".format(name)
174        contents[filename] = _render_common_aliases(
175            repo_name = repo_name,
176            name = name,
177            versions = versions,
178            rules_python = rules_python,
179            default_version = default_version,
180        ).strip()
181
182    return contents
183