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