From 16852e58431be9010b29ca8c30b9e8fd2e945787 Mon Sep 17 00:00:00 2001 From: Anthony Shoumikhin Date: Sun, 25 Oct 2026 13:57:23 -0700 Subject: [PATCH 1/2] Make the clean-install check treat hidden packages as missing The wheel check in .ci/scripts/wheel/test_clean_install.py hides every package the wheel does not declare, then imports the main modules. It hid them with an import finder that raised an error from find_spec. A missing package does not do that. For a missing package, importlib.util.find_spec returns None, and only an actual import fails. torch._dynamo relies on this: when it loads, it calls find_spec on a list of optional packages, hypothesis among them. So once a package on that list is not declared by the wheel, the check fails even though a real install works. Mark hidden packages with None in sys.modules instead. Python then raises ModuleNotFoundError on import and returns None from find_spec, the same as for a missing package. The error message still says the package is not declared by the wheel. --- .ci/scripts/wheel/test_clean_install.py | 36 ++++++++++++++----------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/.ci/scripts/wheel/test_clean_install.py b/.ci/scripts/wheel/test_clean_install.py index 65943277324..13840ed8807 100644 --- a/.ci/scripts/wheel/test_clean_install.py +++ b/.ci/scripts/wheel/test_clean_install.py @@ -215,10 +215,10 @@ def run_tests(work_dir: Path) -> None: # Reads its arguments from stdin, blocks the named modules, then imports. A blocked module -# raises ModuleNotFoundError exactly as it would be absent, so the traceback shows the import -# chain that wanted it. +# behaves like a missing one to import and find_spec, so the traceback shows the import chain +# that wanted it. _IMPORT_PROBE = """ -import importlib.abc, importlib.util, json, os, sys +import importlib, json, os, sys # Drop the working directory, which `python -c` puts first. The equivalent flag, -P, is only # available from 3.11. @@ -229,19 +229,23 @@ def run_tests(work_dir: Path) -> None: request = json.load(sys.stdin) blocked = set(request["blocked"]) - -class _Blocker(importlib.abc.MetaPathFinder): - def find_spec(self, fullname, path=None, target=None): - if fullname.split(".")[0] in blocked: - raise ModuleNotFoundError( - f"No module named {fullname!r} (not declared by the executorch wheel)", - name=fullname, - ) - return None - - -sys.meta_path.insert(0, _Blocker()) -importlib.import_module(request["module"]) +# A None entry in sys.modules makes an import raise ModuleNotFoundError while +# importlib.util.find_spec returns None, which is how a missing package behaves. A finder that +# raises from find_spec instead breaks code that probes for optional packages, such as +# torch._dynamo, which looks up hypothesis that way. +for name in blocked: + sys.modules[name] = None + +try: + importlib.import_module(request["module"]) +except ModuleNotFoundError as error: + package = (error.name or "").split(".")[0] + if package in blocked: + raise ModuleNotFoundError( + f"No module named {package!r} (not declared by the executorch wheel)", + name=package, + ) from error + raise """ From c1db4b7fa8ada8542c639c608a32c71c6094c0a8 Mon Sep 17 00:00:00 2001 From: Anthony Shoumikhin Date: Sun, 25 Oct 2026 12:59:02 -0700 Subject: [PATCH 2/2] Stop installing test-only packages with the executorch package `pip install executorch` installed four packages that are only there for the test suite: expecttest, hypothesis, kgb and parameterized (plus sortedcontainers, which hypothesis brings). Nothing a user imports needs them. The only shipped modules that need them are used by tests or by the edge dialect YAML generator. Move them from the package's runtime dependencies to requirements-dev.txt. The developer setup (install_requirements.py) and every CI job that runs tests already install that file, so tests still get them. --- requirements-dev.txt | 5 +++++ setup.py | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index e9575084648..a6f33b6f885 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -21,3 +21,8 @@ pytest-xdist pytest-rerunfailures==15.1 pytest-json-report gguf # For extension/llm/export/test/test_gguf.py (GGUF Q4_K/Q6_K dequant tests). +# Needed only by tests and development tools, so the executorch package does not depend on them. +expecttest +hypothesis +kgb +parameterized diff --git a/setup.py b/setup.py index c3d73287242..811619321ec 100644 --- a/setup.py +++ b/setup.py @@ -1154,15 +1154,11 @@ def _base_dependencies() -> List[str]: project's runtime needs. """ return [ - "expecttest", "flatbuffers", - "hypothesis", - "kgb", "mpmath==1.3.0", "numpy>=2.0.0; python_version >= '3.10'", "packaging", "pandas>=2.2.2; python_version >= '3.10'", - "parameterized", # The Qualcomm backend needs both, on different paths. Any lowering reads the CPU vendor # to disable an mkldnn path that crashes on AMD, whether or not an SDK is already set up. # Fetching an SDK additionally needs requests, which its downloader imports at module