我有一个带有 Fortran 90 扩展的 Python 包。最初,我使用 distutils 和 f2py 来构建扩展,但由于它已被弃用,我改用 scikit-build 与 cmake 和 f2py 结合使用。这是有效的,但最近对某些用户来说开始失败(我怀疑是由于某些安装工具的弃用)。我正在尝试切换到
scikit-build-core
(参见此处),这样可以完全避免使用 setuptools,但我现在看到一个我不太理解的错误。
我已经根据 scikit-build-core 文档上的示例编写了 CMakeLists.txt:
cmake_minimum_required(VERSION 3.17.2...3.29)
project(${SKBUILD_PROJECT_NAME} LANGUAGES C Fortran)
find_package(
Python
COMPONENTS Interpreter Development.Module NumPy
REQUIRED)
set(CMAKE_VERBOSE_MAKEFILE ON)
# 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)
add_library(fortranobject OBJECT "${F2PY_INCLUDE_DIR}/fortranobject.c")
target_link_libraries(fortranobject PUBLIC Python::NumPy)
target_include_directories(fortranobject PUBLIC "${F2PY_INCLUDE_DIR}")
set_property(TARGET fortranobject PROPERTY POSITION_INDEPENDENT_CODE ON)
set(f2py_module_name "calc11")
set(fortran_src_file "${CMAKE_BINARY_DIR}/calc11/src/calc11.f90")
set(generated_module_file ${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_name}${PYTHON_EXTENSION_MODULE_SUFFIX})
# Generate param11.in file in cmake build directory
file(READ ${CMAKE_SOURCE_DIR}/calc11/src/param11.i.in CONTENTS)
file(WRITE ${CMAKE_SOURCE_DIR}/calc11/src/param11.i ${CONTENTS})
# This is the path with all of the fortran code, which gets concatenated into a single file "calc11.f90"
add_subdirectory(calc11)
# Include all the include files
file(GLOB includes ${CMAKE_SOURCE_DIR}"/calc11/src/*.i")
list(PREPEND includes ${generated_module_file})
# Run f2py to generate extension
add_custom_command(
OUTPUT ${generated_module_file}
DEPENDS ${includes}
VERBATIM
COMMAND "${Python_EXECUTABLE}" -m numpy.f2py
"${fortran_src_file}" -m ${f2py_module_name} --lower)
python_add_library(${f2py_module_name} MODULE "${fortran_src_file}" WITH_SOABI)
target_link_libraries(${f2py_module_name} PRIVATE fortranobject)
install(TARGETS ${f2py_module_name} DESTINATION ${SKBUILD_PROJECT_NAME})
我的理解是 f2py 需要
fortranobject
库才能正确构建内容,但它之所以存在只是因为它在示例代码中。
当我编译扩展时,我从链接器中收到此错误:
[5/5] Linking Fortran shared module calc11.cpython-311-x86_64-linux-gnu.so
FAILED: calc11.cpython-311-x86_64-linux-gnu.so
: && /usr/bin/gfortran -fPIC -O3 -DNDEBUG -O3 -shared -o calc11.cpython-311-x86_64-linux-gnu.so CMakeFiles/fortranobject.dir/tmp/pip-build-env-ebv97u47/overlay/lib/python3.11/site-packages/numpy/f2py/src/fortranobject.c.o CMakeFiles/calc11.dir/calc11/src/calc11.f90.o && :
/usr/bin/ld: CMakeFiles/fortranobject.dir/tmp/pip-build-env-ebv97u47/overlay/lib/python3.11/site-packages/numpy/f2py/src/fortranobject.c.o: in function `get_elsize':
fortranobject.c:(.text+0x240): undefined reference to `_npy_f2py_ARRAY_API'
/usr/bin/ld: CMakeFiles/fortranobject.dir/tmp/pip-build-env-ebv97u47/overlay/lib/python3.11/site-packages/numpy/f2py/src/fortranobject.c.o: relocation R_X86_64_PC32 against undefined hidden symbol `_npy_f2py_ARRAY_API' can not be used when making a shared object
/usr/bin/ld: final link failed: bad value
即,fortranobject 库中没有“_npy_f2py_ARRAY_API”。
我之前能够使用旧版本的 numpy (1.23) 来编译扩展模块,但是当我导入它时,它仍然给我错误,即 _npy_f2py_ARRAY_API 未定义。
问题似乎来自
target_link_libraries
命令,因为如果我忽略它,它就会构建。但是,在这种情况下编译的模块会出错(显然需要链接到 fortranobject)。
我能够解决这个问题。问题似乎是原来的
scikit-build
和 distutils
本身做了很多工作来包含必要的 f2py 库,而我没有包含正确的。
对 f2py 的调用以及添加库并链接它的后续代码应该是这样的:
add_custom_command(
OUTPUT calc11module.c calc11-f2pywrappers.f calc11-f2pywrappers2.f90
DEPENDS ${includes}
VERBATIM
COMMAND "${Python_EXECUTABLE}" -m numpy.f2py
"${fortran_src_file}" -m ${f2py_module_name} --lower
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
python_add_library(
${f2py_module_name}
MODULE
"${CMAKE_CURRENT_BINARY_DIR}/calc11-f2pywrappers2.f90"
"${CMAKE_CURRENT_BINARY_DIR}/calc11-f2pywrappers.f"
"${CMAKE_CURRENT_BINARY_DIR}/calc11module.c"
"${fortran_src_file}"
WITH_SOABI)
target_link_libraries(${f2py_module_name} PRIVATE fortranobject)
“f2pywrapper”文件为Python函数提供必要的API。此链接正确,现在可以运行。