Skip to content

Commit 01719b2

Browse files
committed
feat(pip.parse): pip_config_settings now generates config settings for the whls and they are hooked up to the old render_pkg_aliases
1 parent 98a4858 commit 01719b2

File tree

6 files changed

+320
-21
lines changed

6 files changed

+320
-21
lines changed

python/pip_install/pip_repository.bzl

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -265,13 +265,6 @@ def _create_repository_execution_environment(rctx, python_interpreter):
265265

266266
return env
267267

268-
_BUILD_FILE_CONTENTS = """\
269-
package(default_visibility = ["//visibility:public"])
270-
271-
# Ensure the `requirements.bzl` source can be accessed by stardoc, since users load() from it
272-
exports_files(["requirements.bzl"])
273-
"""
274-
275268
def _pip_repository_impl(rctx):
276269
requirements_by_platform = parse_requirements(
277270
rctx,
@@ -373,7 +366,6 @@ def _pip_repository_impl(rctx):
373366
for path, contents in aliases.items():
374367
rctx.file(path, contents)
375368

376-
rctx.file("BUILD.bazel", _BUILD_FILE_CONTENTS)
377369
rctx.template("requirements.bzl", rctx.attr._template, substitutions = {
378370
" # %%GROUP_LIBRARY%%": """\
379371
group_repo = "{name}__groups"

python/private/bzlmod/pip.bzl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,6 @@ def _create_whl_repos(module_ctx, pip_attr, whl_map, whl_overrides, group_map, s
291291
whl_alias(
292292
repo = repo_name,
293293
version = major_minor,
294-
# Call Label() to canonicalize because its used in a different context
295-
config_setting = Label("//python/config_settings:is_python_" + major_minor),
296294
),
297295
)
298296

python/private/bzlmod/pip_repository.bzl

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,6 @@
1717
load("//python/private:render_pkg_aliases.bzl", "render_pkg_aliases", "whl_alias")
1818
load("//python/private:text_util.bzl", "render")
1919

20-
_BUILD_FILE_CONTENTS = """\
21-
package(default_visibility = ["//visibility:public"])
22-
23-
# Ensure the `requirements.bzl` source can be accessed by stardoc, since users load() from it
24-
exports_files(["requirements.bzl"])
25-
"""
26-
2720
def _pip_repository_impl(rctx):
2821
bzl_packages = rctx.attr.whl_map.keys()
2922
aliases = render_pkg_aliases(
@@ -43,7 +36,6 @@ def _pip_repository_impl(rctx):
4336
# `requirement`, et al. macros.
4437
macro_tmpl = "@@{name}//{{}}:{{}}".format(name = rctx.attr.name)
4538

46-
rctx.file("BUILD.bazel", _BUILD_FILE_CONTENTS)
4739
rctx.template("requirements.bzl", rctx.attr._template, substitutions = {
4840
"%%ALL_DATA_REQUIREMENTS%%": render.list([
4941
macro_tmpl.format(p, "data")

python/private/config_settings.bzl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,16 @@ def is_python_config_setting(name, *, python_version, reuse_conditions = None, *
103103
fail("The 'python_version' must be known to 'rules_python', choose from the values: {}".format(VERSION_FLAG_VALUES.keys()))
104104

105105
python_versions = VERSION_FLAG_VALUES[python_version]
106+
extra_flag_values = kwargs.pop("flag_values", {})
107+
if _PYTHON_VERSION_FLAG in extra_flag_values:
108+
fail("Cannot set '{}' in the flag values".format(_PYTHON_VERSION_FLAG))
109+
106110
if len(python_versions) == 1:
107111
native.config_setting(
108112
name = name,
109113
flag_values = {
110114
_PYTHON_VERSION_FLAG: python_version,
111-
},
115+
} | extra_flag_values,
112116
**kwargs
113117
)
114118
return
@@ -138,7 +142,7 @@ def is_python_config_setting(name, *, python_version, reuse_conditions = None, *
138142
for name_, flag_values_ in create_config_settings.items():
139143
native.config_setting(
140144
name = name_,
141-
flag_values = flag_values_,
145+
flag_values = flag_values_ | extra_flag_values,
142146
**kwargs
143147
)
144148

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
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+
"""
16+
This module is used to construct the config settings for selecting which distribution is used in the pip hub repository.
17+
18+
Bazel's selects work by selecting the most-specialized configuration setting
19+
that matches the target platform. We can leverage this fact to ensure that the
20+
most specialized wheels are used by default with the users being able to
21+
configure string_flag values to select the less specialized ones.
22+
23+
The list of specialization of the dists goes like follows:
24+
* sdist
25+
* -none-any.whl
26+
* -abi3-any.whl
27+
* -cpxy-any.whl
28+
* -none-plat.whl
29+
* -abi3-plat.whl
30+
* -cpxy-plat.whl
31+
32+
Note, that here the specialization of musl vs manylinux wheels is the same in
33+
order to not match an incompatible version in case the user specifies the version
34+
of the libc to match. By default we do not match against the libc version and pick
35+
some whl.
36+
37+
That should cover the majority of cases as only a single `whl`.
38+
39+
We also would like to ensure that we match the python version tag, it can be:
40+
- py2.py3 - all python 3 versions
41+
- py3 - all python 3 versions
42+
- cp38 - all CPython 3.8 and above
43+
- cp39 - all CPython 3.9 and above
44+
- cp310 - all CPython 3.10 and above
45+
46+
Use cases that this code strives to support:
47+
* sdist only - Setting the flag `:whl=no` should do the trick
48+
* whl only - Will be handled differently (i.e. filtering elsewhere). By default
49+
we will give preference to whls, so this should not be necessary most of the
50+
times.
51+
* select only pure python versions of wheels. This may not work all of the time
52+
unless building from `sdist` is disabled.
53+
* select to target a particular libc version
54+
* select to target a particular osx version
55+
"""
56+
57+
load(":config_settings.bzl", "is_python_config_setting")
58+
load(
59+
":pip_flags.bzl",
60+
"UniversalWhlFlag",
61+
"UseWhlAbi3Flag",
62+
"UseWhlAbiCpFlag",
63+
"UseWhlFlag",
64+
"UseWhlPlatformFlag",
65+
"WhlLibcFlag",
66+
)
67+
68+
_flags = struct(
69+
**{
70+
f: str(Label("//python/config_settings:" + f))
71+
for f in [
72+
"python_version",
73+
"pip_whl",
74+
"pip_whl_abi3",
75+
"pip_whl_cpxy",
76+
"pip_whl_glibc_version",
77+
"pip_whl_linux_libc",
78+
"pip_whl_muslc_version",
79+
"pip_whl_osx_arch",
80+
"pip_whl_osx_version",
81+
"pip_whl_plat",
82+
]
83+
}
84+
)
85+
86+
def pip_config_settings(
87+
name,
88+
python_versions,
89+
constraint_values,
90+
glibc_versions = None,
91+
muslc_versions = None,
92+
osx_versions = None,
93+
visibility = None):
94+
"""Create string flags for dist configuration.
95+
96+
Args:
97+
name: Currently unused.
98+
python_versions: list[str] A list of python version to generate
99+
config_setting targets for.
100+
constraint_values: The dict[str, list[Label]] mapping platform labels
101+
to constraint_values passed to the `config_setting`.
102+
osx_versions: The list of osx versions that we need to support.
103+
glibc_versions: The list of glibc versions that we need to support.
104+
muslc_versions: The list of muslc versions that we need to support.
105+
visibility: The visibility of the flags that we create.
106+
"""
107+
presets = {
108+
name: {
109+
"constraint_values": cvs,
110+
"python_versions": python_versions,
111+
"visibility": visibility,
112+
}
113+
for name, cvs in ({"": None} | (constraint_values or {})).items()
114+
}
115+
116+
# Creating config_setting values so that we can
117+
# * sdist - use if this exists or disable usage of it.
118+
# * none-any.whl - use if this exists or use only these in case we need pure python whls.
119+
# * abi3-any.whl - use if this exists and prefer it over none whls
120+
# * cp3x-any.whl - use if this exists and prefer it over abi3 and none whls
121+
for plat, config_settings_args in presets.items():
122+
# A different way to model this would be to ask the user to explicitly allow sdists
123+
# - use only whls (do not register sdist, should be a flag to pip.parse)
124+
# - fallback to sdist (default)
125+
# - use only sdist (disable whls) (flag)
126+
for prefix, flag_values in {
127+
"": {},
128+
"sdist": {_flags.pip_whl: UseWhlFlag.NO},
129+
}.items():
130+
_config_settings(
131+
name = [prefix, plat],
132+
flag_values = flag_values,
133+
# in theory this can only work with the host platform as we
134+
# don't support cross-building the wheels from sdist. If we did
135+
# support that, then it could be different. However, we need
136+
# the `constraint_values` on os and arch to support the case
137+
# where we have different versions on different platforms,
138+
# because we are supplying different requirement files.
139+
**config_settings_args
140+
)
141+
142+
for prefix, flag_values in {
143+
# All of these fallback to sdist
144+
"whl_none": {_flags.pip_whl: UseWhlFlag.AUTO},
145+
"whl_abi3": {_flags.pip_whl: UseWhlFlag.AUTO, _flags.pip_whl_abi3: UseWhlAbi3Flag.AUTO},
146+
"whl_cpxy": {_flags.pip_whl: UseWhlFlag.AUTO, _flags.pip_whl_abi3: UseWhlAbi3Flag.AUTO, _flags.pip_whl_cpxy: UseWhlAbiCpFlag.AUTO},
147+
}.items(): # buildifier: disable=unsorted-dict-items as they have meaning
148+
# [none|abi3|cp]-any.whl
149+
# the platform suffix is for the cases when we have different dists
150+
# on different platforms.
151+
_config_settings(
152+
name = [prefix, "any", plat],
153+
flag_values = flag_values,
154+
**config_settings_args
155+
)
156+
157+
if plat == "":
158+
# We are dealing with platform-specific wheels, so the default
159+
# platform does not make sense here.
160+
continue
161+
elif "windows" in plat:
162+
# [none|abi3|cp]-plat.whl
163+
_config_settings(
164+
name = [prefix, plat],
165+
flag_values = {
166+
_flags.pip_whl_plat: UseWhlPlatformFlag.AUTO,
167+
} | flag_values,
168+
**config_settings_args
169+
)
170+
elif "osx" in plat:
171+
# [none|abi3|cp]-macosx-arch.whl
172+
for name, whl_osx_arch in {
173+
plat: UniversalWhlFlag.ARCH,
174+
plat + "_universal2": UniversalWhlFlag.UNIVERSAL,
175+
}.items():
176+
_config_settings(
177+
name = [prefix, name],
178+
flag_values = {
179+
_flags.pip_whl_osx_arch: whl_osx_arch,
180+
_flags.pip_whl_plat: UseWhlPlatformFlag.AUTO,
181+
} | flag_values,
182+
**config_settings_args
183+
)
184+
elif "linux" in plat:
185+
# [none|abi3|cp]-[|many|musl]linux_arch.whl
186+
for name, whl_linux_libc in {
187+
plat: None,
188+
"many" + plat: WhlLibcFlag.GLIBC,
189+
"musl" + plat: WhlLibcFlag.MUSL,
190+
}.items():
191+
_config_settings(
192+
name = [prefix, name],
193+
flag_values = {
194+
_flags.pip_whl_linux_libc: whl_linux_libc,
195+
_flags.pip_whl_plat: UseWhlPlatformFlag.AUTO,
196+
} | flag_values,
197+
**config_settings_args
198+
)
199+
else:
200+
fail("unknown platform: '{}'".format(plat))
201+
202+
# If we select a whl that is with a platform tag including `1.1`, that
203+
# means that the whl will work on a platform where the libc is 1.1 or later
204+
# (https://peps.python.org/pep-0600/#core-definition). As such, if the user
205+
# wants to target a platform that is 1.3, then a whl with `1.1` version
206+
# should be selected if there is no higher version. This means that the
207+
# alias can be like:
208+
#
209+
# alias(
210+
# name = "whl_matching_1.2_or_lower"
211+
# actual = select(
212+
# {
213+
# "flag_eq_1.2_or_above": "actual_whl_1.1",
214+
# "flag_eq_1.1": "actual_whl_1.1",
215+
# "flag_eq_1.0": "actual_whl_1.0",
216+
# },
217+
# no_match_error = "Could not match, the target supports versions 1.2, 1.1 and 1.0", but you have selected something outside this range.
218+
# )
219+
220+
for (version_name, flag, versions) in [
221+
("glibc", _flags.pip_whl_glibc_version, glibc_versions),
222+
("muslc", _flags.pip_whl_muslc_version, muslc_versions),
223+
("osx", _flags.pip_whl_osx_version, osx_versions),
224+
]:
225+
if not versions:
226+
continue
227+
228+
# Because we want to have a no-match error, this has to be separate from other constraints.
229+
native.config_setting(
230+
name = "is_{}_default".format(version_name),
231+
flag_values = {flag: ""},
232+
visibility = visibility,
233+
)
234+
for version in versions:
235+
native.config_setting(
236+
name = "is_{}_{}".format(version_name, version),
237+
flag_values = {flag: version},
238+
visibility = visibility,
239+
)
240+
241+
def _config_settings(
242+
*,
243+
name,
244+
python_versions,
245+
flag_values,
246+
constraint_values = None,
247+
**kwargs):
248+
# TODO @aignas 2024-05-24: this "auto" may need to go
249+
name = "_".join(["is"] + [n for n in name if n and n != "auto"])
250+
251+
flag_values = {
252+
k: v
253+
for k, v in flag_values.items()
254+
if v != None
255+
}
256+
257+
if flag_values or constraint_values:
258+
native.config_setting(
259+
name = name,
260+
# NOTE @aignas 2024-04-25: we need to add this so that we ensure that
261+
# all config settings have the same number of flag values so that
262+
# the different wheel arches are specializations of each other and
263+
# so that the modelling works fully.
264+
flag_values = flag_values | {_flags.python_version: ""},
265+
constraint_values = constraint_values,
266+
**kwargs
267+
)
268+
269+
for python_version in python_versions:
270+
is_python_config_setting(
271+
name = "is_python_{}{}".format(python_version, name[len("is"):]),
272+
python_version = python_version,
273+
flag_values = flag_values,
274+
constraint_values = constraint_values,
275+
**kwargs
276+
)

0 commit comments

Comments
 (0)