xref: /aosp_15_r20/external/bazelbuild-rules_python/tests/pypi/whl_installer/wheel_test.py (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1import unittest
2from unittest import mock
3
4from python.private.pypi.whl_installer import wheel
5from python.private.pypi.whl_installer.platform import OS, Arch, Platform
6
7_HOST_INTERPRETER_FN = (
8    "python.private.pypi.whl_installer.wheel.host_interpreter_minor_version"
9)
10
11
12class DepsTest(unittest.TestCase):
13    def test_simple(self):
14        deps = wheel.Deps("foo", requires_dist=["bar"])
15
16        got = deps.build()
17
18        self.assertIsInstance(got, wheel.FrozenDeps)
19        self.assertEqual(["bar"], got.deps)
20        self.assertEqual({}, got.deps_select)
21
22    def test_can_add_os_specific_deps(self):
23        deps = wheel.Deps(
24            "foo",
25            requires_dist=[
26                "bar",
27                "an_osx_dep; sys_platform=='darwin'",
28                "posix_dep; os_name=='posix'",
29                "win_dep; os_name=='nt'",
30            ],
31            platforms={
32                Platform(os=OS.linux, arch=Arch.x86_64),
33                Platform(os=OS.osx, arch=Arch.x86_64),
34                Platform(os=OS.osx, arch=Arch.aarch64),
35                Platform(os=OS.windows, arch=Arch.x86_64),
36            },
37        )
38
39        got = deps.build()
40
41        self.assertEqual(["bar"], got.deps)
42        self.assertEqual(
43            {
44                "@platforms//os:linux": ["posix_dep"],
45                "@platforms//os:osx": ["an_osx_dep", "posix_dep"],
46                "@platforms//os:windows": ["win_dep"],
47            },
48            got.deps_select,
49        )
50
51    def test_can_add_os_specific_deps_with_specific_python_version(self):
52        deps = wheel.Deps(
53            "foo",
54            requires_dist=[
55                "bar",
56                "an_osx_dep; sys_platform=='darwin'",
57                "posix_dep; os_name=='posix'",
58                "win_dep; os_name=='nt'",
59            ],
60            platforms={
61                Platform(os=OS.linux, arch=Arch.x86_64, minor_version=8),
62                Platform(os=OS.osx, arch=Arch.x86_64, minor_version=8),
63                Platform(os=OS.osx, arch=Arch.aarch64, minor_version=8),
64                Platform(os=OS.windows, arch=Arch.x86_64, minor_version=8),
65            },
66        )
67
68        got = deps.build()
69
70        self.assertEqual(["bar"], got.deps)
71        self.assertEqual(
72            {
73                "@platforms//os:linux": ["posix_dep"],
74                "@platforms//os:osx": ["an_osx_dep", "posix_dep"],
75                "@platforms//os:windows": ["win_dep"],
76            },
77            got.deps_select,
78        )
79
80    def test_deps_are_added_to_more_specialized_platforms(self):
81        got = wheel.Deps(
82            "foo",
83            requires_dist=[
84                "m1_dep; sys_platform=='darwin' and platform_machine=='arm64'",
85                "mac_dep; sys_platform=='darwin'",
86            ],
87            platforms={
88                Platform(os=OS.osx, arch=Arch.x86_64),
89                Platform(os=OS.osx, arch=Arch.aarch64),
90            },
91        ).build()
92
93        self.assertEqual(
94            wheel.FrozenDeps(
95                deps=[],
96                deps_select={
97                    "osx_aarch64": ["m1_dep", "mac_dep"],
98                    "@platforms//os:osx": ["mac_dep"],
99                },
100            ),
101            got,
102        )
103
104    def test_deps_from_more_specialized_platforms_are_propagated(self):
105        got = wheel.Deps(
106            "foo",
107            requires_dist=[
108                "a_mac_dep; sys_platform=='darwin'",
109                "m1_dep; sys_platform=='darwin' and platform_machine=='arm64'",
110            ],
111            platforms={
112                Platform(os=OS.osx, arch=Arch.x86_64),
113                Platform(os=OS.osx, arch=Arch.aarch64),
114            },
115        ).build()
116
117        self.assertEqual([], got.deps)
118        self.assertEqual(
119            {
120                "osx_aarch64": ["a_mac_dep", "m1_dep"],
121                "@platforms//os:osx": ["a_mac_dep"],
122            },
123            got.deps_select,
124        )
125
126    def test_non_platform_markers_are_added_to_common_deps(self):
127        got = wheel.Deps(
128            "foo",
129            requires_dist=[
130                "bar",
131                "baz; implementation_name=='cpython'",
132                "m1_dep; sys_platform=='darwin' and platform_machine=='arm64'",
133            ],
134            platforms={
135                Platform(os=OS.linux, arch=Arch.x86_64),
136                Platform(os=OS.osx, arch=Arch.x86_64),
137                Platform(os=OS.osx, arch=Arch.aarch64),
138                Platform(os=OS.windows, arch=Arch.x86_64),
139            },
140        ).build()
141
142        self.assertEqual(["bar", "baz"], got.deps)
143        self.assertEqual(
144            {
145                "osx_aarch64": ["m1_dep"],
146            },
147            got.deps_select,
148        )
149
150    def test_self_is_ignored(self):
151        deps = wheel.Deps(
152            "foo",
153            requires_dist=[
154                "bar",
155                "req_dep; extra == 'requests'",
156                "foo[requests]; extra == 'ssl'",
157                "ssl_lib; extra == 'ssl'",
158            ],
159            extras={"ssl"},
160        )
161
162        got = deps.build()
163
164        self.assertEqual(["bar", "req_dep", "ssl_lib"], got.deps)
165        self.assertEqual({}, got.deps_select)
166
167    def test_self_dependencies_can_come_in_any_order(self):
168        deps = wheel.Deps(
169            "foo",
170            requires_dist=[
171                "bar",
172                "baz; extra == 'feat'",
173                "foo[feat2]; extra == 'all'",
174                "foo[feat]; extra == 'feat2'",
175                "zdep; extra == 'all'",
176            ],
177            extras={"all"},
178        )
179
180        got = deps.build()
181
182        self.assertEqual(["bar", "baz", "zdep"], got.deps)
183        self.assertEqual({}, got.deps_select)
184
185    def test_can_get_deps_based_on_specific_python_version(self):
186        requires_dist = [
187            "bar",
188            "baz; python_version < '3.8'",
189            "posix_dep; os_name=='posix' and python_version >= '3.8'",
190        ]
191
192        py38_deps = wheel.Deps(
193            "foo",
194            requires_dist=requires_dist,
195            platforms=[
196                Platform(os=OS.linux, arch=Arch.x86_64, minor_version=8),
197            ],
198        ).build()
199        py37_deps = wheel.Deps(
200            "foo",
201            requires_dist=requires_dist,
202            platforms=[
203                Platform(os=OS.linux, arch=Arch.x86_64, minor_version=7),
204            ],
205        ).build()
206
207        self.assertEqual(["bar", "baz"], py37_deps.deps)
208        self.assertEqual({}, py37_deps.deps_select)
209        self.assertEqual(["bar"], py38_deps.deps)
210        self.assertEqual({"@platforms//os:linux": ["posix_dep"]}, py38_deps.deps_select)
211
212    @mock.patch(_HOST_INTERPRETER_FN)
213    def test_no_version_select_when_single_version(self, mock_host_interpreter_version):
214        requires_dist = [
215            "bar",
216            "baz; python_version >= '3.8'",
217            "posix_dep; os_name=='posix'",
218            "posix_dep_with_version; os_name=='posix' and python_version >= '3.8'",
219            "arch_dep; platform_machine=='x86_64' and python_version >= '3.8'",
220        ]
221        mock_host_interpreter_version.return_value = 7
222
223        self.maxDiff = None
224
225        deps = wheel.Deps(
226            "foo",
227            requires_dist=requires_dist,
228            platforms=[
229                Platform(os=os, arch=Arch.x86_64, minor_version=minor)
230                for minor in [8]
231                for os in [OS.linux, OS.windows]
232            ],
233        )
234        got = deps.build()
235
236        self.assertEqual(["bar", "baz"], got.deps)
237        self.assertEqual(
238            {
239                "@platforms//os:linux": ["posix_dep", "posix_dep_with_version"],
240                "linux_x86_64": ["arch_dep", "posix_dep", "posix_dep_with_version"],
241                "windows_x86_64": ["arch_dep"],
242            },
243            got.deps_select,
244        )
245
246    @mock.patch(_HOST_INTERPRETER_FN)
247    def test_can_get_version_select(self, mock_host_interpreter_version):
248        requires_dist = [
249            "bar",
250            "baz; python_version < '3.8'",
251            "baz_new; python_version >= '3.8'",
252            "posix_dep; os_name=='posix'",
253            "posix_dep_with_version; os_name=='posix' and python_version >= '3.8'",
254            "arch_dep; platform_machine=='x86_64' and python_version < '3.8'",
255        ]
256        mock_host_interpreter_version.return_value = 7
257
258        self.maxDiff = None
259
260        deps = wheel.Deps(
261            "foo",
262            requires_dist=requires_dist,
263            platforms=[
264                Platform(os=os, arch=Arch.x86_64, minor_version=minor)
265                for minor in [7, 8, 9]
266                for os in [OS.linux, OS.windows]
267            ],
268        )
269        got = deps.build()
270
271        self.assertEqual(["bar"], got.deps)
272        self.assertEqual(
273            {
274                "//conditions:default": ["baz"],
275                "@//python/config_settings:is_python_3.7": ["baz"],
276                "@//python/config_settings:is_python_3.8": ["baz_new"],
277                "@//python/config_settings:is_python_3.9": ["baz_new"],
278                "@platforms//os:linux": ["baz", "posix_dep"],
279                "cp37_linux_x86_64": ["arch_dep", "baz", "posix_dep"],
280                "cp37_windows_x86_64": ["arch_dep", "baz"],
281                "cp37_linux_anyarch": ["baz", "posix_dep"],
282                "cp38_linux_anyarch": [
283                    "baz_new",
284                    "posix_dep",
285                    "posix_dep_with_version",
286                ],
287                "cp39_linux_anyarch": [
288                    "baz_new",
289                    "posix_dep",
290                    "posix_dep_with_version",
291                ],
292                "linux_x86_64": ["arch_dep", "baz", "posix_dep"],
293                "windows_x86_64": ["arch_dep", "baz"],
294            },
295            got.deps_select,
296        )
297
298    @mock.patch(_HOST_INTERPRETER_FN)
299    def test_deps_spanning_all_target_py_versions_are_added_to_common(
300        self, mock_host_version
301    ):
302        requires_dist = [
303            "bar",
304            "baz (<2,>=1.11) ; python_version < '3.8'",
305            "baz (<2,>=1.14) ; python_version >= '3.8'",
306        ]
307        mock_host_version.return_value = 8
308
309        deps = wheel.Deps(
310            "foo",
311            requires_dist=requires_dist,
312            platforms=Platform.from_string(["cp37_*", "cp38_*", "cp39_*"]),
313        )
314        got = deps.build()
315
316        self.assertEqual(["bar", "baz"], got.deps)
317        self.assertEqual({}, got.deps_select)
318
319    @mock.patch(_HOST_INTERPRETER_FN)
320    def test_deps_are_not_duplicated(self, mock_host_version):
321        mock_host_version.return_value = 7
322
323        # See an example in
324        # https://files.pythonhosted.org/packages/76/9e/db1c2d56c04b97981c06663384f45f28950a73d9acf840c4006d60d0a1ff/opencv_python-4.9.0.80-cp37-abi3-win32.whl.metadata
325        requires_dist = [
326            "bar >=0.1.0 ; python_version < '3.7'",
327            "bar >=0.2.0 ; python_version >= '3.7'",
328            "bar >=0.4.0 ; python_version >= '3.6' and platform_system == 'Linux' and platform_machine == 'aarch64'",
329            "bar >=0.4.0 ; python_version >= '3.9'",
330            "bar >=0.5.0 ; python_version <= '3.9' and platform_system == 'Darwin' and platform_machine == 'arm64'",
331            "bar >=0.5.0 ; python_version >= '3.10' and platform_system == 'Darwin'",
332            "bar >=0.5.0 ; python_version >= '3.10'",
333            "bar >=0.6.0 ; python_version >= '3.11'",
334        ]
335
336        deps = wheel.Deps(
337            "foo",
338            requires_dist=requires_dist,
339            platforms=Platform.from_string(["cp37_*", "cp310_*"]),
340        )
341        got = deps.build()
342
343        self.assertEqual(["bar"], got.deps)
344        self.assertEqual({}, got.deps_select)
345
346    @mock.patch(_HOST_INTERPRETER_FN)
347    def test_deps_are_not_duplicated_when_encountering_platform_dep_first(
348        self, mock_host_version
349    ):
350        mock_host_version.return_value = 7
351
352        # Note, that we are sorting the incoming `requires_dist` and we need to ensure that we are not getting any
353        # issues even if the platform-specific line comes first.
354        requires_dist = [
355            "bar >=0.4.0 ; python_version >= '3.6' and platform_system == 'Linux' and platform_machine == 'aarch64'",
356            "bar >=0.5.0 ; python_version >= '3.9'",
357        ]
358
359        deps = wheel.Deps(
360            "foo",
361            requires_dist=requires_dist,
362            platforms=Platform.from_string(["cp37_*", "cp310_*"]),
363        )
364        got = deps.build()
365
366        self.assertEqual(["bar"], got.deps)
367        self.assertEqual({}, got.deps_select)
368
369
370if __name__ == "__main__":
371    unittest.main()
372