From 19dc381db36ed5851e1edd8a0ef1457e5f1ba4f1 Mon Sep 17 00:00:00 2001 From: Showta Ishizaki Date: Sat, 26 Sep 2026 23:20:46 +0900 Subject: [PATCH 1/2] fix(pypi): skip a pip.parse version with no interpreter for the host A pip.parse whose python_version has a toolchain but no interpreter that runs on the host fails the whole pip extension. That takes down every other hub with it, including the root module's own, even when nothing in the build uses the failing one. This shows up on Windows ARM64. grpc, protoc-gen-validate and rules_fuzzing each call pip.parse for Python 3.9 and 3.10, and there is no CPython 3.9 or 3.10 build for aarch64-pc-windows-msvc, so evaluating the extension fails with "Unable to find interpreter for pip hub 'grpc_python_dependencies' for python_version=3.9" before any of it is needed. A version missing from minor_mapping is already skipped with an info message, leaving the hub's select to fail later only if that version is actually used. Treat a version with no host interpreter the same way. --- .../pip_parse_skip_host_less_version.fixed.md | 5 ++ python/private/pypi/hub_builder.bzl | 26 ++++++- tests/pypi/hub_builder/hub_builder_tests.bzl | 69 +++++++++++++++++++ 3 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 news/pip_parse_skip_host_less_version.fixed.md diff --git a/news/pip_parse_skip_host_less_version.fixed.md b/news/pip_parse_skip_host_less_version.fixed.md new file mode 100644 index 0000000000..7edd8a9f06 --- /dev/null +++ b/news/pip_parse_skip_host_less_version.fixed.md @@ -0,0 +1,5 @@ +(pypi) A `pip.parse` for a Python version that has no interpreter for the host +platform is now skipped, like a version missing from the minor mapping, instead +of failing the whole `pip` extension. A dependency that lists, say, Python 3.9 +no longer breaks every other hub on hosts that have no CPython 3.9 build, such +as Windows on ARM64. diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 596cfb2cbe..d1b583dc32 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -179,6 +179,25 @@ def _pip_parse(self, module_ctx, pip_attr, python_version = None): )) return + if (pip_attr.python_interpreter_target == None and + not pip_attr.python_interpreter and + _host_interpreter_name(python_version) not in self._available_interpreters): + # The version has a toolchain, but none of its interpreters runs on + # this host, e.g. there is no CPython build for this os and cpu. Treat + # it like a version missing from minor_mapping instead of failing the + # extension, which would also take down every other version and hub, + # including ones this host can use. + self._logger.info(lambda: ( + "Ignoring pip python version '{version}' for hub " + + "'{hub}' in module '{module}' because there is no " + + "interpreter for it that runs on this host." + ).format( + hub = self.name, + module = self.module_name, + version = python_version, + )) + return + _set_get_index_urls(self, module_ctx, pip_attr) self._platforms[python_version] = _platforms( module_ctx, @@ -395,12 +414,13 @@ def _set_get_index_urls(self, mctx, pip_attr): ) return True +def _host_interpreter_name(python_version): + return "python_{}_host".format(python_version.replace(".", "_")) + def _detect_interpreter(self, pip_attr, python_version): python_interpreter_target = pip_attr.python_interpreter_target if python_interpreter_target == None and not pip_attr.python_interpreter: - python_name = "python_{}_host".format( - python_version.replace(".", "_"), - ) + python_name = _host_interpreter_name(python_version) if python_name not in self._available_interpreters: fail(( "Unable to find interpreter for pip hub '{hub_name}' for " + diff --git a/tests/pypi/hub_builder/hub_builder_tests.bzl b/tests/pypi/hub_builder/hub_builder_tests.bzl index f421c7e351..1d4a602dc9 100644 --- a/tests/pypi/hub_builder/hub_builder_tests.bzl +++ b/tests/pypi/hub_builder/hub_builder_tests.bzl @@ -529,6 +529,75 @@ new-package==0.0.1 --hash=sha256:deadb00f2 _tests.append(_test_simple_multiple_python_versions) +def _test_skip_python_version_without_host_interpreter(env): + # A module can ask for a Python version that has a toolchain but no + # interpreter for this host, e.g. a dependency listing 3.9 when rules_python + # ships no 3.9 CPython for aarch64-pc-windows-msvc. That version must be + # skipped instead of failing the whole extension, like a version missing + # from minor_mapping is. + builder = hub_builder( + env, + available_interpreters = { + "python_3_15_host": "unit_test_interpreter_target", + }, + minor_mapping = { + "3.15": "3.15.19", + "3.16": "3.16.9", + }, + ) + builder.pip_parse( + mocks.mctx( + mock_files = { + "requirements_3_15.txt": """ +simple==0.0.1 --hash=sha256:deadbeef +""", + }, + os_name = "linux", + arch_name = "amd64", + ), + _parse( + hub_name = "pypi", + python_version = "3.15", + requirements_lock = "requirements_3_15.txt", + ), + ) + builder.pip_parse( + mocks.mctx( + mock_files = { + "requirements_3_16.txt": """ +simple==0.0.2 --hash=sha256:deadb00f +""", + }, + os_name = "linux", + arch_name = "amd64", + ), + _parse( + hub_name = "pypi", + python_version = "3.16", + requirements_lock = "requirements_3_16.txt", + ), + ) + pypi = builder.build() + + pypi.exposed_packages().contains_exactly(["simple"]) + pypi.whl_map().contains_exactly({ + "simple": { + "pypi_315_simple": [ + whl_config_setting(version = "3.15"), + ], + }, + }) + pypi.whl_libraries().contains_exactly({ + "pypi_315_simple": { + "config_load": "@pypi//:config.bzl", + "dep_template": "@pypi//{name}:{target}", + "python_interpreter_target": "unit_test_interpreter_target", + "requirement": "simple==0.0.1 --hash=sha256:deadbeef", + }, + }) + +_tests.append(_test_skip_python_version_without_host_interpreter) + def _test_simple_with_markers(env): sub_tests = { ("osx", "aarch64"): "torch==2.4.1 --hash=sha256:deadbeef", From a1ed05f5f1a0fffc8e000b073d102a7be2065768 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 27 Sep 2026 13:15:59 -0700 Subject: [PATCH 2/2] docs(pypi): format news fragment for #4184 Rename the news fragment to match the PR number, use Sphinx MyST {obj} cross-references, avoid internal minor_mapping terminology, and append the issue link. --- news/4184.fixed.md | 6 ++++++ news/pip_parse_skip_host_less_version.fixed.md | 5 ----- 2 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 news/4184.fixed.md delete mode 100644 news/pip_parse_skip_host_less_version.fixed.md diff --git a/news/4184.fixed.md b/news/4184.fixed.md new file mode 100644 index 0000000000..edd4167df7 --- /dev/null +++ b/news/4184.fixed.md @@ -0,0 +1,6 @@ +(pypi) A {obj}`pip.parse` call for a Python version that has no interpreter for +the host platform is now skipped, like a version with no registered toolchain, +instead of failing the whole {obj}`pip` extension. A dependency that lists, +say, Python 3.9 no longer breaks every other hub on hosts that have no CPython +3.9 build, such as Windows on ARM64 +([#4184](https://github.com/bazel-contrib/rules_python/issues/4184)). diff --git a/news/pip_parse_skip_host_less_version.fixed.md b/news/pip_parse_skip_host_less_version.fixed.md deleted file mode 100644 index 7edd8a9f06..0000000000 --- a/news/pip_parse_skip_host_less_version.fixed.md +++ /dev/null @@ -1,5 +0,0 @@ -(pypi) A `pip.parse` for a Python version that has no interpreter for the host -platform is now skipped, like a version missing from the minor mapping, instead -of failing the whole `pip` extension. A dependency that lists, say, Python 3.9 -no longer breaks every other hub on hosts that have no CPython 3.9 build, such -as Windows on ARM64.