8000 feat: support alias rendering for python aware toolchain targets · bazel-contrib/rules_python@9ea1fc0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9ea1fc0

Browse files
committed
feat: support alias rendering for python aware toolchain targets
Move and extend the alias rendering utility function.
1 parent bb7004b commit 9ea1fc0

File tree

4 files changed

+409
-48
lines changed

4 files changed

+409
-48
lines changed

python/pip_install/pip_repository.bzl

Lines changed: 7 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ load("//python/pip_install:requirements_parser.bzl", parse_requirements = "parse
2121
load("//python/pip_install/private:srcs.bzl", "PIP_INSTALL_PY_SRCS")
2222
load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
2323
load("//python/private:normalize_name.bzl", "normalize_name")
24+
load("//python/private:render_pkg_aliases.bzl", "render_pkg_aliases")
2425
load("//python/private:toolchains_repo.bzl", "get_host_os_arch")
2526

2627
CPPFLAGS = "CPPFLAGS"
@@ -268,56 +269,12 @@ A requirements_lock attribute must be specified, or a platform-specific lockfile
268269
""")
269270
return requirements_txt
270271

271-
def _pkg_aliases(rctx, repo_name, bzl_packages):
272-
"""Create alias declarations for each python dependency.
273-
274-
The aliases should be appended to the pip_repository BUILD.bazel file. These aliases
275-
allow users to use requirement() without needed a corresponding `use_repo()` for each dep
276-
when using bzlmod.
277-
278-
Args:
279-
rctx: the repository context.
280-
repo_name: the repository name of the parent that is visible to the users.
281-
bzl_packages: the list of packages to setup.
282-
"""
283-
for name in bzl_packages:
284-
build_content = """package(default_visibility = ["//visibility:public"])
285-
286-
alias(
287-
name = "{name}",
288-
actual = "@{repo_name}_{dep}//:pkg",
289-
)
290-
291-
alias(
292-
name = "pkg",
293-
actual = "@{repo_name}_{dep}//:pkg",
294-
)
295-
296-
alias(
297-
name = "whl",
298-
actual = "@{repo_name}_{dep}//:whl",
299-
)
300-
301-
alias(
302-
name = "data",
303-
actual = "@{repo_name}_{dep}//:data",
304-
)
305-
306-
alias(
307-
name = "dist_info",
308-
actual = "@{repo_name}_{dep}//:dist_info",
309-
)
310-
""".format(
311-
name = name,
312-
repo_name = repo_name,
313-
dep = name,
314-
)
315-
rctx.file("{}/BUILD.bazel".format(name), build_content)
316-
317272
def _create_pip_repository_bzlmod(rctx, bzl_packages, requirements):
318273
repo_name = rctx.attr.repo_name
319274
build_contents = _BUILD_FILE_CONTENTS
320-
_pkg_aliases(rctx, repo_name, bzl_packages)
275+
aliases = render_pkg_aliases(repo_name = repo_name, bzl_packages = bzl_packages)
276+
for path, contents in aliases.items():
277+
rctx.file(path, contents)
321278

322279
# NOTE: we are using the canonical name with the double '@' in order to
323280
# always uniquely identify a repository, as the labels are being passed as
@@ -458,7 +415,9 @@ def _pip_repository_impl(rctx):
458415
config["python_interpreter_target"] = str(rctx.attr.python_interpreter_target)
459416

460417
if rctx.attr.incompatible_generate_aliases:
461-
_pkg_aliases(rctx, rctx.attr.name, bzl_packages)
418+
aliases = render_pkg_aliases(repo_name = rctx.attr.name, bzl_packages = bzl_packages)
419+
for path, contents in aliases.items():
420+
rctx.file(path, contents)
462421

463422
rctx.file("BUILD.bazel", _BUILD_FILE_CONTENTS)
464423
rctx.template("requirements.bzl", rctx.attr._template, substitutions = {

python/private/render_pkg_aliases.bzl

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
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+
17+
This is used in bzlmod and non-bzlmod setups."""
18+
19+
load("//python/private:normalize_name.bzl", "normalize_name")
20+
load(":version_label.bzl", "version_label")
21+
22+
def _indent(text, indent = " " * 4):
23+
if "\n" not in text:
24+
return indent + text
25+
26+
return "\n".join([indent + line for line in text.splitlines()])
27+
28+
def _render_alias(name, actual):
29+
return "\n".join([
30+
"alias(",
31+
_indent("name = \"{}\",".format(name)),
32+
_indent("actual = {},".format(actual)),
33+
")",
34+
])
35+
36+
def _render_select(selects, *, no_match_error = None):
37+
dict_str = "\n".join([
38+
"{",
39+
_indent("\n".join([
40+
"{}: {},".format(repr(k), repr(v))
41+
for k, v in selects.items()
42+
])),
43+
"}",
44+
])
45+
46+
if no_match_error:
47+
args = "\n".join([
48+
"",
49+
_indent(dict_str + ","),
50+
_indent("no_match_error = {},".format(repr(no_match_error))),
51+
"",
52+
])
53+
else:
54+
args = dict_str
55+
56+
return "select({})".format(args)
57+
58+
def _render_whl_library_alias(
59+
*,
60+
name,
61+
repo_name,
62+
dep,
63+
target,
64+
default_version,
65+
versions,
66+
rules_python):
67+
"""Render an alias for common targets
68+
69+
If the versions is passed, then the `rules_python` must be passed as well and
70+
an alias with a select statement based on the python version is going to be
71+
generated.
72+
"""
73+
if versions == None:
74+
return _render_alias(
75+
name = name,
76+
actual = repr("@{repo_name}_{dep}//:{target}".format(
77+
repo_name = repo_name,
78+
dep = dep,
79+
target = target,
80+
)),
81+
)
82+
83+
# Create the alias repositories which contains different select
84+
# statements These select statements point to the different pip
85+
# whls that are based on a specific version of Python.
86+
selects = {}
87+
for full_version in versions:
88+
condition = "@@{rules_python}//python/config_settings:is_python_{full_python_version}".format(
89+
rules_python = rules_python,
90+
full_python_version = full_version,
91+
)
92+
actual = "@{repo_name}_{version}_{dep}//:{target}".format(
93+
repo_name = repo_name,
94+
version = version_label(full_version),
95+
dep = dep,
96+
target = target,
97+
)
98+
selects[condition] = actual
99+
100+
if default_version:
101+
no_match_error = None
102+
default_actual = "@{repo_name}_{version}_{dep}//:{target}".format(
103+
repo_name = repo_name,
104+
version = version_label(default_version),
105+
dep = dep,
106+
target = target,
107+
)
108+
selects["//conditions:default"] = default_actual
109+
else:
110+
no_match_error = "PyPI package is only available for versions: {}".format(
111+
",".join(versions),
112+
)
113+
114+
return _render_alias(
115+
name = name,
116+
actual = _render_select(
117+
selects,
118+
no_match_error = no_match_error,
119+
),
120+
)
121+
122+
def _render_common_aliases(repo_name, name, versions = None, default_version = None, rules_python = None):
123+
return "\n\n".join([
124+
"""package(default_visibility = ["//visibility:public"])""",
125+
_render_alias(
126+
name = name,
127+
actual = repr(":pkg"),
128+
),
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+
def render_pkg_aliases(*, repo_name, bzl_packages = None, whl_map = None, rules_python = None, default_version = None):
143+
"""Create alias declarations for each PyPI package.
144+
145+
The aliases should be appended to the pip_repository BUILD.bazel file. These aliases
146+
allow users to use requirement() without needed a corresponding `use_repo()` for each dep
147+
when using bzlmod.
148+
149+
Args:
150+
repo_name: the repository name of the hub repository that is visible to the users that is
151+
also used as the prefix for the spoke repo names (e.g. "pip", "pypi").
152+
bzl_packages: the list of packages to setup, if not specified, whl_map.keys() will be used instead.
153+
whl_map: the whl_map for generating Python version aware aliases.
154+
default_version: the default version to be used for the aliases.
155+
rules_python: the name of the rules_python workspace.
156+
157+
Returns:
158+
A dict of file paths and their contents.
159+
"""
160+
if not bzl_packages and whl_map:
161+
bzl_packages = list(whl_map.keys())
162+
163+
contents = {}
164+
for name in bzl_packages:
165+
versions = None
166+
if whl_map != None:
167+
versions = whl_map[name]
168+
name = normalize_name(name)
169+
170+
filename = "{}/BUILD.bazel".format(name)
171+
contents[filename] = _render_common_aliases(
172+
repo_name = repo_name,
173+
name = name,
174+
versions = versions,
175+
rules_python = rules_python,
176+
default_version = default_version,
177+
).strip()
178+
179+
return contents
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
load(":render_pkg_aliases_test.bzl", "render_pkg_aliases_test_suite")
2+
3+
render_pkg_aliases_test_suite(name = "render_pkg_aliases_tests")

0 commit comments

Comments
 (0)
0