• 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"""Tests for construction of Python version matching config settings."""
15
16load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
17load("@rules_testing//lib:test_suite.bzl", "test_suite")
18load("@rules_testing//lib:truth.bzl", "subjects")
19load("@rules_testing//lib:util.bzl", test_util = "util")
20load("//python/private/pypi:config_settings.bzl", "config_settings")  # buildifier: disable=bzl-visibility
21
22def _subject_impl(ctx):
23    _ = ctx  # @unused
24    return [DefaultInfo()]
25
26_subject = rule(
27    implementation = _subject_impl,
28    attrs = {
29        "dist": attr.string(),
30    },
31)
32
33_flag = struct(
34    platform = lambda x: ("//command_line_option:platforms", str(Label("//tests/support:" + x))),
35    pip_whl = lambda x: (str(Label("//python/config_settings:pip_whl")), str(x)),
36    pip_whl_glibc_version = lambda x: (str(Label("//python/config_settings:pip_whl_glibc_version")), str(x)),
37    pip_whl_muslc_version = lambda x: (str(Label("//python/config_settings:pip_whl_muslc_version")), str(x)),
38    pip_whl_osx_version = lambda x: (str(Label("//python/config_settings:pip_whl_osx_version")), str(x)),
39    pip_whl_osx_arch = lambda x: (str(Label("//python/config_settings:pip_whl_osx_arch")), str(x)),
40    py_linux_libc = lambda x: (str(Label("//python/config_settings:py_linux_libc")), str(x)),
41    python_version = lambda x: (str(Label("//python/config_settings:python_version")), str(x)),
42)
43
44def _analysis_test(*, name, dist, want, config_settings = [_flag.platform("linux_aarch64")]):
45    subject_name = name + "_subject"
46    test_util.helper_target(
47        _subject,
48        name = subject_name,
49        dist = select(
50            dist | {
51                "//conditions:default": "no_match",
52            },
53        ),
54    )
55    config_settings = dict(config_settings)
56    if not config_settings:
57        fail("For reproducibility on different platforms, the config setting must be specified")
58    python_version, default_value = _flag.python_version("3.7.10")
59    config_settings.setdefault(python_version, default_value)
60
61    analysis_test(
62        name = name,
63        target = subject_name,
64        impl = lambda env, target: _match(env, target, want),
65        config_settings = config_settings,
66    )
67
68def _match(env, target, want):
69    target = env.expect.that_target(target)
70    target.attr("dist", factory = subjects.str).equals(want)
71
72_tests = []
73
74# Tests when we only have an `sdist` present.
75
76def _test_sdist_default(name):
77    _analysis_test(
78        name = name,
79        dist = {
80            "is_cp3.7_sdist": "sdist",
81        },
82        want = "sdist",
83    )
84
85_tests.append(_test_sdist_default)
86
87def _test_sdist_no_whl(name):
88    _analysis_test(
89        name = name,
90        dist = {
91            "is_cp3.7_sdist": "sdist",
92        },
93        config_settings = [
94            _flag.platform("linux_aarch64"),
95            _flag.pip_whl("no"),
96        ],
97        want = "sdist",
98    )
99
100_tests.append(_test_sdist_no_whl)
101
102def _test_sdist_no_sdist(name):
103    _analysis_test(
104        name = name,
105        dist = {
106            "is_cp3.7_sdist": "sdist",
107        },
108        config_settings = [
109            _flag.platform("linux_aarch64"),
110            _flag.pip_whl("only"),
111        ],
112        # We will use `no_match_error` in the real case to indicate that `sdist` is not
113        # allowed to be used.
114        want = "no_match",
115    )
116
117_tests.append(_test_sdist_no_sdist)
118
119def _test_basic_whl_default(name):
120    _analysis_test(
121        name = name,
122        dist = {
123            "is_cp3.7_py_none_any": "whl",
124            "is_cp3.7_sdist": "sdist",
125        },
126        want = "whl",
127    )
128
129_tests.append(_test_basic_whl_default)
130
131def _test_basic_whl_nowhl(name):
132    _analysis_test(
133        name = name,
134        dist = {
135            "is_cp3.7_py_none_any": "whl",
136            "is_cp3.7_sdist": "sdist",
137        },
138        config_settings = [
139            _flag.platform("linux_aarch64"),
140            _flag.pip_whl("no"),
141        ],
142        want = "sdist",
143    )
144
145_tests.append(_test_basic_whl_nowhl)
146
147def _test_basic_whl_nosdist(name):
148    _analysis_test(
149        name = name,
150        dist = {
151            "is_cp3.7_py_none_any": "whl",
152            "is_cp3.7_sdist": "sdist",
153        },
154        config_settings = [
155            _flag.platform("linux_aarch64"),
156            _flag.pip_whl("only"),
157        ],
158        want = "whl",
159    )
160
161_tests.append(_test_basic_whl_nosdist)
162
163def _test_whl_default(name):
164    _analysis_test(
165        name = name,
166        dist = {
167            "is_cp3.7_py3_none_any": "whl",
168            "is_cp3.7_py_none_any": "basic_whl",
169        },
170        want = "whl",
171    )
172
173_tests.append(_test_whl_default)
174
175def _test_whl_nowhl(name):
176    _analysis_test(
177        name = name,
178        dist = {
179            "is_cp3.7_py3_none_any": "whl",
180            "is_cp3.7_py_none_any": "basic_whl",
181        },
182        config_settings = [
183            _flag.platform("linux_aarch64"),
184            _flag.pip_whl("no"),
185        ],
186        want = "no_match",
187    )
188
189_tests.append(_test_whl_nowhl)
190
191def _test_whl_nosdist(name):
192    _analysis_test(
193        name = name,
194        dist = {
195            "is_cp3.7_py3_none_any": "whl",
196        },
197        config_settings = [
198            _flag.platform("linux_aarch64"),
199            _flag.pip_whl("only"),
200        ],
201        want = "whl",
202    )
203
204_tests.append(_test_whl_nosdist)
205
206def _test_abi_whl_is_prefered(name):
207    _analysis_test(
208        name = name,
209        dist = {
210            "is_cp3.7_py3_abi3_any": "abi_whl",
211            "is_cp3.7_py3_none_any": "whl",
212        },
213        want = "abi_whl",
214    )
215
216_tests.append(_test_abi_whl_is_prefered)
217
218def _test_whl_with_constraints_is_prefered(name):
219    _analysis_test(
220        name = name,
221        dist = {
222            "is_cp3.7_py3_none_any": "default_whl",
223            "is_cp3.7_py3_none_any_linux_aarch64": "whl",
224            "is_cp3.7_py3_none_any_linux_x86_64": "amd64_whl",
225        },
226        want = "whl",
227    )
228
229_tests.append(_test_whl_with_constraints_is_prefered)
230
231def _test_cp_whl_is_prefered_over_py3(name):
232    _analysis_test(
233        name = name,
234        dist = {
235            "is_cp3.7_cp3x_none_any": "cp",
236            "is_cp3.7_py3_abi3_any": "py3_abi3",
237            "is_cp3.7_py3_none_any": "py3",
238        },
239        want = "cp",
240    )
241
242_tests.append(_test_cp_whl_is_prefered_over_py3)
243
244def _test_cp_abi_whl_is_prefered_over_py3(name):
245    _analysis_test(
246        name = name,
247        dist = {
248            "is_cp3.7_cp3x_abi3_any": "cp",
249            "is_cp3.7_py3_abi3_any": "py3",
250        },
251        want = "cp",
252    )
253
254_tests.append(_test_cp_abi_whl_is_prefered_over_py3)
255
256def _test_cp_version_is_selected_when_python_version_is_specified(name):
257    _analysis_test(
258        name = name,
259        dist = {
260            "is_cp3.10_cp3x_none_any": "cp310",
261            "is_cp3.8_cp3x_none_any": "cp38",
262            "is_cp3.9_cp3x_none_any": "cp39",
263        },
264        want = "cp310",
265        config_settings = [
266            _flag.python_version("3.10.9"),
267            _flag.platform("linux_aarch64"),
268        ],
269    )
270
271_tests.append(_test_cp_version_is_selected_when_python_version_is_specified)
272
273def _test_py_none_any_versioned(name):
274    _analysis_test(
275        name = name,
276        dist = {
277            "is_cp3.10_py_none_any": "whl",
278            "is_cp3.9_py_none_any": "too-low",
279        },
280        want = "whl",
281        config_settings = [
282            _flag.python_version("3.10.9"),
283            _flag.platform("linux_aarch64"),
284        ],
285    )
286
287_tests.append(_test_py_none_any_versioned)
288
289def _test_cp_cp_whl(name):
290    _analysis_test(
291        name = name,
292        dist = {
293            "is_cp3.10_cp3x_cp_linux_aarch64": "whl",
294        },
295        want = "whl",
296        config_settings = [
297            _flag.python_version("3.10.9"),
298            _flag.platform("linux_aarch64"),
299        ],
300    )
301
302_tests.append(_test_cp_cp_whl)
303
304def _test_cp_version_sdist_is_selected(name):
305    _analysis_test(
306        name = name,
307        dist = {
308            "is_cp3.10_sdist": "sdist",
309        },
310        want = "sdist",
311        config_settings = [
312            _flag.python_version("3.10.9"),
313            _flag.platform("linux_aarch64"),
314        ],
315    )
316
317_tests.append(_test_cp_version_sdist_is_selected)
318
319def _test_platform_whl_is_prefered_over_any_whl_with_constraints(name):
320    _analysis_test(
321        name = name,
322        dist = {
323            "is_cp3.7_py3_abi3_any": "better_default_whl",
324            "is_cp3.7_py3_abi3_any_linux_aarch64": "better_default_any_whl",
325            "is_cp3.7_py3_none_any": "default_whl",
326            "is_cp3.7_py3_none_any_linux_aarch64": "whl",
327            "is_cp3.7_py3_none_linux_aarch64": "platform_whl",
328        },
329        want = "platform_whl",
330    )
331
332_tests.append(_test_platform_whl_is_prefered_over_any_whl_with_constraints)
333
334def _test_abi3_platform_whl_preference(name):
335    _analysis_test(
336        name = name,
337        dist = {
338            "is_cp3.7_py3_abi3_linux_aarch64": "abi3_platform",
339            "is_cp3.7_py3_none_linux_aarch64": "platform",
340        },
341        want = "abi3_platform",
342    )
343
344_tests.append(_test_abi3_platform_whl_preference)
345
346def _test_glibc(name):
347    _analysis_test(
348        name = name,
349        dist = {
350            "is_cp3.7_cp3x_cp_manylinux_aarch64": "glibc",
351            "is_cp3.7_py3_abi3_linux_aarch64": "abi3_platform",
352        },
353        want = "glibc",
354    )
355
356_tests.append(_test_glibc)
357
358def _test_glibc_versioned(name):
359    _analysis_test(
360        name = name,
361        dist = {
362            "is_cp3.7_cp3x_cp_manylinux_2_14_aarch64": "glibc",
363            "is_cp3.7_cp3x_cp_manylinux_2_17_aarch64": "glibc",
364            "is_cp3.7_py3_abi3_linux_aarch64": "abi3_platform",
365        },
366        want = "glibc",
367        config_settings = [
368            _flag.py_linux_libc("glibc"),
369            _flag.pip_whl_glibc_version("2.17"),
370            _flag.platform("linux_aarch64"),
371        ],
372    )
373
374_tests.append(_test_glibc_versioned)
375
376def _test_glibc_compatible_exists(name):
377    _analysis_test(
378        name = name,
379        dist = {
380            # Code using the conditions will need to construct selects, which
381            # do the version matching correctly.
382            "is_cp3.7_cp3x_cp_manylinux_2_14_aarch64": "2_14_whl_via_2_14_branch",
383            "is_cp3.7_cp3x_cp_manylinux_2_17_aarch64": "2_14_whl_via_2_17_branch",
384        },
385        want = "2_14_whl_via_2_17_branch",
386        config_settings = [
387            _flag.py_linux_libc("glibc"),
388            _flag.pip_whl_glibc_version("2.17"),
389            _flag.platform("linux_aarch64"),
390        ],
391    )
392
393_tests.append(_test_glibc_compatible_exists)
394
395def _test_musl(name):
396    _analysis_test(
397        name = name,
398        dist = {
399            "is_cp3.7_cp3x_cp_musllinux_aarch64": "musl",
400        },
401        want = "musl",
402        config_settings = [
403            _flag.py_linux_libc("musl"),
404            _flag.platform("linux_aarch64"),
405        ],
406    )
407
408_tests.append(_test_musl)
409
410def _test_windows(name):
411    _analysis_test(
412        name = name,
413        dist = {
414            "is_cp3.7_cp3x_cp_windows_x86_64": "whl",
415        },
416        want = "whl",
417        config_settings = [
418            _flag.platform("windows_x86_64"),
419        ],
420    )
421
422_tests.append(_test_windows)
423
424def _test_osx(name):
425    _analysis_test(
426        name = name,
427        dist = {
428            # We prefer arch specific whls over universal
429            "is_cp3.7_cp3x_cp_osx_x86_64": "whl",
430            "is_cp3.7_cp3x_cp_osx_x86_64_universal2": "universal_whl",
431        },
432        want = "whl",
433        config_settings = [
434            _flag.platform("mac_x86_64"),
435        ],
436    )
437
438_tests.append(_test_osx)
439
440def _test_osx_universal_default(name):
441    _analysis_test(
442        name = name,
443        dist = {
444            # We default to universal if only that exists
445            "is_cp3.7_cp3x_cp_osx_x86_64_universal2": "whl",
446        },
447        want = "whl",
448        config_settings = [
449            _flag.platform("mac_x86_64"),
450        ],
451    )
452
453_tests.append(_test_osx_universal_default)
454
455def _test_osx_universal_only(name):
456    _analysis_test(
457        name = name,
458        dist = {
459            # If we prefer universal, then we use that
460            "is_cp3.7_cp3x_cp_osx_x86_64": "whl",
461            "is_cp3.7_cp3x_cp_osx_x86_64_universal2": "universal",
462        },
463        want = "universal",
464        config_settings = [
465            _flag.pip_whl_osx_arch("universal"),
466            _flag.platform("mac_x86_64"),
467        ],
468    )
469
470_tests.append(_test_osx_universal_only)
471
472def _test_osx_os_version(name):
473    _analysis_test(
474        name = name,
475        dist = {
476            # Similarly to the libc version, the user of the config settings will have to
477            # construct the select so that the version selection is correct.
478            "is_cp3.7_cp3x_cp_osx_10_9_x86_64": "whl",
479        },
480        want = "whl",
481        config_settings = [
482            _flag.pip_whl_osx_version("10.9"),
483            _flag.platform("mac_x86_64"),
484        ],
485    )
486
487_tests.append(_test_osx_os_version)
488
489def _test_all(name):
490    _analysis_test(
491        name = name,
492        dist = {
493            "is_cp3.7_" + f: f
494            for f in [
495                "{py}_{abi}_{plat}".format(py = valid_py, abi = valid_abi, plat = valid_plat)
496                # we have py2.py3, py3, cp3x
497                for valid_py in ["py", "py3", "cp3x"]
498                # cp abi usually comes with a version and we only need one
499                # config setting variant for all of them because the python
500                # version will discriminate between different versions.
501                for valid_abi in ["none", "abi3", "cp"]
502                for valid_plat in [
503                    "any",
504                    "manylinux_2_17_x86_64",
505                    "manylinux_2_17_aarch64",
506                    "osx_x86_64",
507                    "windows_x86_64",
508                ]
509                if not (
510                    valid_abi == "abi3" and valid_py == "py" or
511                    valid_abi == "cp" and valid_py != "cp3x"
512                )
513            ]
514        },
515        want = "cp3x_cp_manylinux_2_17_x86_64",
516        config_settings = [
517            _flag.pip_whl_glibc_version("2.17"),
518            _flag.platform("linux_x86_64"),
519        ],
520    )
521
522_tests.append(_test_all)
523
524def config_settings_test_suite(name):  # buildifier: disable=function-docstring
525    test_suite(
526        name = name,
527        tests = _tests,
528    )
529
530    config_settings(
531        name = "dummy",
532        python_versions = ["3.7", "3.8", "3.9", "3.10"],
533        glibc_versions = [(2, 14), (2, 17)],
534        muslc_versions = [(1, 1)],
535        osx_versions = [(10, 9), (11, 0)],
536        target_platforms = [
537            "windows_x86_64",
538            "windows_aarch64",
539            "linux_x86_64",
540            "linux_ppc",
541            "linux_aarch64",
542            "osx_x86_64",
543            "osx_aarch64",
544        ],
545    )
546