Skip to content

Commit 02591a5

Browse files
aignasrickeylev
andauthored
refactor(whl_library): reimplement wheel platform parsing in starlark (#1636)
It seems that the CI did not catch the `macosx_10_9_universal2` wheels edge case, which this new code is handling. I moved the implementation to starlark because #1625 needs this. No changelog is needed because the feature this is fixing/refactoring an unreleased feature. --------- Co-authored-by: Richard Levasseur <[email protected]>
1 parent df234d9 commit 02591a5

File tree

8 files changed

+173
-60
lines changed

8 files changed

+173
-60
lines changed

python/pip_install/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ bzl_library(
3131
"//python/pip_install/private:srcs_bzl",
3232
"//python/private:bzlmod_enabled_bzl",
3333
"//python/private:normalize_name_bzl",
34+
"//python/private:parse_whl_name_bzl",
3435
"//python/private:patch_whl_bzl",
3536
"//python/private:render_pkg_aliases_bzl",
3637
"//python/private:toolchains_repo_bzl",
3738
"//python/private:which_bzl",
39+
"//python/private:whl_target_platforms_bzl",
3840
"@bazel_skylib//lib:sets",
3941
],
4042
)

python/pip_install/pip_repository.bzl

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ load("//python/pip_install/private:generate_whl_library_build_bazel.bzl", "gener
2424
load("//python/pip_install/private:srcs.bzl", "PIP_INSTALL_PY_SRCS")
2525
load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
2626
load("//python/private:normalize_name.bzl", "normalize_name")
27+
load("//python/private:parse_whl_name.bzl", "parse_whl_name")
2728
load("//python/private:patch_whl.bzl", "patch_whl")
2829
load("//python/private:render_pkg_aliases.bzl", "render_pkg_aliases")
2930
load("//python/private:toolchains_repo.bzl", "get_host_os_arch")
3031
load("//python/private:which.bzl", "which_with_fail")
32+
load("//python/private:whl_target_platforms.bzl", "whl_target_platforms")
3133

3234
CPPFLAGS = "CPPFLAGS"
3335

@@ -743,11 +745,22 @@ def _whl_library_impl(rctx):
743745
timeout = rctx.attr.timeout,
744746
)
745747

748+
target_platforms = rctx.attr.experimental_target_platforms
749+
if target_platforms:
750+
parsed_whl = parse_whl_name(whl_path.basename)
751+
if parsed_whl.platform_tag != "any":
752+
# NOTE @aignas 2023-12-04: if the wheel is a platform specific
753+
# wheel, we only include deps for that target platform
754+
target_platforms = [
755+
"{}_{}".format(p.os, p.cpu)
756+
for p in whl_target_platforms(parsed_whl.platform_tag)
757+
]
758+
746759
result = rctx.execute(
747760
args + [
748761
"--whl-file",
749762
whl_path,
750-
] + ["--platform={}".format(p) for p in rctx.attr.experimental_target_platforms],
763+
] + ["--platform={}".format(p) for p in target_platforms],
751764
environment = environment,
752765
quiet = rctx.attr.quiet,
753766
timeout = rctx.attr.timeout,

python/pip_install/tools/wheel_installer/wheel.py

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,6 @@ class OS(Enum):
3636
darwin = osx
3737
win32 = windows
3838

39-
@staticmethod
40-
def from_tag(tag: str) -> "OS":
41-
if tag.startswith("linux"):
42-
return OS.linux
43-
elif tag.startswith("manylinux"):
44-
return OS.linux
45-
elif tag.startswith("musllinux"):
46-
return OS.linux
47-
elif tag.startswith("macos"):
48-
return OS.osx
49-
elif tag.startswith("win"):
50-
return OS.windows
51-
else:
52-
raise ValueError(f"unknown tag: {tag}")
53-
5439

5540
class Arch(Enum):
5641
x86_64 = 1
@@ -65,17 +50,6 @@ class Arch(Enum):
6550
x86 = x86_32
6651
ppc64le = ppc
6752

68-
@staticmethod
69-
def from_tag(tag: str) -> "Arch":
70-
for s, value in Arch.__members__.items():
71-
if s in tag:
72-
return value
73-
74-
if tag == "win32":
75-
return Arch.x86_32
76-
else:
77-
raise ValueError(f"unknown tag: {tag}")
78-
7953

8054
@dataclass(frozen=True)
8155
class Platform:
@@ -142,13 +116,6 @@ def __str__(self) -> str:
142116

143117
return self.os.name.lower() + "_" + self.arch.name.lower()
144118

