From 9bb93dfb8d6d2a0d55e44d451cfa4e6e3b7a8283 Mon Sep 17 00:00:00 2001 From: LeSingh1 Date: Tue, 4 Aug 2026 15:39:17 -0700 Subject: [PATCH] Match private extension modules against the in-package path only check_cython_abi's private-module filter tested `so_path.parts` on the absolute path, so any ancestor directory starting with an underscore made every module look private. That is the normal layout under manylinux (/opt/_internal/cpython-*/) and in GitHub Actions containers (/__w/), where `generate` then writes zero ABI files and exits 0 -- a green run with no coverage at all. `check`'s new-module scan had no filter, while `generate` skipped private modules. Since `generate` never wrote an .abi.json for them, `check` reported every private module as "New module added" on every run and set has_allowed_changes, so it could not print "No changes found" for a package shipping private submodules (cuda.bindings has _bindings/, _internal/, _lib/). Extract the predicate into iter_public_extension_modules() so both paths use it, and match only on the path relative to the package root. --- toolshed/check_cython_abi.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/toolshed/check_cython_abi.py b/toolshed/check_cython_abi.py index 155d9c625f3..b72edf40c5c 100644 --- a/toolshed/check_cython_abi.py +++ b/toolshed/check_cython_abi.py @@ -92,6 +92,21 @@ def is_cython_module(module: object) -> bool: return hasattr(module, "__pyx_capi__") +def iter_public_extension_modules(build_dir: Path): + """Yield the extension modules under `build_dir` that are part of the public ABI. + + Private modules (e.g. cuda/bindings/_internal/utils.so) are skipped. Only the + path *inside* the package is inspected: directories above it routinely start + with an underscore (manylinux installs Python under /opt/_internal, GitHub + Actions containers check out under /__w), and those must not make every + module look private. + """ + for so_path in Path(build_dir).glob(f"**/*{EXT_SUFFIX}"): + if any(part.startswith("_") for part in so_path.relative_to(build_dir).parts): + continue + yield so_path + + ###################################################################################### # STRUCTS @@ -473,7 +488,7 @@ def check(package: str, abi_dir: Path) -> bool: print(f"No module found for {abi_path.relative_to(abi_dir)}") has_errors = True - for so_path in Path(build_dir).glob(f"**/*{EXT_SUFFIX}"): + for so_path in iter_public_extension_modules(build_dir): module = import_from_path(package, build_dir, so_path) if hasattr(module, "__pyx_capi__"): abi_path = so_path_to_abi_path(so_path, build_dir, abi_dir) @@ -498,10 +513,7 @@ def generate(package: str, abi_dir: Path) -> bool: return True build_dir = get_package_path(package) - for so_path in Path(build_dir).glob(f"**/*{EXT_SUFFIX}"): - if any(x.startswith("_") for x in so_path.parts): - # Skip private modules (e.g. _driver.so) since they are not part of the public ABI - continue + for so_path in iter_public_extension_modules(build_dir): try: module = import_from_path(package, build_dir, so_path) except ImportError: