Skip to content

Commit ddf6496

Browse files
committed
Fix extension module packaging for Windows/macOS multi-config generators
Root cause: PRTree*.pyd (Windows) and PRTree*.so (macOS) were not being placed in the correct package directory, causing ModuleNotFoundError. Changes: 1. Add fallback code in setup.py to copy extension file to extdir after build - Recursively search build tree for PRTree*.pyd or PRTree*.so - Prefer files in Release directory, then by most recent mtime - Only copy if extension doesn't already exist in extdir - Handles multi-config generators (MSVC, Xcode) properly 2. Create tests/_ci_debug_import.py for robust CI diagnostics - Prints Python version, platform, and executable - Attempts to import python_prtree and lists package contents - Tests PRTree3D import with full traceback on failure - Exits non-zero on import failure 3. Update CI workflow to use debug script - CIBW_TEST_COMMAND: python {project}/tests/_ci_debug_import.py && pytest - CIBW_TEST_COMMAND_WINDOWS: python {project}\tests\_ci_debug_import.py && pytest - Avoids complex quote escaping issues with inline Python This fixes ModuleNotFoundError on both Windows and macOS CI. All 123 tests pass locally.
1 parent 65db6ad commit ddf6496

File tree

3 files changed

+73
-4
lines changed

3 files changed

+73
-4
lines changed

.github/workflows/cibuildwheel.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,8 @@ jobs:
224224
CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.manylinux_image }}
225225
CIBW_BUILD: cp${{ matrix.python }}-${{ matrix.platform_id }}
226226
CIBW_BEFORE_BUILD: pip install pybind11
227-
CIBW_TEST_COMMAND: pytest {project}/tests -vv
228-
CIBW_TEST_COMMAND_WINDOWS: >-
229-
python -c "import sys, importlib.util, pathlib, traceback; print('sys.version:', sys.version); print('sys.platform:', sys.platform); exec('try:\n import python_prtree\n p = pathlib.Path(python_prtree.__file__).parent\n print(\"python_prtree dir:\", p)\n print(\"contents:\", sorted(x.name for x in p.iterdir()))\n print(\"find_spec(PRTree):\", importlib.util.find_spec(\"python_prtree.PRTree\"))\n from python_prtree import PRTree3D\n print(\"import PRTree3D ok\")\nexcept Exception as e:\n print(\"IMPORT FAILED:\", repr(e))\n traceback.print_exc()\n raise')" &&
230-
pytest {project}\\tests -vv
227+
CIBW_TEST_COMMAND: python {project}/tests/_ci_debug_import.py && pytest {project}/tests -vv
228+
CIBW_TEST_COMMAND_WINDOWS: python {project}\tests\_ci_debug_import.py && pytest {project}\tests -vv
231229
CIBW_TEST_REQUIRES: pytest numpy
232230
CIBW_BUILD_VERBOSITY: 1
233231
CIBW_ARCHS: ${{ matrix.arch }}

setup.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,29 @@ def build_extension(self, ext):
113113
subprocess.check_call(
114114
["cmake", "--build", "."] + build_args, cwd=self.build_temp
115115
)
116+
117+
import glob
118+
import shutil
119+
ext_suffix = self.get_ext_filename(ext.name).split('.')[-1]
120+
if platform.system() == "Windows":
121+
pattern = f"PRTree*.pyd"
122+
else:
123+
pattern = f"PRTree*.so"
124+
125+
expected_file = os.path.join(extdir, os.path.basename(self.get_ext_filename(ext.name)))
126+
if not os.path.exists(expected_file):
127+
found_files = glob.glob(os.path.join(self.build_temp, "**", pattern), recursive=True)
128+
if found_files:
129+
release_files = [f for f in found_files if "Release" in f or "release" in f]
130+
if release_files:
131+
src_file = max(release_files, key=os.path.getmtime)
132+
else:
133+
src_file = max(found_files, key=os.path.getmtime)
134+
135+
print(f"Copying extension from {src_file} to {extdir}")
136+
shutil.copy2(src_file, extdir)
137+
else:
138+
print(f"Warning: Could not find {pattern} in build tree")
116139

117140

118141
setup(

tests/_ci_debug_import.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env python
2+
"""Debug script to diagnose import issues in CI environments."""
3+
import sys
4+
import pathlib
5+
import importlib.util
6+
import traceback
7+
8+
print("=" * 60)
9+
print("CI Debug Import Check")
10+
print("=" * 60)
11+
print(f"sys.version: {sys.version}")
12+
print(f"sys.platform: {sys.platform}")
13+
print(f"sys.executable: {sys.executable}")
14+
print()
15+
16+
try:
17+
import python_prtree
18+
print(f"✓ python_prtree imported successfully")
19+
print(f" Location: {python_prtree.__file__}")
20+
21+
pkg_dir = pathlib.Path(python_prtree.__file__).parent
22+
print(f" Package directory: {pkg_dir}")
23+
print(f" Contents: {sorted(x.name for x in pkg_dir.iterdir())}")
24+
print()
25+
26+
spec = importlib.util.find_spec("python_prtree.PRTree")
27+
print(f" find_spec('python_prtree.PRTree'): {spec}")
28+
print()
29+
30+
from python_prtree import PRTree3D
31+
print(f"✓ PRTree3D imported successfully")
32+
print(f" PRTree3D: {PRTree3D}")
33+
print()
34+
35+
print("=" * 60)
36+
print("All imports successful!")
37+
print("=" * 60)
38+
sys.exit(0)
39+
40+
except Exception as e:
41+
print(f"✗ IMPORT FAILED: {repr(e)}")
42+
print()
43+
traceback.print_exc()
44+
print()
45+
print("=" * 60)
46+
print("Import check failed - see traceback above")
47+
print("=" * 60)
48+
sys.exit(1)

0 commit comments

Comments
 (0)