• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""pkg_aliases tests"""
16
17load("@rules_testing//lib:test_suite.bzl", "test_suite")
18load("//python/private/pypi:config_settings.bzl", "config_settings")  # buildifier: disable=bzl-visibility
19load(
20    "//python/private/pypi:pkg_aliases.bzl",
21    "multiplatform_whl_aliases",
22    "pkg_aliases",
23)  # buildifier: disable=bzl-visibility
24load("//python/private/pypi:whl_config_setting.bzl", "whl_config_setting")  # buildifier: disable=bzl-visibility
25
26_tests = []
27
28def _test_legacy_aliases(env):
29    got = {}
30    pkg_aliases(
31        name = "foo",
32        actual = "repo",
33        native = struct(
34            alias = lambda name, actual: got.update({name: actual}),
35        ),
36        extra_aliases = ["my_special"],
37    )
38
39    # buildifier: disable=unsorted-dict-items
40    want = {
41        "foo": ":pkg",
42        "pkg": "@repo//:pkg",
43        "whl": "@repo//:whl",
44        "data": "@repo//:data",
45        "dist_info": "@repo//:dist_info",
46        "my_special": "@repo//:my_special",
47    }
48
49    env.expect.that_dict(got).contains_exactly(want)
50
51_tests.append(_test_legacy_aliases)
52
53def _test_config_setting_aliases(env):
54    # Use this function as it is used in pip_repository
55    got = {}
56    actual_no_match_error = []
57
58    def mock_select(value, no_match_error = None):
59        actual_no_match_error.append(no_match_error)
60        env.expect.that_str(no_match_error).contains("""\
61configuration settings:
62    //:my_config_setting
63
64""")
65        return value
66
67    pkg_aliases(
68        name = "bar_baz",
69        actual = {
70            "//:my_config_setting": "bar_baz_repo",
71        },
72        extra_aliases = ["my_special"],
73        native = struct(
74            alias = lambda name, actual: got.update({name: actual}),
75        ),
76        select = mock_select,
77    )
78
79    # buildifier: disable=unsorted-dict-items
80    want = {
81        "pkg": {
82            "//:my_config_setting": "@bar_baz_repo//:pkg",
83        },
84    }
85    env.expect.that_dict(got).contains_at_least(want)
86
87_tests.append(_test_config_setting_aliases)
88
89def _test_config_setting_aliases_many(env):
90    # Use this function as it is used in pip_repository
91    got = {}
92    actual_no_match_error = []
93
94    def mock_select(value, no_match_error = None):
95        actual_no_match_error.append(no_match_error)
96        env.expect.that_str(no_match_error).contains("""\
97configuration settings:
98    //:another_config_setting
99    //:my_config_setting
100    //:third_config_setting
101""")
102        return value
103
104    pkg_aliases(
105        name = "bar_baz",
106        actual = {
107            (
108                "//:my_config_setting",
109                "//:another_config_setting",
110            ): "bar_baz_repo",
111            "//:third_config_setting": "foo_repo",
112        },
113        extra_aliases = ["my_special"],
114        native = struct(
115            alias = lambda name, actual: got.update({name: actual}),
116        ),
117        select = mock_select,
118    )
119
120    # buildifier: disable=unsorted-dict-items
121    want = {
122        "my_special": {
123            (
124                "//:my_config_setting",
125                "//:another_config_setting",
126            ): "@bar_baz_repo//:my_special",
127            "//:third_config_setting": "@foo_repo//:my_special",
128        },
129    }
130    env.expect.that_dict(got).contains_at_least(want)
131
132_tests.append(_test_config_setting_aliases_many)
133
134def _test_multiplatform_whl_aliases(env):
135    # Use this function as it is used in pip_repository
136    got = {}
137    actual_no_match_error = []
138
139    def mock_select(value, no_match_error = None):
140        actual_no_match_error.append(no_match_error)
141        env.expect.that_str(no_match_error).contains("""\
142configuration settings:
143    //:my_config_setting
144    //_config:is_cp3.9_linux_x86_64
145    //_config:is_cp3.9_py3_none_any
146    //_config:is_cp3.9_py3_none_any_linux_x86_64
147
148""")
149        return value
150
151    pkg_aliases(
152        name = "bar_baz",
153        actual = {
154            whl_config_setting(
155                filename = "foo-0.0.0-py3-none-any.whl",
156                version = "3.9",
157            ): "filename_repo",
158            whl_config_setting(
159                filename = "foo-0.0.0-py3-none-any.whl",
160                version = "3.9",
161                target_platforms = ["cp39_linux_x86_64"],
162            ): "filename_repo_for_platform",
163            whl_config_setting(
164                version = "3.9",
165                target_platforms = ["cp39_linux_x86_64"],
166            ): "bzlmod_repo_for_a_particular_platform",
167            "//:my_config_setting": "bzlmod_repo",
168        },
169        extra_aliases = [],
170        native = struct(
171            alias = lambda name, actual: got.update({name: actual}),
172        ),
173        select = mock_select,
174        glibc_versions = [],
175        muslc_versions = [],
176        osx_versions = [],
177    )
178
179    # buildifier: disable=unsorted-dict-items
180    want = {
181        "pkg": {
182            "//:my_config_setting": "@bzlmod_repo//:pkg",
183            "//_config:is_cp3.9_linux_x86_64": "@bzlmod_repo_for_a_particular_platform//:pkg",
184            "//_config:is_cp3.9_py3_none_any": "@filename_repo//:pkg",
185            "//_config:is_cp3.9_py3_none_any_linux_x86_64": "@filename_repo_for_platform//:pkg",
186        },
187    }
188    env.expect.that_dict(got).contains_at_least(want)
189
190_tests.append(_test_multiplatform_whl_aliases)
191
192def _test_group_aliases(env):
193    # Use this function as it is used in pip_repository
194    actual = []
195
196    pkg_aliases(
197        name = "foo",
198        actual = "repo",
199        group_name = "my_group",
200        native = struct(
201            alias = lambda **kwargs: actual.append(kwargs),
202        ),
203    )
204
205    # buildifier: disable=unsorted-dict-items
206    want = [
207        {
208            "name": "foo",
209            "actual": ":pkg",
210        },
211        {
212            "name": "_pkg",
213            "actual": "@repo//:pkg",
214            "visibility": ["//_groups:__subpackages__"],
215        },
216        {
217            "name": "_whl",
218            "actual": "@repo//:whl",
219            "visibility": ["//_groups:__subpackages__"],
220        },
221        {
222            "name": "data",
223            "actual": "@repo//:data",
224        },
225        {
226            "name": "dist_info",
227            "actual": "@repo//:dist_info",
228        },
229        {
230            "name": "pkg",
231            "actual": "//_groups:my_group_pkg",
232        },
233        {
234            "name": "whl",
235            "actual": "//_groups:my_group_whl",
236        },
237    ]
238    env.expect.that_collection(actual).contains_exactly(want)
239
240_tests.append(_test_group_aliases)
241
242def _test_multiplatform_whl_aliases_empty(env):
243    # Check that we still work with an empty requirements.txt
244    got = multiplatform_whl_aliases(aliases = {})
245    env.expect.that_dict(got).contains_exactly({})
246
247_tests.append(_test_multiplatform_whl_aliases_empty)
248
249def _test_multiplatform_whl_aliases_nofilename(env):
250    aliases = {
251        "//:label": "foo",
252    }
253    got = multiplatform_whl_aliases(aliases = aliases)
254    env.expect.that_dict(got).contains_exactly(aliases)
255
256_tests.append(_test_multiplatform_whl_aliases_nofilename)
257
258def _test_multiplatform_whl_aliases_nofilename_target_platforms(env):
259    aliases = {
260        whl_config_setting(
261            config_setting = "//:ignored",
262            version = "3.1",
263            target_platforms = [
264                "cp31_linux_x86_64",
265                "cp31_linux_aarch64",
266            ],
267        ): "foo",
268    }
269
270    got = multiplatform_whl_aliases(aliases = aliases)
271
272    want = {
273        "//_config:is_cp3.1_linux_aarch64": "foo",
274        "//_config:is_cp3.1_linux_x86_64": "foo",
275    }
276    env.expect.that_dict(got).contains_exactly(want)
277
278_tests.append(_test_multiplatform_whl_aliases_nofilename_target_platforms)
279
280def _test_multiplatform_whl_aliases_filename(env):
281    aliases = {
282        whl_config_setting(
283            filename = "foo-0.0.3-py3-none-any.whl",
284            version = "3.2",
285        ): "foo-py3-0.0.3",
286        whl_config_setting(
287            filename = "foo-0.0.1-py3-none-any.whl",
288            version = "3.1",
289        ): "foo-py3-0.0.1",
290        whl_config_setting(
291            filename = "foo-0.0.2-py3-none-any.whl",
292            version = "3.1",
293            target_platforms = [
294                "cp31_linux_x86_64",
295                "cp31_linux_aarch64",
296            ],
297        ): "foo-0.0.2",
298    }
299    got = multiplatform_whl_aliases(
300        aliases = aliases,
301        glibc_versions = [],
302        muslc_versions = [],
303        osx_versions = [],
304    )
305    want = {
306        "//_config:is_cp3.1_py3_none_any": "foo-py3-0.0.1",
307        "//_config:is_cp3.1_py3_none_any_linux_aarch64": "foo-0.0.2",
308        "//_config:is_cp3.1_py3_none_any_linux_x86_64": "foo-0.0.2",
309        "//_config:is_cp3.2_py3_none_any": "foo-py3-0.0.3",
310    }
311    env.expect.that_dict(got).contains_exactly(want)
312
313_tests.append(_test_multiplatform_whl_aliases_filename)
314
315def _test_multiplatform_whl_aliases_filename_versioned(env):
316    aliases = {
317        whl_config_setting(
318            filename = "foo-0.0.1-py3-none-manylinux_2_17_x86_64.whl",
319            version = "3.1",
320        ): "glibc-2.17",
321        whl_config_setting(
322            filename = "foo-0.0.1-py3-none-manylinux_2_18_x86_64.whl",
323            version = "3.1",
324        ): "glibc-2.18",
325        whl_config_setting(
326            filename = "foo-0.0.1-py3-none-musllinux_1_1_x86_64.whl",
327            version = "3.1",
328        ): "musl-1.1",
329    }
330    got = multiplatform_whl_aliases(
331        aliases = aliases,
332        glibc_versions = [(2, 17), (2, 18)],
333        muslc_versions = [(1, 1), (1, 2)],
334        osx_versions = [],
335    )
336    want = {
337        # This could just work with:
338        # select({
339        #     "//_config:is_gt_eq_2.18": "//_config:is_cp3.1_py3_none_manylinux_x86_64",
340        #     "//conditions:default": "//_config:is_gt_eq_2.18",
341        # }): "glibc-2.18",
342        # select({
343        #     "//_config:is_range_2.17_2.18": "//_config:is_cp3.1_py3_none_manylinux_x86_64",
344        #     "//_config:is_glibc_default": "//_config:is_cp3.1_py3_none_manylinux_x86_64",
345        #     "//conditions:default": "//_config:is_glibc_default",
346        # }): "glibc-2.17",
347        # (
348        #     "//_config:is_gt_musl_1.1":  "musl-1.1",
349        #     "//_config:is_musl_default": "musl-1.1",
350        # ): "musl-1.1",
351        #
352        # For this to fully work we need to have the pypi:config_settings.bzl to generate the
353        # extra targets that use the FeatureFlagInfo and this to generate extra aliases for the
354        # config settings.
355        "//_config:is_cp3.1_py3_none_manylinux_2_17_x86_64": "glibc-2.17",
356        "//_config:is_cp3.1_py3_none_manylinux_2_18_x86_64": "glibc-2.18",
357        "//_config:is_cp3.1_py3_none_manylinux_x86_64": "glibc-2.17",
358        "//_config:is_cp3.1_py3_none_musllinux_1_1_x86_64": "musl-1.1",
359        "//_config:is_cp3.1_py3_none_musllinux_1_2_x86_64": "musl-1.1",
360        "//_config:is_cp3.1_py3_none_musllinux_x86_64": "musl-1.1",
361    }
362    env.expect.that_dict(got).contains_exactly(want)
363
364_tests.append(_test_multiplatform_whl_aliases_filename_versioned)
365
366def _mock_alias(container):
367    return lambda name, **kwargs: container.append(name)
368
369def _mock_config_setting(container):
370    def _inner(name, flag_values = None, constraint_values = None, **_):
371        if flag_values or constraint_values:
372            container.append(name)
373            return
374
375        fail("At least one of 'flag_values' or 'constraint_values' needs to be set")
376
377    return _inner
378
379def _test_config_settings_exist_legacy(env):
380    aliases = {
381        whl_config_setting(
382            version = "3.11",
383            target_platforms = [
384                "cp311_linux_aarch64",
385                "cp311_linux_x86_64",
386            ],
387        ): "repo",
388    }
389    available_config_settings = []
390    config_settings(
391        python_versions = ["3.11"],
392        native = struct(
393            alias = _mock_alias(available_config_settings),
394            config_setting = _mock_config_setting(available_config_settings),
395        ),
396        target_platforms = [
397            "linux_aarch64",
398            "linux_x86_64",
399        ],
400    )
401
402    got_aliases = multiplatform_whl_aliases(
403        aliases = aliases,
404    )
405    got = [a.partition(":")[-1] for a in got_aliases]
406
407    env.expect.that_collection(available_config_settings).contains_at_least(got)
408
409_tests.append(_test_config_settings_exist_legacy)
410
411def _test_config_settings_exist(env):
412    for py_tag in ["py2.py3", "py3", "py311", "cp311"]:
413        if py_tag == "py2.py3":
414            abis = ["none"]
415        elif py_tag.startswith("py"):
416            abis = ["none", "abi3"]
417        else:
418            abis = ["none", "abi3", "cp311"]
419
420        for abi_tag in abis:
421            for platform_tag, kwargs in {
422                "any": {},
423                "macosx_11_0_arm64": {
424                    "osx_versions": [(11, 0)],
425                    "target_platforms": ["osx_aarch64"],
426                },
427                "manylinux_2_17_x86_64": {
428                    "glibc_versions": [(2, 17), (2, 18)],
429                    "target_platforms": ["linux_x86_64"],
430                },
431                "manylinux_2_18_x86_64": {
432                    "glibc_versions": [(2, 17), (2, 18)],
433                    "target_platforms": ["linux_x86_64"],
434                },
435                "musllinux_1_1_aarch64": {
436                    "muslc_versions": [(1, 2), (1, 1), (1, 0)],
437                    "target_platforms": ["linux_aarch64"],
438                },
439            }.items():
440                aliases = {
441                    whl_config_setting(
442                        filename = "foo-0.0.1-{}-{}-{}.whl".format(py_tag, abi_tag, platform_tag),
443                        version = "3.11",
444                    ): "repo",
445                }
446                available_config_settings = []
447                config_settings(
448                    python_versions = ["3.11"],
449                    native = struct(
450                        alias = _mock_alias(available_config_settings),
451                        config_setting = _mock_config_setting(available_config_settings),
452                    ),
453                    **kwargs
454                )
455
456                got_aliases = multiplatform_whl_aliases(
457                    aliases = aliases,
458                    glibc_versions = kwargs.get("glibc_versions", []),
459                    muslc_versions = kwargs.get("muslc_versions", []),
460                    osx_versions = kwargs.get("osx_versions", []),
461                )
462                got = [a.partition(":")[-1] for a in got_aliases]
463
464                env.expect.that_collection(available_config_settings).contains_at_least(got)
465
466_tests.append(_test_config_settings_exist)
467
468def pkg_aliases_test_suite(name):
469    """Create the test suite.
470
471    Args:
472        name: the name of the test suite
473    """
474    test_suite(name = name, basic_tests = _tests)
475