From 08d7f77ffef202f83d615d23b5ebd986c9aa2542 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Tue, 9 Sep 2025 09:30:41 +0900 Subject: [PATCH 01/11] fix(pypi): select the lowest available libc version by default The #3058 PR has subtly changed the default behaviour of `experimental_index_url` code path and I think in order to make things easier by default for our users we should go back to that behaviour. The selection of the wheels happens in two ways: 1. If the user specifies `whl_platform_tags` value like `manylinux_*_x86_64` then we we should just get the lowest available versioned wheel. I.e. if `manylinux_2_28_x86_64` and `manylinux_2_32_x86_64` are available, we should prefer the former. 2. If the user specifies `whl_platform_tags` value like `manylinux_2_32_x86_64` then we we should get the highest version that is lower or equal than the specified one. I.e. if `manylinux_2_21_x86_64`, `manylinux_2_28_x86_64` and `manylinux_2_36_x86_64` are available, we should prefer the `manylinux_2_28_x86_64` one. Fixes #3250 --- CHANGELOG.md | 4 ++++ python/private/pypi/select_whl.bzl | 5 +++- tests/pypi/select_whl/select_whl_tests.bzl | 27 ++++++++++++++++++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55d0d3fa2f..fe2c1c92dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,10 @@ END_UNRELEASED_TEMPLATE length errors due to too long environment variables. * (bootstrap) {obj}`--bootstrap_impl=script` now supports the `-S` interpreter setting. +* (pypi) We now by default select the highest closest match wheel version if the user is + specifying a particular version via the `whl_platform_tags` or if any version is matched via + the `*` token, then we select the lowest available version. + Fixes [#3250](https://github.com/bazel-contrib/rules_python/issues/3250). {#v0-0-0-added} ### Added diff --git a/python/private/pypi/select_whl.bzl b/python/private/pypi/select_whl.bzl index e9db1886e7..5c94e9e037 100644 --- a/python/private/pypi/select_whl.bzl +++ b/python/private/pypi/select_whl.bzl @@ -46,6 +46,8 @@ def _platform_tag_priority(*, tag, values): arch = tail version = (int(major), int(minor)) + select_highest = True + keys = [] for priority, wp in enumerate(values): want_os, sep, tail = wp.partition("_") @@ -65,6 +67,7 @@ def _platform_tag_priority(*, tag, values): want_major = "" want_minor = "" want_arch = tail + select_highest = False elif os.startswith(_ANDROID): # we set it to `0` above, so setting the `want_minor` her to `0` will make things # consistent. @@ -81,7 +84,7 @@ def _platform_tag_priority(*, tag, values): # if want_major is defined, then we know that we don't have a `*` in the matcher. want_version = (int(want_major), int(want_minor)) if want_major else None if not want_version or version <= want_version: - keys.append((priority, version)) + keys.append((priority, version if select_highest else (-version[0], -version[1]))) return max(keys) if keys else None diff --git a/tests/pypi/select_whl/select_whl_tests.bzl b/tests/pypi/select_whl/select_whl_tests.bzl index 28e17ba3b3..7a28723e4b 100644 --- a/tests/pypi/select_whl/select_whl_tests.bzl +++ b/tests/pypi/select_whl/select_whl_tests.bzl @@ -406,9 +406,10 @@ def _test_multiple_musllinux(env): _match( env, got, - # select the one with the highest version that is matching - "pkg-0.0.1-py3-none-musllinux_1_1_x86_64.whl", + # select the one with the lowest version that is matching because we want to + # increase the compatibility "pkg-0.0.1-py3-none-musllinux_1_2_x86_64.whl", + "pkg-0.0.1-py3-none-musllinux_1_1_x86_64.whl", ) _tests.append(_test_multiple_musllinux) @@ -434,6 +435,28 @@ def _test_multiple_musllinux_exact_params(env): _tests.append(_test_multiple_musllinux_exact_params) +def _test_multiple_highest_closest_match(env): + got = _select_whl( + whls = [ + "pkg-0.0.1-py3-none-musllinux_1_4_x86_64.whl", + "pkg-0.0.1-py3-none-musllinux_1_2_x86_64.whl", + "pkg-0.0.1-py3-none-musllinux_1_1_x86_64.whl", + ], + whl_platform_tags = ["musllinux_1_3_x86_64"], + whl_abi_tags = ["none"], + python_version = "3.12", + limit = 2, + ) + _match( + env, + got, + # select the one with the lowest version, because of the input to the function + "pkg-0.0.1-py3-none-musllinux_1_1_x86_64.whl", + "pkg-0.0.1-py3-none-musllinux_1_2_x86_64.whl", + ) + +_tests.append(_test_multiple_highest_closest_match) + def _test_android(env): got = _select_whl( whls = [ From c6ffd3ed7a33ff950dbeb1b9e0eabcb6c129b810 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Mon, 15 Sep 2025 20:05:22 +0900 Subject: [PATCH 02/11] document the intention --- CHANGELOG.md | 10 ++++++---- python/private/pypi/extension.bzl | 9 +++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe2c1c92dd..4e207af4d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,10 +79,12 @@ END_UNRELEASED_TEMPLATE length errors due to too long environment variables. * (bootstrap) {obj}`--bootstrap_impl=script` now supports the `-S` interpreter setting. -* (pypi) We now by default select the highest closest match wheel version if the user is - specifying a particular version via the `whl_platform_tags` or if any version is matched via - the `*` token, then we select the lowest available version. - Fixes [#3250](https://github.com/bazel-contrib/rules_python/issues/3250). +* (pypi) We now use the MVC algorithm to select the right wheel when there are multiple wheels for + the target platform (e.g. `musllinux_1_1_x86_64` and `musllinux_1_2_x86_64`). If the user + wants to set the minimum version for the selection algorithm, use the + {attr}`pip.defaults.whl_platform_tags` attribute to configure that. If `musllinux_*_x86_64` is + specified, we will chose the lowest available wheel version. Fixes + [#3250](https://github.com/bazel-contrib/rules_python/issues/3250). {#v0-0-0-added} ### Added diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 4708c8e53a..9329cbfa44 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -492,11 +492,12 @@ preference. Will always include `"any"` even if it is not specified. The items in this list can contain a single `*` character that is equivalent to matching the -latest available version component in the platform_tag. Note, if the wheel platform tag does not -have a version component, e.g. `linux_x86_64` or `win_amd64`, then `*` will act as a regular -character. +lowest available version component in the platform_tag. Note, if the wheel platform tag does not +have a version component, e.g. `linux_x86_64` or `win_amd64`, then `*` will act as a lower bound +for the platform version, i.e. `musllinux_1_2_x86_64` means select a wheel that is built for +`muslc` 1.2 or greater, i.e. prefer `musllinux_1_2_x86_64` over `musllinux_1_3_x86_64` over `musllinux_1_4_x86_64` and so on, but exclude `musllinux_1_1_x86_64` and lower. -We will always select the highest available `platform_tag` version that is compatible with the +We will always select the lowest available `platform_tag` version that is compatible with the target platform. :::{note} From ae40e32af9639e2b92874f7c99997b179a22a905 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Mon, 15 Sep 2025 20:06:29 +0900 Subject: [PATCH 03/11] test: remove debug flag usage --- tests/pypi/select_whl/select_whl_tests.bzl | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/pypi/select_whl/select_whl_tests.bzl b/tests/pypi/select_whl/select_whl_tests.bzl index 7a28723e4b..e6744b9d8d 100644 --- a/tests/pypi/select_whl/select_whl_tests.bzl +++ b/tests/pypi/select_whl/select_whl_tests.bzl @@ -131,7 +131,6 @@ def _test_not_select_abi3(env): whl_abi_tags = ["none"], python_version = "3.13", limit = 2, - debug = True, ) _match( env, From 2722109877a9b4d5f581475950f41e7fc37478db Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Mon, 15 Sep 2025 21:01:00 +0900 Subject: [PATCH 04/11] change: always select the lowest --- python/private/pypi/select_whl.bzl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/python/private/pypi/select_whl.bzl b/python/private/pypi/select_whl.bzl index 5c94e9e037..1827550f6e 100644 --- a/python/private/pypi/select_whl.bzl +++ b/python/private/pypi/select_whl.bzl @@ -46,8 +46,6 @@ def _platform_tag_priority(*, tag, values): arch = tail version = (int(major), int(minor)) - select_highest = True - keys = [] for priority, wp in enumerate(values): want_os, sep, tail = wp.partition("_") @@ -67,7 +65,6 @@ def _platform_tag_priority(*, tag, values): want_major = "" want_minor = "" want_arch = tail - select_highest = False elif os.startswith(_ANDROID): # we set it to `0` above, so setting the `want_minor` her to `0` will make things # consistent. @@ -84,7 +81,7 @@ def _platform_tag_priority(*, tag, values): # if want_major is defined, then we know that we don't have a `*` in the matcher. want_version = (int(want_major), int(want_minor)) if want_major else None if not want_version or version <= want_version: - keys.append((priority, version if select_highest else (-version[0], -version[1]))) + keys.append((priority, (-version[0], -version[1]))) return max(keys) if keys else None From 585ccd8b7bf0890817b9613577cf57d1f746d71d Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Mon, 15 Sep 2025 21:08:18 +0900 Subject: [PATCH 05/11] extra tests --- tests/pypi/select_whl/select_whl_tests.bzl | 52 ++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/tests/pypi/select_whl/select_whl_tests.bzl b/tests/pypi/select_whl/select_whl_tests.bzl index e6744b9d8d..f7563143b8 100644 --- a/tests/pypi/select_whl/select_whl_tests.bzl +++ b/tests/pypi/select_whl/select_whl_tests.bzl @@ -434,7 +434,7 @@ def _test_multiple_musllinux_exact_params(env): _tests.append(_test_multiple_musllinux_exact_params) -def _test_multiple_highest_closest_match(env): +def _test_multiple_mvs_match(env): got = _select_whl( whls = [ "pkg-0.0.1-py3-none-musllinux_1_4_x86_64.whl", @@ -449,12 +449,58 @@ def _test_multiple_highest_closest_match(env): _match( env, got, - # select the one with the lowest version, because of the input to the function + # select the one with the lowest version + "pkg-0.0.1-py3-none-musllinux_1_2_x86_64.whl", "pkg-0.0.1-py3-none-musllinux_1_1_x86_64.whl", + ) + +_tests.append(_test_multiple_mvs_match) + +def _test_multiple_mvs_match_override_more_specific(env): + got = _select_whl( + whls = [ + "pkg-0.0.1-py3-none-musllinux_1_4_x86_64.whl", + "pkg-0.0.1-py3-none-musllinux_1_2_x86_64.whl", + "pkg-0.0.1-py3-none-musllinux_1_1_x86_64.whl", + ], + whl_platform_tags = [ + "musllinux_*_x86_64", # default to something + "musllinux_1_3_x86_64", # override the previous + ], + whl_abi_tags = ["none"], + python_version = "3.12", + limit = 2, + ) + _match( + env, + got, + # Should be the same as without the `*` match "pkg-0.0.1-py3-none-musllinux_1_2_x86_64.whl", + "pkg-0.0.1-py3-none-musllinux_1_1_x86_64.whl", + ) + +_tests.append(_test_multiple_mvs_match_override_more_specific) + +def _test_multiple_mvs_match_override_less_specific(env): + got = _select_whl( + whls = [ + "pkg-0.0.1-py3-none-musllinux_1_4_x86_64.whl", + ], + whl_platform_tags = [ + "musllinux_1_3_x86_64", # default to 1.3 + "musllinux_*_x86_64", # then override to something less specific + ], + whl_abi_tags = ["none"], + python_version = "3.12", + limit = 2, + ) + _match( + env, + got, + "pkg-0.0.1-py3-none-musllinux_1_4_x86_64.whl", ) -_tests.append(_test_multiple_highest_closest_match) +_tests.append(_test_multiple_mvs_match_override_less_specific) def _test_android(env): got = _select_whl( From 6b1fc22021b86dc29c98998728d55fadad013da9 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Mon, 15 Sep 2025 21:25:29 +0900 Subject: [PATCH 06/11] update docs --- python/private/pypi/extension.bzl | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 9329cbfa44..bab59b850a 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -493,20 +493,21 @@ Will always include `"any"` even if it is not specified. The items in this list can contain a single `*` character that is equivalent to matching the lowest available version component in the platform_tag. Note, if the wheel platform tag does not -have a version component, e.g. `linux_x86_64` or `win_amd64`, then `*` will act as a lower bound -for the platform version, i.e. `musllinux_1_2_x86_64` means select a wheel that is built for -`muslc` 1.2 or greater, i.e. prefer `musllinux_1_2_x86_64` over `musllinux_1_3_x86_64` over `musllinux_1_4_x86_64` and so on, but exclude `musllinux_1_1_x86_64` and lower. - -We will always select the lowest available `platform_tag` version that is compatible with the -target platform. +have a version component, e.g. `linux_x86_64` or `win_amd64`, then `*` will act as a regular character. :::{note} We select a single wheel and the last match will take precedence, if the platform_tag that we match has a version component (e.g. `android_x_arch`, then the version `x` will be used in the matching algorithm). -If the matcher you provide has `*`, then we will match a wheel with the highest available target platform, i.e. if `musllinux_1_1_arch` and `musllinux_1_2_arch` are both present, then we will select `musllinux_1_2_arch`. -Otherwise we will select the highest available version that is equal or lower to the specifier, i.e. if `manylinux_2_12` and `manylinux_2_17` wheels are present and the matcher is `manylinux_2_15`, then we will match `manylinux_2_12` but not `manylinux_2_17`. +Normally, the `*` in the matcher means that we will target the lowest platform version that we can +and will give preference to whls built targeting the older versions of the platform. If you +specify the version, then we will use the MVS (Minimal Version Selection) algorithm to select the +compatible wheel. As such, you need to keep in mind how to configure the target platforms to +select a particular wheel of your preference. To sum up: +* To select any wheel, use `*`. +* To exclude versions up to `X.Y` - submit a PR supporting this feature. +* To exclude versions above `X.Y`, provide the full platform tag specifier, e.g. `musllinux_1_2_x86_64`, which will ensure that no wheels with `musllinux_1_3_x86_64` or higher are selected. ::: :::{note} @@ -522,6 +523,10 @@ latest format. :::{seealso} See official [docs](https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/#platform-tag) for more information. ::: +:::{versionchanged} VERSION_NEXT_FEATURE +The matching of versioned platforms have been switched to MVS (Minimal Version Selection) +algorithm for easier evaluation logic and fewer surprises. +::: """, ), } | AUTH_ATTRS From 132124c50282eb5c3d1cd83f7f845106aadeae1e Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Mon, 15 Sep 2025 23:20:49 +0900 Subject: [PATCH 07/11] do some whl_platform_tag processing --- python/private/pypi/select_whl.bzl | 43 ++++++++++++++++++++++ tests/pypi/select_whl/select_whl_tests.bzl | 4 +- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/python/private/pypi/select_whl.bzl b/python/private/pypi/select_whl.bzl index 1827550f6e..083ef257c9 100644 --- a/python/private/pypi/select_whl.bzl +++ b/python/private/pypi/select_whl.bzl @@ -105,6 +105,46 @@ def _python_tag_priority(*, tag, implementation, py_version): version.key(ver), ) +def _filter_platform_tags(tags): + ret = [] + replacements = {} + for tag in tags: + if not ( + tag.startswith(_ANDROID) or + tag.startswith(_IOS) or + tag.startswith(_MACOSX) or + tag.startswith(_MANYLINUX) or + tag.startswith(_MUSLLINUX) + ): + ret.append(tag) + continue + + want_os, sep, tail = tag.partition("_") + if not sep: + fail("could not parse the tag") + + want_major, _, tail = tail.partition("_") + if want_major == "*": + # the expected match is any version + want_arch = tail + elif want_os.startswith(_ANDROID): + want_arch = tail + else: + # drop the minor version segment + _, _, want_arch = tail.partition("_") + + placeholder = "{}_*_{}".format(want_os, want_arch) + replacements[placeholder] = tag + if placeholder in ret: + ret.remove(placeholder) + + ret.append(placeholder) + + return [ + replacements.get(p, p) + for p in ret + ] + def _candidates_by_priority( *, whls, @@ -121,6 +161,9 @@ def _candidates_by_priority( """ py_version = version.parse(python_version, strict = True) implementation = python_tag(implementation_name) + logger.debug(lambda: "input: {}".format(whl_platform_tags)) + whl_platform_tags = _filter_platform_tags(whl_platform_tags) + logger.debug(lambda: "output: {}".format(whl_platform_tags)) ret = {} for whl in whls: diff --git a/tests/pypi/select_whl/select_whl_tests.bzl b/tests/pypi/select_whl/select_whl_tests.bzl index f7563143b8..baf2799089 100644 --- a/tests/pypi/select_whl/select_whl_tests.bzl +++ b/tests/pypi/select_whl/select_whl_tests.bzl @@ -423,12 +423,12 @@ def _test_multiple_musllinux_exact_params(env): whl_abi_tags = ["none"], python_version = "3.12", limit = 2, + debug = True, ) _match( env, got, - # select the one with the lowest version, because of the input to the function - "pkg-0.0.1-py3-none-musllinux_1_2_x86_64.whl", + # 1.2 is not within the candidates because it is not compatible "pkg-0.0.1-py3-none-musllinux_1_1_x86_64.whl", ) From b1ae72e86a0e05076182208d10753440a9aae1e1 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Mon, 15 Sep 2025 23:28:39 +0900 Subject: [PATCH 08/11] refactor: cleanup and rename --- python/private/pypi/select_whl.bzl | 103 ++++++++++++++--------------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/python/private/pypi/select_whl.bzl b/python/private/pypi/select_whl.bzl index 083ef257c9..a32e614b58 100644 --- a/python/private/pypi/select_whl.bzl +++ b/python/private/pypi/select_whl.bzl @@ -18,17 +18,59 @@ def _value_priority(*, tag, values): return max(keys) if keys else None -def _platform_tag_priority(*, tag, values): - # Implements matching platform tag - # https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/ - - if not ( +def _is_platform_tag_versioned(tag): + return ( tag.startswith(_ANDROID) or tag.startswith(_IOS) or tag.startswith(_MACOSX) or tag.startswith(_MANYLINUX) or tag.startswith(_MUSLLINUX) - ): + ) + +def _parse_platform_tags(tags): + """A helper function that parses all of the platform tags. + + The main idea is to make this more robust and have better debug messages about which will + is compatible and which is not with the target platform. + """ + ret = [] + replacements = {} + for tag in tags: + if not _is_platform_tag_versioned(tag): + ret.append(tag) + continue + + want_os, sep, tail = tag.partition("_") + if not sep: + fail("could not parse the tag") + + want_major, _, tail = tail.partition("_") + if want_major == "*": + # the expected match is any version + want_arch = tail + elif want_os.startswith(_ANDROID): + want_arch = tail + else: + # drop the minor version segment + _, _, want_arch = tail.partition("_") + + placeholder = "{}_*_{}".format(want_os, want_arch) + replacements[placeholder] = tag + if placeholder in ret: + ret.remove(placeholder) + + ret.append(placeholder) + + return [ + replacements.get(p, p) + for p in ret + ] + +def _platform_tag_priority(*, tag, values): + # Implements matching platform tag + # https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/ + + if not _is_platform_tag_versioned(tag): res = _value_priority(tag = tag, values = values) if res == None: return res @@ -39,7 +81,7 @@ def _platform_tag_priority(*, tag, values): os, _, tail = tag.partition("_") major, _, tail = tail.partition("_") - if not os.startswith(_ANDROID): + if not tag.startswith(_ANDROID): minor, _, arch = tail.partition("_") else: minor = "0" @@ -65,7 +107,7 @@ def _platform_tag_priority(*, tag, values): want_major = "" want_minor = "" want_arch = tail - elif os.startswith(_ANDROID): + elif tag.startswith(_ANDROID): # we set it to `0` above, so setting the `want_minor` her to `0` will make things # consistent. want_minor = "0" @@ -105,46 +147,6 @@ def _python_tag_priority(*, tag, implementation, py_version): version.key(ver), ) -def _filter_platform_tags(tags): - ret = [] - replacements = {} - for tag in tags: - if not ( - tag.startswith(_ANDROID) or - tag.startswith(_IOS) or - tag.startswith(_MACOSX) or - tag.startswith(_MANYLINUX) or - tag.startswith(_MUSLLINUX) - ): - ret.append(tag) - continue - - want_os, sep, tail = tag.partition("_") - if not sep: - fail("could not parse the tag") - - want_major, _, tail = tail.partition("_") - if want_major == "*": - # the expected match is any version - want_arch = tail - elif want_os.startswith(_ANDROID): - want_arch = tail - else: - # drop the minor version segment - _, _, want_arch = tail.partition("_") - - placeholder = "{}_*_{}".format(want_os, want_arch) - replacements[placeholder] = tag - if placeholder in ret: - ret.remove(placeholder) - - ret.append(placeholder) - - return [ - replacements.get(p, p) - for p in ret - ] - def _candidates_by_priority( *, whls, @@ -161,9 +163,6 @@ def _candidates_by_priority( """ py_version = version.parse(python_version, strict = True) implementation = python_tag(implementation_name) - logger.debug(lambda: "input: {}".format(whl_platform_tags)) - whl_platform_tags = _filter_platform_tags(whl_platform_tags) - logger.debug(lambda: "output: {}".format(whl_platform_tags)) ret = {} for whl in whls: @@ -265,7 +264,7 @@ def select_whl( implementation_name = implementation_name, python_version = python_version, whl_abi_tags = whl_abi_tags, - whl_platform_tags = whl_platform_tags, + whl_platform_tags = _parse_platform_tags(whl_platform_tags), logger = logger, ) From 3943a32649650feb612096b20c1a5f511ddfb787 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Mon, 15 Sep 2025 23:49:58 +0900 Subject: [PATCH 09/11] finish some extra refactoring to make things more robust --- python/private/pypi/extension.bzl | 34 ++++++++++------------ python/private/pypi/select_whl.bzl | 17 +++++++++++ tests/pypi/select_whl/select_whl_tests.bzl | 28 ++++++++++++++++++ 3 files changed, 60 insertions(+), 19 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index bab59b850a..c14912c2c9 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -492,32 +492,27 @@ preference. Will always include `"any"` even if it is not specified. The items in this list can contain a single `*` character that is equivalent to matching the -lowest available version component in the platform_tag. Note, if the wheel platform tag does not +lowest available version component in the platform_tag. If the wheel platform tag does not have a version component, e.g. `linux_x86_64` or `win_amd64`, then `*` will act as a regular character. :::{note} -We select a single wheel and the last match will take precedence, if the platform_tag that we -match has a version component (e.g. `android_x_arch`, then the version `x` will be used in the -matching algorithm). - Normally, the `*` in the matcher means that we will target the lowest platform version that we can and will give preference to whls built targeting the older versions of the platform. If you specify the version, then we will use the MVS (Minimal Version Selection) algorithm to select the compatible wheel. As such, you need to keep in mind how to configure the target platforms to -select a particular wheel of your preference. To sum up: -* To select any wheel, use `*`. -* To exclude versions up to `X.Y` - submit a PR supporting this feature. -* To exclude versions above `X.Y`, provide the full platform tag specifier, e.g. `musllinux_1_2_x86_64`, which will ensure that no wheels with `musllinux_1_3_x86_64` or higher are selected. -::: +select a particular wheel of your preference. -:::{note} -The following tag prefixes should be used instead of the legacy equivalents: -* `manylinux_2_5` instead of `manylinux1` -* `manylinux_2_12` instead of `manylinux2010` -* `manylinux_2_17` instead of `manylinux2014` - -When parsing the whl filenames `rules_python` will automatically transform wheel filenames to the -latest format. +We select a single wheel and the last match will take precedence, if the platform_tag that we +match has a version component (e.g. `android_x_arch`, then the version `x` will be used in the +MVS matching algorithm). + +Common patterns: +* To select any versioned wheel for an ``, ``, use `_*_`, e.g. + `manylinux_2_17_x86_64`. +* To exclude versions up to `X.Y` - **submit a PR supporting this feature**. +* To exclude versions above `X.Y`, provide the full platform tag specifier, e.g. + `musllinux_1_2_x86_64`, which will ensure that no wheels with `musllinux_1_3_x86_64` or higher + are selected. ::: :::{seealso} @@ -525,7 +520,8 @@ See official [docs](https://packaging.python.org/en/latest/specifications/platfo ::: :::{versionchanged} VERSION_NEXT_FEATURE The matching of versioned platforms have been switched to MVS (Minimal Version Selection) -algorithm for easier evaluation logic and fewer surprises. +algorithm for easier evaluation logic and fewer surprises. The legacy platform tags are +supported from this version without extra handling from the user. ::: """, ), diff --git a/python/private/pypi/select_whl.bzl b/python/private/pypi/select_whl.bzl index a32e614b58..759647234e 100644 --- a/python/private/pypi/select_whl.bzl +++ b/python/private/pypi/select_whl.bzl @@ -10,6 +10,21 @@ _MANYLINUX = "manylinux" _MACOSX = "macosx" _MUSLLINUX = "musllinux" +# Taken from https://peps.python.org/pep-0600/ +_LEGACY_ALIASES = { + "manylinux1_i686": "manylinux_2_5_i686", + "manylinux1_x86_64": "manylinux_2_5_x86_64", + "manylinux2010_i686": "manylinux_2_12_i686", + "manylinux2010_x86_64": "manylinux_2_12_x86_64", + "manylinux2014_aarch64": "manylinux_2_17_aarch64", + "manylinux2014_armv7l": "manylinux_2_17_armv7l", + "manylinux2014_i686": "manylinux_2_17_i686", + "manylinux2014_ppc64": "manylinux_2_17_ppc64", + "manylinux2014_ppc64le": "manylinux_2_17_ppc64le", + "manylinux2014_s390x": "manylinux_2_17_s390x", + "manylinux2014_x86_64": "manylinux_2_17_x86_64", +} + def _value_priority(*, tag, values): keys = [] for priority, wp in enumerate(values): @@ -36,6 +51,8 @@ def _parse_platform_tags(tags): ret = [] replacements = {} for tag in tags: + tag = _LEGACY_ALIASES.get(tag, tag) + if not _is_platform_tag_versioned(tag): ret.append(tag) continue diff --git a/tests/pypi/select_whl/select_whl_tests.bzl b/tests/pypi/select_whl/select_whl_tests.bzl index baf2799089..1c28fcca5f 100644 --- a/tests/pypi/select_whl/select_whl_tests.bzl +++ b/tests/pypi/select_whl/select_whl_tests.bzl @@ -231,6 +231,34 @@ def _test_select_by_supported_cp_version(env): _tests.append(_test_select_by_supported_cp_version) +def _test_legacy_manylinux(env): + for legacy, replacement in { + "manylinux1": "manylinux_2_5", + "manylinux2010": "manylinux_2_12", + "manylinux2014": "manylinux_2_17", + }.items(): + for plat in [legacy, replacement]: + whls = [ + "pkg-0.0.1-py3-none-{}_x86_64.whl".format(plat), + "pkg-0.0.1-py3-none-any.whl", + ] + + got = _select_whl( + whls = whls, + whl_platform_tags = ["{}_x86_64".format(legacy)], + whl_abi_tags = ["none"], + python_version = "3.10", + ) + want = _select_whl( + whls = whls, + whl_platform_tags = ["{}_x86_64".format(replacement)], + whl_abi_tags = ["none"], + python_version = "3.10", + ) + _match(env, [got], want.filename) + +_tests.append(_test_legacy_manylinux) + def _test_supported_cp_version_manylinux(env): whls = [ "pkg-0.0.1-py2.py3-none-manylinux_1_1_x86_64.whl", From 8da07e45a1e6e7c673b52faf0bee86e7a71e8268 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Mon, 15 Sep 2025 23:51:26 +0900 Subject: [PATCH 10/11] better error message --- python/private/pypi/select_whl.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/pypi/select_whl.bzl b/python/private/pypi/select_whl.bzl index 759647234e..b32fc68f01 100644 --- a/python/private/pypi/select_whl.bzl +++ b/python/private/pypi/select_whl.bzl @@ -59,7 +59,7 @@ def _parse_platform_tags(tags): want_os, sep, tail = tag.partition("_") if not sep: - fail("could not parse the tag") + fail("could not parse the tag: {}".format(tag)) want_major, _, tail = tail.partition("_") if want_major == "*": From 246848233ca1bdafe5f8f337b31159eb51ebe166 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 15 Sep 2025 15:58:58 -0700 Subject: [PATCH 11/11] Update CHANGELOG.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e207af4d8..b62898fade 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,12 +79,13 @@ END_UNRELEASED_TEMPLATE length errors due to too long environment variables. * (bootstrap) {obj}`--bootstrap_impl=script` now supports the `-S` interpreter setting. -* (pypi) We now use the MVC algorithm to select the right wheel when there are multiple wheels for - the target platform (e.g. `musllinux_1_1_x86_64` and `musllinux_1_2_x86_64`). If the user +* (pypi) We now use the Minimal Version Selection (MVS) algorithm to select + the right wheel when there are multiple wheels for the target platform + (e.g. `musllinux_1_1_x86_64` and `musllinux_1_2_x86_64`). If the user wants to set the minimum version for the selection algorithm, use the - {attr}`pip.defaults.whl_platform_tags` attribute to configure that. If `musllinux_*_x86_64` is - specified, we will chose the lowest available wheel version. Fixes - [#3250](https://github.com/bazel-contrib/rules_python/issues/3250). + {attr}`pip.defaults.whl_platform_tags` attribute to configure that. If + `musllinux_*_x86_64` is specified, we will chose the lowest available + wheel version. Fixes [#3250](https://github.com/bazel-contrib/rules_python/issues/3250). {#v0-0-0-added} ### Added