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