diff --git a/projects/pi-fortran/CMakeLists.txt b/projects/pi-fortran/CMakeLists.txt index 70d6b01..3f49193 100644 --- a/projects/pi-fortran/CMakeLists.txt +++ b/projects/pi-fortran/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.17.2...3.26) +cmake_minimum_required(VERSION 3.17.2...3.29) project(pi VERSION 1.0.1 @@ -6,47 +6,22 @@ project(pi LANGUAGES C Fortran ) -find_package(Python REQUIRED COMPONENTS Interpreter Development.Module NumPy) +find_package( + Python + COMPONENTS Interpreter Development.Module NumPy + REQUIRED) -# F2PY headers -execute_process( - COMMAND "${Python_EXECUTABLE}" - -c "import numpy.f2py; print(numpy.f2py.get_include())" - OUTPUT_VARIABLE F2PY_INCLUDE_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE -) -# On Windows the path comes back with backslashes, which CMake reads as escapes. -file(TO_CMAKE_PATH "${F2PY_INCLUDE_DIR}" F2PY_INCLUDE_DIR) - -set(f2py_module_name "pi") -set(fortran_src_file "${CMAKE_SOURCE_DIR}/pi/_pi.f") -set(f2py_module_c "${f2py_module_name}module.c") - -add_custom_command( - OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_c}" - COMMAND ${PYTHON_EXECUTABLE} -m "numpy.f2py" - "${fortran_src_file}" - "${CMAKE_SOURCE_DIR}/pi/pi.pyf" # Must include custom .pyf file - -m "pi" - --lower # Important - DEPENDS pi/_pi.f # Fortran source -) - -python_add_library(${CMAKE_PROJECT_NAME} MODULE - "${f2py_module_name}module.c" - "${F2PY_INCLUDE_DIR}/fortranobject.c" - "${fortran_src_file}" WITH_SOABI) - -target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC - ${F2PY_INCLUDE_DIR} - ) +include(UseF2Py) -target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC Python::NumPy) +# Build the extension in one call: f2py generates the wrappers from the +# checked-in signature file, then it and _pi.f are compiled and linked together +# with fortranobject. The module name (pi) is taken from the .pyf stem. +f2py_add_module(pi/pi.pyf pi/_pi.f) # With the MinGW toolchain (Windows), statically link the GNU runtime so the # module has no external libgfortran/libgcc/libwinpthread DLL dependencies. if(WIN32 AND CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") - target_link_options(${CMAKE_PROJECT_NAME} PRIVATE -static) + target_link_options(pi PRIVATE -static) endif() -install(TARGETS ${CMAKE_PROJECT_NAME} DESTINATION pi) +install(TARGETS pi DESTINATION pi) diff --git a/projects/pi-fortran/README.md b/projects/pi-fortran/README.md index 72bb506..d2ca72e 100644 --- a/projects/pi-fortran/README.md +++ b/projects/pi-fortran/README.md @@ -19,7 +19,9 @@ pytest This is slightly modified from the example in the [numpy docs](https://numpy.org/devdocs/f2py/buildtools/skbuild.html), but we are using Monte Carlo to estimate the value of $\pi$. +The f2py glue is handled by [f2py-cmake](https://github.com/scikit-build/f2py-cmake): `include(UseF2Py)` and a single `f2py_add_module(pi/pi.pyf pi/_pi.f)` call generate the wrappers and build the extension, instead of wiring up `add_custom_command` and `fortranobject.c` by hand. + A few surprises: -1. The dreaded underscore problem has a way of cropping up. One solution is explicitly writing out the interface in a [signature (`.pyf`) file](https://numpy.org/devdocs/f2py/signature-file.html). +1. The dreaded underscore problem has a way of cropping up. One solution is explicitly writing out the interface in a [signature (`.pyf`) file](https://numpy.org/devdocs/f2py/signature-file.html), which we pass to `f2py_add_module`. 2. The module will require numpy to work. -3. Between failed builds, it is best to clear out the `_skbuild` folder. +3. Between failed builds, it is best to clear out the `build` folder. diff --git a/projects/pi-fortran/pyproject.toml b/projects/pi-fortran/pyproject.toml index c450f6a..6426f90 100644 --- a/projects/pi-fortran/pyproject.toml +++ b/projects/pi-fortran/pyproject.toml @@ -2,6 +2,7 @@ requires = [ "scikit-build-core", "numpy>=1.21", + "f2py-cmake>=0.2", ] build-backend = "scikit_build_core.build"