Skip to content

Commit 9af5ba0

Browse files
committed
move select_whl function to a separate file
1 parent 02453f2 commit 9af5ba0

File tree

7 files changed

+166
-175
lines changed

7 files changed

+166
-175
lines changed

python/private/pypi/BUILD.bazel

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ bzl_library(
120120
":whl_config_setting_bzl",
121121
":whl_library_bzl",
122122
":whl_repo_name_bzl",
123-
":whl_target_platforms_bzl",
124123
"//python/private:full_version_bzl",
125124
"//python/private:normalize_name_bzl",
126125
"//python/private:version_bzl",
@@ -209,7 +208,7 @@ bzl_library(
209208
":parse_requirements_txt_bzl",
210209
":pypi_repo_utils_bzl",
211210
":requirements_files_by_platform_bzl",
212-
":whl_target_platforms_bzl",
211+
":select_whl_bzl",
213212
"//python/private:normalize_name_bzl",
214213
"//python/private:repo_utils_bzl",
215214
],
@@ -359,6 +358,15 @@ bzl_library(
359358
],
360359
)
361360

361+
bzl_library(
362+
name = "select_whl_bzl",
363+
srcs = ["select_whl.bzl"],
364+
deps = [
365+
":parse_whl_name_bzl",
366+
"//python/private:version_bzl",
367+
],
368+
)
369+
362370
bzl_library(
363371
name = "simpleapi_download_bzl",
364372
srcs = ["simpleapi_download.bzl"],
@@ -422,8 +430,4 @@ bzl_library(
422430
bzl_library(
423431
name = "whl_target_platforms_bzl",
424432
srcs = ["whl_target_platforms.bzl"],
425-
deps = [
426-
":parse_whl_name_bzl",
427-
"//python/private:version_bzl",
428-
],
429433
)

python/private/pypi/parse_requirements.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ load("//python/private:repo_utils.bzl", "repo_utils")
3131
load(":index_sources.bzl", "index_sources")
3232
load(":parse_requirements_txt.bzl", "parse_requirements_txt")
3333
load(":pep508_requirement.bzl", "requirement")
34-
load(":whl_target_platforms.bzl", "select_whl")
34+
load(":select_whl.bzl", "select_whl")
3535

3636
def parse_requirements(
3737
ctx,

python/private/pypi/select_whl.bzl

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"Select a single wheel that fits the parameters of a target platform."
2+
3+
load("//python/private:version.bzl", "version")
4+
load(":parse_whl_name.bzl", "parse_whl_name")
5+
6+
def _get_priority(*, tag, values, allow_wildcard = True):
7+
for priority, wp in enumerate(values):
8+
# TODO @aignas 2025-06-16: move the matcher validation out of this
9+
# TODO @aignas 2025-06-21: test the 'cp*' matching
10+
head, sep, tail = wp.partition("*")
11+
if "*" in tail:
12+
fail("only a single '*' can be present in the matcher")
13+
if not allow_wildcard and sep:
14+
fail("'*' is not allowed in the matcher")
15+
16+
for p in tag.split("."):
17+
if not sep and p == head:
18+
return priority
19+
elif sep and p.startswith(head) and p.endswith(tail):
20+
return priority
21+
22+
return None
23+
24+
def select_whl(*, whls, python_version, platforms, want_abis, implementation = "cp", limit = 1, logger = None):
25+
"""Select a whl that is the most suitable for the given platform.
26+
27+
Args:
28+
whls: {type}`list[struct]` a list of candidates which have a `filename`
29+
attribute containing the `whl` filename.
30+
python_version: {type}`str` the target python version.
31+
platforms: {type}`list[str]` the target platform identifiers that may contain
32+
a single `*` character.
33+
implementation: {type}`str` TODO
34+
want_abis: {type}`str` TODO
35+
limit: {type}`int` number of wheels to return. Defaults to 1.
36+
logger: {type}`struct` the logger instance.
37+
38+
Returns:
39+
{type}`list[struct] | struct | None`, a single struct from the `whls` input
40+
argument or `None` if a match is not found. If the `limit` is greater than
41+
one, then we will return a list.
42+
"""
43+
py_version = version.parse(python_version, strict = True)
44+
45+
# Get the minor version instead
46+
# TODO @aignas 2025-06-27: do this more efficiently
47+
py_version = version.parse("{0}.{1}".format(*py_version.release), strict = True)
48+
candidates = {}
49+
50+
for whl in whls:
51+
parsed = parse_whl_name(whl.filename)
52+
53+
suffix = ""
54+
if parsed.abi_tag.startswith(implementation):
55+
v = parsed.abi_tag[2:]
56+
min_whl_py_version = version.parse(
57+
"{}.{}".format(v[0], v[1:].strip("tmu")),
58+
strict = False,
59+
)
60+
if not min_whl_py_version:
61+
if logger:
62+
logger.warn(lambda: "Discarding the wheel ('{}') because we could not parse the version from the abi tag".format(whl.filename))
63+
continue
64+
65+
if parsed.abi_tag.endswith("t"):
66+
suffix = "t"
67+
68+
if not version.is_eq(py_version, min_whl_py_version):
69+
if logger:
70+
logger.debug(lambda: "Discarding the wheel ('{}') because the min version supported based on the wheel ABI tag '{}' ({}) is not compatible with the provided target Python version '{}'".format(
71+
whl.filename,
72+
parsed.abi_tag,
73+
min_whl_py_version.string,
74+
py_version.string,
75+
))
76+
continue
77+
else:
78+
if parsed.python_tag.startswith("py"):
79+
pass
80+
elif not parsed.python_tag.startswith(implementation):
81+
if logger:
82+
logger.debug(lambda: "Discarding the wheel because the implementation '{}' is not compatible with target implementation '{}'".format(
83+
parsed.python_tag,
84+
implementation,
85+
))
86+
continue
87+
88+
if parsed.python_tag == "py2.py3":
89+
min_version = "2"
90+
else:
91+
min_version = parsed.python_tag[2:]
92+
93+
if len(min_version) > 1:
94+
min_version = "{}.{}".format(min_version[0], min_version[1:])
95+
96+
min_whl_py_version = version.parse(min_version, strict = True)
97+
if not version.is_ge(py_version, min_whl_py_version):
98+
if logger:
99+
logger.debug(lambda: "Discarding the wheel because the min version supported based on the wheel ABI tag '{}' ({}) is not compatible with the provided target Python version '{}'".format(
100+
parsed.abi_tag,
101+
min_whl_py_version.string,
102+
py_version.string,
103+
))
104+
continue
105+
106+
abi_priority = _get_priority(
107+
tag = parsed.abi_tag,
108+
values = want_abis,
109+
allow_wildcard = False,
110+
)
111+
if abi_priority == None:
112+
if logger:
113+
logger.debug(lambda: "The abi '{}' does not match given list: {}".format(
114+
parsed.abi_tag,
115+
want_abis,
116+
))
117+
continue
118+
platform_priority = _get_priority(
119+
tag = parsed.platform_tag,
120+
values = platforms,
121+
)
122+
if platform_priority == None:
123+
if logger:
124+
logger.debug(lambda: "The platform_tag '{}' does not match given list: {}".format(
125+
parsed.platform_tag,
126+
platforms,
127+
))
128+
continue
129+
130+
key = (
131+
# Ensure that we chose the highest compatible version
132+
parsed.python_tag.startswith(implementation),
133+
platform_priority,
134+
# prefer abi_tags in this order
135+
version.key(min_whl_py_version),
136+
abi_priority,
137+
suffix,
138+
)
139+
candidates.setdefault(key, whl)
140+
141+
if not candidates:
142+
return None
143+
144+
sorted_candidates = [i[1] for i in sorted(candidates.items())]
145+
if logger:
146+
logger.debug(lambda: "Sorted candidates:\n{}".format(
147+
"\n".join([c.filename for c in sorted_candidates]),
148+
))
149+
results = sorted_candidates[-limit:] if sorted_candidates else None
150+
151+
return results[-1] if limit == 1 else results

python/private/pypi/whl_target_platforms.bzl

Lines changed: 0 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616
A starlark implementation of the wheel platform tag parsing to get the target platform.
1717
"""
1818

19-
load("//python/private:version.bzl", "version")
20-
load(":parse_whl_name.bzl", "parse_whl_name")
21-
2219
# The order of the dictionaries is to keep definitions with their aliases next to each
2320
# other
2421
_CPU_ALIASES = {
@@ -47,153 +44,6 @@ _OS_PREFIXES = {
4744
"win": "windows",
4845
} # buildifier: disable=unsorted-dict-items
4946

50-
def _get_priority(*, tag, values, allow_wildcard = True):
51-
for priority, wp in enumerate(values):
52-
# TODO @aignas 2025-06-16: move the matcher validation out of this
53-
# TODO @aignas 2025-06-21: test the 'cp*' matching
54-
head, sep, tail = wp.partition("*")
55-
if "*" in tail:
56-
fail("only a single '*' can be present in the matcher")
57-
if not allow_wildcard and sep:
58-
fail("'*' is not allowed in the matcher")
59-
60-
for p in tag.split("."):
61-
if not sep and p == head:
62-
return priority
63-
elif sep and p.startswith(head) and p.endswith(tail):
64-
return priority
65-
66-
return None
67-
68-
def select_whl(*, whls, python_version, platforms, want_abis, implementation = "cp", limit = 1, logger = None):
69-
"""Select a whl that is the most suitable for the given platform.
70-
71-
Args:
72-
whls: {type}`list[struct]` a list of candidates which have a `filename`
73-
attribute containing the `whl` filename.
74-
python_version: {type}`str` the target python version.
75-
platforms: {type}`list[str]` the target platform identifiers that may contain
76-
a single `*` character.
77-
implementation: {type}`str` TODO
78-
want_abis: {type}`str` TODO
79-
limit: {type}`int` number of wheels to return. Defaults to 1.
80-
logger: {type}`struct` the logger instance.
81-
82-
Returns:
83-
{type}`list[struct] | struct | None`, a single struct from the `whls` input
84-
argument or `None` if a match is not found. If the `limit` is greater than
85-
one, then we will return a list.
86-
"""
87-
py_version = version.parse(python_version, strict = True)
88-
89-
# Get the minor version instead
90-
# TODO @aignas 2025-06-27: do this more efficiently
91-
py_version = version.parse("{0}.{1}".format(*py_version.release), strict = True)
92-
candidates = {}
93-
94-
for whl in whls:
95-
parsed = parse_whl_name(whl.filename)
96-
97-
suffix = ""
98-
if parsed.abi_tag.startswith(implementation):
99-
v = parsed.abi_tag[2:]
100-
min_whl_py_version = version.parse(
101-
"{}.{}".format(v[0], v[1:].strip("tmu")),
102-
strict = False,
103-
)
104-
if not min_whl_py_version:
105-
if logger:
106-
logger.warn(lambda: "Discarding the wheel ('{}') because we could not parse the version from the abi tag".format(whl.filename))
107-
continue
108-
109-
if parsed.abi_tag.endswith("t"):
110-
suffix = "t"
111-
112-
if not version.is_eq(py_version, min_whl_py_version):
113-
if logger:
114-
logger.debug(lambda: "Discarding the wheel ('{}') because the min version supported based on the wheel ABI tag '{}' ({}) is not compatible with the provided target Python version '{}'".format(
115-
whl.filename,
116-
parsed.abi_tag,
117-
min_whl_py_version.string,
118-
py_version.string,
119-
))
120-
continue
121-
else:
122-
if parsed.python_tag.startswith("py"):
123-
pass
124-
elif not parsed.python_tag.startswith(implementation):
125-
if logger:
126-
logger.debug(lambda: "Discarding the wheel because the implementation '{}' is not compatible with target implementation '{}'".format(
127-
parsed.python_tag,
128-
implementation,
129-
))
130-
continue
131-
132-
if parsed.python_tag == "py2.py3":
133-
min_version = "2"
134-
else:
135-
min_version = parsed.python_tag[2:]
136-
137-
if len(min_version) > 1:
138-
min_version = "{}.{}".format(min_version[0], min_version[1:])
139-
140-
min_whl_py_version = version.parse(min_version, strict = True)
141-
if not version.is_ge(py_version, min_whl_py_version):
142-
if logger:
143-
logger.debug(lambda: "Discarding the wheel because the min version supported based on the wheel ABI tag '{}' ({}) is not compatible with the provided target Python version '{}'".format(
144-
parsed.abi_tag,
145-
min_whl_py_version.string,
146-
py_version.string,
147-
))
148-
continue
149-
150-
abi_priority = _get_priority(
151-
tag = parsed.abi_tag,
152-
values = want_abis,
153-
allow_wildcard = False,
154-
)
155-
if abi_priority == None:
156-
if logger:
157-
logger.debug(lambda: "The abi '{}' does not match given list: {}".format(
158-
parsed.abi_tag,
159-
want_abis,
160-
))
161-
continue
162-
platform_priority = _get_priority(
163-
tag = parsed.platform_tag,
164-
values = platforms,
165-
)
166-
if platform_priority == None:
167-
if logger:
168-
logger.debug(lambda: "The platform_tag '{}' does not match given list: {}".format(
169-
parsed.platform_tag,
170-
platforms,
171-
))
172-
continue
173-
174-
key = (
175-
# Ensure that we chose the highest compatible version
176-
parsed.python_tag.startswith(implementation),
177-
platform_priority,
178-
# prefer abi_tags in this order
179-
version.key(min_whl_py_version),
180-
abi_priority,
181-
suffix,
182-
)
183-
candidates.setdefault(key, whl)
184-
185-
if not candidates:
186-
return None
187-
188-
sorted_candidates = [i[1] for i in sorted(candidates.items())]
189-
if logger:
190-
logger.debug(lambda: "Sorted candidates:\n{}".format(
191-
"\n".join([c.filename for c in sorted_candidates]),
192-
))
193-
results = sorted_candidates[-limit:] if sorted_candidates else None
194-
195-
return results[-1] if limit == 1 else results
196-
19747
def whl_target_platforms(platform_tag, abi_tag = ""):
19848
"""Parse the wheel abi and platform tags and return (os, cpu) tuples.
19949

tests/pypi/select_whl/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
load(":select_whl_tests.bzl", "select_whl_test_suite")
2+
3+
select_whl_test_suite(name = "select_whl_tests")

tests/pypi/whl_target_platforms/select_whl_tests.bzl renamed to tests/pypi/select_whl/select_whl_tests.bzl

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,8 @@
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-
151
""
162

173
load("@rules_testing//lib:test_suite.bzl", "test_suite")
184
load("//python/private:repo_utils.bzl", "REPO_DEBUG_ENV_VAR", "REPO_VERBOSITY_ENV_VAR", "repo_utils") # buildifier: disable=bzl-visibility
19-
load("//python/private/pypi:whl_target_platforms.bzl", "select_whl") # buildifier: disable=bzl-visibility
5+
load("//python/private/pypi:select_whl.bzl", "select_whl") # buildifier: disable=bzl-visibility
206

217
WHL_LIST = [
228
"pkg-0.0.1-cp311-cp311-macosx_10_9_universal2.whl",

0 commit comments

Comments
 (0)