145-
@classmethod
146-
def from_tag(cls, tag: str) -> "Platform":
147-
return cls(
148-
os=OS.from_tag(tag),
149-
arch=Arch.from_tag(tag),
150-
)
151-
152119
@classmethod
153120
def from_string(cls, platform: Union[str, List[str]]) -> List["Platform"]:
154121
"""Parse a string and return a list of platforms"""
@@ -462,17 +429,6 @@ def dependencies(
462429
extras_requested: Set[str] = None,
463430
platforms: Optional[Set[Platform]] = None,
464431
) -> FrozenDeps:
465-
if platforms:
466-
# NOTE @aignas 2023-12-04: if the wheel is a platform specific wheel, we only include deps for that platform
467-
_, _, platform_tag = self._path.name.rpartition("-")
468-
platform_tag = platform_tag[:-4] # strip .whl
469-
if platform_tag != "any":
470-
platform = Platform.from_tag(platform_tag)
471-
assert (
472-
platform in platforms
473-
), f"BUG: wheel platform '{platform}' must be one of '{platforms}'"
474-
platforms = {platform}
475-
476432
dependency_set = Deps(
477433
self.name,
478434
extras=extras_requested,

python/pip_install/tools/wheel_installer/wheel_test.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -199,21 +199,6 @@ def test_handle_etils(self):
199199

200200

201201
class PlatformTest(unittest.TestCase):
202-
def test_platform_from_string(self):
203-
tests = {
204-
"win_amd64": "windows_x86_64",
205-
"macosx_10_9_arm64": "osx_aarch64",
206-
"manylinux1_i686.manylinux_2_17_i686": "linux_x86_32",
207-
"musllinux_1_1_ppc64le": "linux_ppc",
208-
}
209-
210-
for give, want in tests.items():
211-
with self.subTest(give=give, want=want):
212-
self.assertEqual(
213-
wheel.Platform.from_string(want)[0],
214-
wheel.Platform.from_tag(give),
215-
)
216-
217202
def test_can_get_host(self):
218203
host = wheel.Platform.host()
219204
self.assertIsNotNone(host)

python/private/BUILD.bazel

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,12 @@ bzl_library(
238238
],
239239
)
240240

241+
bzl_library(
242+
name = "whl_target_platforms_bzl",
243+
srcs = ["whl_target_platforms.bzl"],
244+
visibility = ["//:__subpackages__"],
245+
)
246+
241247
bzl_library(
242248
name = "labels_bzl",
243249
srcs = ["labels.bzl"],
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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+
"""
16+
A starlark implementation of the wheel platform tag parsing to get the target platform.
17+
"""
18+
19+
# The order of the dictionaries is to keep definitions with their aliases next to each
20+
# other
21+
_CPU_ALIASES = {
22+
"x86_32": "x86_32",
23+
"i386": "x86_32",
24+
"i686": "x86_32",
25+
"x86": "x86_32",
26+
"x86_64": "x86_64",
27+
"amd64": "x86_64",
28+
"aarch64": "aarch64",
29+
"arm64": "aarch64",
30+
"ppc": "ppc",
31+
"ppc64le": "ppc",
32+
"s390x": "s390x",
33+
} # buildifier: disable=unsorted-dict-items
34+
35+
_OS_PREFIXES = {
36+
"linux": "linux",
37+
"manylinux": "linux",
38+
"musllinux": "linux",
39+
"macos": "osx",
40+
"win": "windows",
41+
} # buildifier: disable=unsorted-dict-items
42+
43+
def whl_target_platforms(tag):
44+
"""Parse the wheel platform tag and return (os, cpu) tuples.
45+
46+
Args:
47+
tag (str): The platform_tag part of the wheel name. See
48+
./parse_whl_name.bzl for more details.
49+
50+
Returns:
51+
A list of structs, with attributes:
52+
* os: str, one of the _OS_PREFIXES values
53+
* cpu: str, one of the _CPU_PREFIXES values
54+
"""
55+
cpus = _cpu_from_tag(tag)
56+
57+
for prefix, os in _OS_PREFIXES.items():
58+
if tag.startswith(prefix):
59+
return [
60+
struct(os = os, cpu = cpu)
61+
for cpu in cpus
62+
]
63+
64+
fail("unknown tag os: {}".format(tag))
65+
66+
def _cpu_from_tag(tag):
67+
candidate = [
68+
cpu
69+
for input, cpu in _CPU_ALIASES.items()
70+
if tag.endswith(input)
71+
]
72+
if candidate:
73+
return candidate
74+
75+
if tag == "win32":
76+
return ["x86_32"]
77+
elif tag.endswith("universal2") and tag.startswith("macosx"):
78+
return ["x86_64", "aarch64"]
79+
else:
80+
fail("Unrecognized tag: '{}': cannot determine CPU".format(tag))
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
load(":whl_target_platforms_tests.bzl", "whl_target_platforms_test_suite")
16+
17+
whl_target_platforms_test_suite(name = "whl_target_platforms_tests")
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
""
16+
17+
load("@rules_testing//lib:test_suite.bzl", "test_suite")
18+
load("//python/private:whl_target_platforms.bzl", "whl_target_platforms") # buildifier: disable=bzl-visibility
19+
20+
_tests = []
21+
22+
def _test_simple(env):
23+
tests = {
24+
"macosx_10_9_arm64": [
25+
struct(os = "osx", cpu = "aarch64"),
26+
],
27+
"macosx_10_9_universal2": [
28+
struct(os = "osx", cpu = "x86_64"),
29+
struct(os = "osx", cpu = "aarch64"),
30+
],
31+
"manylinux1_i686.manylinux_2_17_i686": [
32+
struct(os = "linux", cpu = "x86_32"),
33+
],
34+
"musllinux_1_1_ppc64le": [
35+
struct(os = "linux", cpu = "ppc"),
36+
],
37+
"win_amd64": [
38+
struct(os = "windows", cpu = "x86_64"),
39+
],
40+
}
41+
42+
for give, want in tests.items():
43+
got = whl_target_platforms(give)
44+
env.expect.that_collection(got).contains_exactly(want)
45+
46+
_tests.append(_test_simple)
47+
48+
def whl_target_platforms_test_suite(name):
49+
"""Create the test suite.
50+
51+
Args:
52+
name: the name of the test suite
53+
"""
54+
test_suite(name = name, basic_tests = _tests)

0 commit comments

Comments
 (0)