Fix pip installation: Migrate from CFFI/f2py to ctypes (SpacePy-style)#155
Open
sapols wants to merge 24 commits intonasa:masterfrom
Open
Fix pip installation: Migrate from CFFI/f2py to ctypes (SpacePy-style)#155sapols wants to merge 24 commits intonasa:masterfrom
sapols wants to merge 24 commits intonasa:masterfrom
Conversation
This commit fixes the long-standing issue where kamodo-ccmc requires manual compilation of C and Fortran extensions before installation. Following SpacePy's proven approach, the build system now automatically compiles extensions during 'pip install'. Changes: - Created custom build_ext class in kamodo_ccmc/build_tools.py - Refactored CFFI builder scripts to be importable as modules - Updated setup.py to use custom build commands - Updated setup.cfg (added cffi, fixed setuptools constraint) - Added graceful degradation to model readers - Updated README.md and QuickStart.md documentation Outcome: Users can now simply 'pip install kamodo-ccmc' and extensions will be automatically compiled if compilers are available. Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Review fixes for the initial pip installation automation: 1. build_tools.py: Rewrote to override install/develop commands - build_ext alone won't trigger without ext_modules defined - Now extensions compile after package installation completes - Cleaner architecture with compile_extensions() helper function 2. setup.py: Fixed import issue for fresh installs - Added sys.path manipulation to import build_tools before install - Properly registers all three command classes - Graceful fallback if build_tools import fails 3. Model readers: Added __init__ checks for clear error messages - MODEL classes now raise ImportError with actionable instructions - openggcm_gm_tocdf.py: Function-level check added Co-Authored-By: Claude Opus 4.5 <[email protected]>
…ilation Rewrite the build system to use cffi_modules in setup.py instead of custom install/develop commands. This is the proper way to integrate CFFI with setuptools and ensures extensions compile during wheel building. Changes: - setup.py: Use cffi_modules parameter to register CFFI builders - pyproject.toml: Add cffi to build-requires, specify build-backend - interpolate_amrdata_extension_build.py: - Add _rel_path() helper for setuptools-compatible relative paths - Use full module path: kamodo_ccmc.readers.OCTREE_BLOCK_GRID._interpolate_amrdata - Add include_dirs for header file resolution - interpolate_tri2d_extension_build.py: - Same changes as above for Tri2D module Verified working: - pip install . now compiles both CFFI extensions automatically - Extensions install to correct locations in site-packages - Extensions load and work correctly (tested direct import) Note: Full integration testing blocked by upstream kamodo package (v23.3.0) using deprecated numpy.distutils, which fails on Python 3.12. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Complete the pip install fix by adding OpenGGCM Fortran compilation: - setup.py: Add custom build_ext class that compiles Fortran via f2py - Follows SpacePy's pattern: register Extension to trigger build_ext - Runs 'python -m numpy.f2py' via subprocess - Handles build_lib output directory for wheel builds - Graceful degradation if gfortran not available - KAMODO_RELEASE env var for strict release builds - pyproject.toml: Add numpy to build-requires for f2py All three extension types now compile automatically during pip install: 1. OCTREE_BLOCK_GRID (CFFI) - for SWMF-GM reader 2. Tri2D (CFFI) - for GAMER-AM reader 3. readOpenGGCM (f2py) - for OpenGGCM reader Verified working in Python 3.11: - All extensions compile during wheel build - All model readers import and work correctly Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add automated CI/CD pipelines for Kamodo: 1. .github/workflows/ci.yml - CI for PRs and pushes - Tests on Ubuntu and macOS - Python 3.10, 3.11, 3.12 - Verifies all extensions compile and import 2. .github/workflows/build-wheels.yml - Wheel building for releases - Builds wheels for Linux (manylinux2014), macOS (Intel + ARM), Windows - Python 3.10, 3.11, 3.12, 3.13 - Uses cibuildwheel for cross-platform builds - Publishes to PyPI on version tags (requires PyPI trusted publishing setup) - Manual trigger option for testing 3. pyproject.toml - cibuildwheel configuration - Platform-specific settings for Linux, macOS, Windows - Fortran compiler setup for each platform - Wheel repair tools (auditwheel for Linux, delvewheel for Windows) To use: - CI runs automatically on PRs - Wheel builds trigger on 'v*' tags or manual dispatch - PyPI publishing requires setting up trusted publishing on PyPI Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit combines the best approaches from both AI solutions: From Claude's solution: - Native cffi_modules integration (cleaner, officially recommended) - Comprehensive graceful degradation with try/except and clear error messages - Complete CI/CD workflows (Linux, macOS Intel+ARM, Windows) - cibuildwheel configuration in pyproject.toml From Codex's solution: - Runtime library bundling for portable wheels (SpacePy-style) - KAMODO_SKIP_NATIVE environment variable to skip extensions - Python 3.12+ distutils compatibility workaround - kamodo_ccmc/libs/ package with Windows DLL handling - docs/PyPI-Release.md documentation Changes: - setup.py: Added runtime lib bundling, KAMODO_SKIP_NATIVE, distutils fix - kamodo_ccmc/__init__.py: Added Windows DLL path handling - kamodo_ccmc/libs/__init__.py: New package for bundled runtime libs - .github/workflows/ci.yml: Added Python 3.13 and Windows coverage - pyproject.toml: Fixed TOML syntax error - MANIFEST.in: Added Fortran files, removed build artifacts - docs/PyPI-Release.md: New release checklist documentation Co-Authored-By: Claude Opus 4.5 <[email protected]>
Ensures Tri2D extension and bundled runtime libs are properly included in wheels by design, not by accident. Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit adds key improvements from the Codex solution: 1. Add _clean_source_artifacts() to setup.py - Removes intermediate .o and .so files after build - Prevents stale artifacts from contaminating wheels 2. Create package marker __init__.py files - kamodo_ccmc/readers/OCTREE_BLOCK_GRID/__init__.py - kamodo_ccmc/readers/Tri2D/__init__.py - Ensures setuptools properly discovers extension packages 3. Fix import paths to use relative imports - swmfgm_4D.py: from .OCTREE_BLOCK_GRID._interpolate_amrdata - gameragm_4D.py: from .Tri2D._interpolate_tri2d - swmf_gm_octree.py: from .OCTREE_BLOCK_GRID._interpolate_amrdata 4. Retain graceful degradation pattern - Readers warn but don't crash when extensions unavailable - Clear error messages guide users to install compilers Combined with Claude's CI/CD infrastructure, this creates the optimal pip installation solution with comprehensive platform support. Co-Authored-By: Claude Opus 4.5 <[email protected]>
- OpenGGCM: Add FORTRAN_FUNCTIONS dict, type aliases (int4, real4), single name resolution, fail-fast on missing functions, ABI docs - OCTREE_BLOCK_GRID: Add c_int_p/c_float_p type aliases - Tri2D: Add c_int_p/c_float_p type aliases - Remove old CFFI build scripts Co-Authored-By: Claude Opus 4.5 <[email protected]>
- CI: Change to non-editable install (pip install . not -e) Fixes OpenGGCM not compiling due to PEP 660 changes in setuptools 64+ - CI: Make test fail if any extension fails to compile Previously showed "All extensions loaded successfully!" even when OpenGGCM was unavailable - now properly validates each extension - pyproject.toml: Add zip-safe = false for setuptools compatibility - README: Document proper editable install workflow for developers (build_ext --inplace before pip install -e) Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
The zip-safe setting was causing build failures because setuptools expected a full [project] section when tool.setuptools was present. Kamodo uses setup.cfg for metadata, not pyproject.toml. The zip-safe setting wasn't needed anyway - the real PEP 660 fixes are: (1) CI uses non-editable install, (2) README documents manual build step for developer editable installs. Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Without ext_modules declared, setuptools skips build_ext entirely, even with a custom build_ext command registered. Adding dummy Extension objects with empty source lists makes setuptools call our custom build_ext.run() method, which handles the actual C/Fortran compilation. Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
The original CFFI build scripts embedded C code inline that provided: - xyz_ranges() and interpolate_amrdata_multipos() for OCTREE - interpolate_tri2d_plus_1d_multipos() for Tri2D These functions were not in the tracked C files, causing undefined symbol errors when linking the shared libraries. This commit: - Extracts CFFI inline code to ctypes_wrappers.c for both modules - Adds globals.c to define extern variables (previously in CFFI wrapper) - Updates setup.py to compile the new files - Adds -Dno_idl flag to OCTREE compilation for ctypes-compatible signatures Co-Authored-By: Claude Opus 4.5 <[email protected]>
When running pip install . followed by python -c import kamodo_ccmc, Python was finding the local source kamodo_ccmc/ directory first instead of the installed package in site-packages. The source directory doesn't have the compiled extensions, causing the import tests to fail. Fix by running the test from a different directory (runner.temp) so Python imports from site-packages where the extensions are installed. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
The existing C files already define the global variables. The globals.c files were duplicating these definitions, causing linker errors. Co-Authored-By: Claude Opus 4.5 <[email protected]>
shutil.move() was failing because outdir was a relative path like 'build/lib.linux-x86_64-cpython-311/...' but we had already chdir'd to the source directory for compilation. The relative path then resolved incorrectly, causing 'No such file or directory' errors. Fix: Use os.path.abspath() to convert outdir to an absolute path before changing directories. Co-Authored-By: Claude Opus 4.5 <[email protected]>
1. Add libinterpolate_*.so to macOS search list in loaders - distutils creates .so files on macOS (not .dylib) when using gcc/clang - The loaders were looking for .dylib but the compiled file was .so 2. Create gfortran symlink on macOS CI - brew install gcc installs gfortran as gfortran-XX (e.g., gfortran-14) - Create symlink so 'gfortran' command works Co-Authored-By: Claude Opus 4.5 <[email protected]>
Contributor
Author
|
@darrendezeeuw this is identical to the PR in my fork I already showed you, just opening here for better visibility after I cleaned up the cruft. No rush nor pressure to accept, just for your consideration! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR fixes Kamodo's broken pip installation by replacing CFFI and f2py with direct ctypes loading of shared libraries—the same approach SpacePy uses successfully.
The Problem:
pip install kamodo-ccmcfrom PyPI is broken:The Solution: Compile C/Fortran code to standalone shared libraries (
.so/.dll/.dylib) at build time, then load them via Python's built-inctypesat runtime. No CFFI, no f2py, no numpy build dependency.Status: ✅ CI passing on Ubuntu, macOS, and Windows for Python 3.10 & 3.11 |⚠️ Python 3.12 & 3.13 fail (Kamodo compatibility — separate issue)
What Changed
Build System (
setup.py,pyproject.toml)distutils.ccompilerabstraction (respects CC env var)gfortrancompilation to shared libraries (.so/.dll/.dylib)numpyat build timecp310-abi3-*, works for Python 3.10+)New
setup.pyfeatures (SpacePy-style):build_extcommand that compiles C/Fortran directlydistutils.ccompiler(respectsCCenv var and platform defaults; no hardcodedgcc)os.path.abspath(self.build_lib)for correct output paths during compilationlibgfortran,libquadmath) into wheels for portability-Wl,-rpath,@loader_path/../libs); rewrites hardcodedlibgcc_spaths in bundled libspy_limited_api='cp310'); CI verifies produced wheel tagsKAMODO_RELEASE=1env var for strict mode (fails on any compilation error)KAMODO_SKIP_NATIVE=1env var to skip native compilation (for debugging)New C Wrapper Files
The CFFI build scripts had inline C code for wrapper functions. These have been extracted to standalone files:
kamodo_ccmc/readers/OCTREE_BLOCK_GRID/ctypes_wrappers.c- Containsxyz_ranges()andinterpolate_amrdata_multipos()kamodo_ccmc/readers/Tri2D/ctypes_wrappers.c- Containsinterpolate_tri2d_plus_1d_multipos()C Extension Loaders (OCTREE_BLOCK_GRID, Tri2D)
Before: CFFI with
ffi.dlopen()andffi.new()for memory allocationAfter: Pure ctypes with:
c_int_p,c_float_p.so/.dylibon macOS and MinGW-style.dll.anaming on Windows)Fortran Extension Loader (OpenGGCM)
Before: f2py with automatic array handling and Fortran module imports
After: ctypes with SpacePy-style patterns:
int4,real4(matches Fortranreal= 4 bytes)FORTRAN_FUNCTIONS)func_,func__,func)Reader Modules (swmfgm_4D, gameragm_4D, swmf_gm_octree)
Updated to use ctypes instead of CFFI:
ffi.new('float[]', size)→numpy.zeros(size, dtype=np.float32)ffi.cast('float *', arr)→arr.ctypes.data_as(c_float_p)ffi.addressof()→ctypes.byref()CI/CD (
.github/workflows/)ci.yml- Tests on every push:otoolon macOS to verify rpathbuild-wheels.yml- Builds wheels on version tags:cibuildwheelfor consistent cross-platform buildsComparison with SpacePy
ctypes.CDLL()ctypes.CDLL()✓distutils.ccompilerdistutils.ccompiler✓os.path.abspath(self.build_lib)os.path.abspath(self.build_lib)✓dptr,lptr(C);int4,real8(Fortran)c_int_p,c_float_p(C);int4,real4(Fortran) ✓func_then plainfunc_,func__, plain ✓EXT_SUFFIX→SOEXT_SUFFIX→SO✓Key difference: SpacePy's Fortran functions have no string arguments, so all args can be auto-wrapped in
POINTER(). Kamodo's OpenGGCM has Fortran strings with hidden length arguments (gfortran ABI), so pointer types are explicit in the signature dict.Notable Behavioral Fix
This PR also includes a genuine bug fix in
kamodo_ccmc/readers/OpenGGCM/openggcm_gm_tocdf.py: removal of a spurious extra10argument inread_grid_for_vectorcalls (for theby/bz/ey/ezgrid paths). This corrected the function signature to match the Fortran subroutine interface.Files Changed
setup.pybuild_ext(distutils.ccompilerfor C, subprocessgfortran/FCfor Fortran)pyproject.tomlsetup.cfgMANIFEST.inREADME.mdQuickStart.mdkamodo_ccmc/__init__.pyPATHandos.add_dll_directory)kamodo_ccmc/libs/__init__.pykamodo_ccmc/readers/OCTREE_BLOCK_GRID/__init__.pykamodo_ccmc/readers/OCTREE_BLOCK_GRID/ctypes_wrappers.ckamodo_ccmc/readers/Tri2D/__init__.pykamodo_ccmc/readers/Tri2D/ctypes_wrappers.ckamodo_ccmc/readers/OpenGGCM/__init__.pykamodo_ccmc/readers/swmfgm_4D.pykamodo_ccmc/readers/gameragm_4D.pykamodo_ccmc/readers/swmf_gm_octree.pykamodo_ccmc/readers/OpenGGCM/openggcm_gm_tocdf.py.github/workflows/build-wheels.yml.github/workflows/ci.yml.gitignore*.dylib,*.dll,kamodo_ccmc/libs/*patternsdocs/PyPI-Release.mdDeleted:
kamodo_ccmc/readers/OCTREE_BLOCK_GRID/interpolate_amrdata_extension_build.py(old CFFI)kamodo_ccmc/readers/Tri2D/interpolate_tri2d_extension_build.py(old CFFI)How to Test
Local Build Test
Expected Outcome
After merging and releasing:
No compiler installation required for end users. The wheel contains pre-compiled shared libraries that work on:
References