通过 meson 使用#

注意

本文档的大部分内容现已过时,可以通过 `f2py` 结合 `--build-dir` 运行来获得一个骨架 `meson` 项目,并设置好基本依赖。

版本 1.26.x 中有更改: `f2py` 的默认构建系统现在是 `meson`,请参阅 `numpy.distutils` 的状态和迁移建议 以了解更多详细信息。

相较于 通过 `numpy.distutils` 使用 中描述的技术,利用 `meson` 的主要优势在于它可以轻松地融入现有系统和大型项目。`meson` 具有相当 Python 风格的语法,这使得它对 `python` 用户来说更舒适且更易于扩展。

斐波那契演练 (F77)#

在我们能够使用像 `meson` 这样的通用构建系统之前,我们需要生成 `C` 包装器。我们将通过以下方式获得它:

python -m numpy.f2py fib1.f -m fib2

现在,考虑以下 `meson.build` 文件,用于 三种包装方式 - 入门 小节中的 `fib` 和 `scalar` 示例。

project('f2py_examples', 'c',
  version : '0.1',
  license: 'BSD-3',
  meson_version: '>=0.64.0',
  default_options : ['warning_level=2'],
)

add_languages('fortran')

py_mod = import('python')
py = py_mod.find_installation(pure: false)
py_dep = py.dependency()

incdir_numpy = run_command(py,
  ['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'],
  check : true
).stdout().strip()

incdir_f2py = run_command(py,
    ['-c', 'import os; os.chdir(".."); import numpy.f2py; print(numpy.f2py.get_include())'],
    check : true
).stdout().strip()

inc_np = include_directories(incdir_numpy, incdir_f2py)

py.extension_module('fib2',
  [
    'fib1.f',
    'fib2module.c',  # note: this assumes f2py was manually run before!
  ],
  incdir_f2py / 'fortranobject.c',
  include_directories: inc_np,
  dependencies : py_dep,
  install : true
)

此时构建将完成,但导入将失败。

meson setup builddir
meson compile -C builddir
cd builddir
python -c 'import fib2'
Traceback (most recent call last):
File "<string>", line 1, in <module>
ImportError: fib2.cpython-39-x86_64-linux-gnu.so: undefined symbol: FIB_
# Check this isn't a false positive
nm -A fib2.cpython-39-x86_64-linux-gnu.so | grep FIB_
fib2.cpython-39-x86_64-linux-gnu.so: U FIB_

请记住,如下面所示的原始示例是采用 SCREAMCASE 命名的。

C FILE: FIB1.F
      SUBROUTINE FIB(A,N)
C
C     CALCULATE FIRST N FIBONACCI NUMBERS
C
      INTEGER N
      REAL*8 A(N)
      DO I=1,N
         IF (I.EQ.1) THEN
            A(I) = 0.0D0
         ELSEIF (I.EQ.2) THEN
            A(I) = 1.0D0
         ELSE 
            A(I) = A(I-1) + A(I-2)
         ENDIF
      ENDDO
      END
C END FILE FIB1.F

采用标准方法,暴露给 `python` 的子程序是 `fib` 而不是 `FIB`。这意味着我们有几种选择。一种方法(如果可能的话)是将原始 Fortran 文件转换为小写,例如:

tr "[:upper:]" "[:lower:]" < fib1.f > fib1.f
python -m numpy.f2py fib1.f -m fib2
meson --wipe builddir
meson compile -C builddir
cd builddir
python -c 'import fib2'

然而,这需要修改源代码的能力,而这并非总是可能的。解决此问题的最简单方法是让 `f2py` 处理它。

python -m numpy.f2py fib1.f -m fib2 --lower
meson --wipe builddir
meson compile -C builddir
cd builddir
python -c 'import fib2'

自动化包装器生成#

上述工作流程中的一个主要痛点是手动跟踪输入。尽管出于 F2PY 和构建系统 中讨论的原因,确定实际输出需要付出更多努力。

注意

从 NumPy `1.22.4` 起,`f2py` 将根据输入文件的 Fortran 标准(F77 或更高版本)确定性地生成包装器文件。可以将 `--skip-empty-wrappers` 传递给 `f2py`,以恢复之前仅在输入需要时才生成包装器的行为。

然而,我们可以通过一种直接的方式来增强我们的工作流程,以考虑在构建系统设置时已知输出的文件。

project('f2py_examples', 'c',
  version : '0.1',
  license: 'BSD-3',
  meson_version: '>=0.64.0',
  default_options : ['warning_level=2'],
)

add_languages('fortran')

py_mod = import('python')
py = py_mod.find_installation(pure: false)
py_dep = py.dependency()

incdir_numpy = run_command(py,
  ['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'],
  check : true
).stdout().strip()

incdir_f2py = run_command(py,
    ['-c', 'import os; os.chdir(".."); import numpy.f2py; print(numpy.f2py.get_include())'],
    check : true
).stdout().strip()

fibby_source = custom_target('fibbymodule.c',
  input : ['fib1.f'],  # .f so no F90 wrappers
  output : ['fibbymodule.c', 'fibby-f2pywrappers.f'],
  command : [py, '-m', 'numpy.f2py', '@INPUT@', '-m', 'fibby', '--lower']
)

inc_np = include_directories(incdir_numpy, incdir_f2py)

py.extension_module('fibby',
  ['fib1.f', fibby_source],
  incdir_f2py / 'fortranobject.c',
  include_directories: inc_np,
  dependencies : py_dep,
  install : true
)

这可以像以前一样编译和运行。

rm -rf builddir
meson setup builddir
meson compile -C builddir
cd builddir
python -c "import numpy as np; import fibby; a = np.zeros(9); fibby.fib(a); print (a)"
# [ 0.  1.  1.  2.  3.  5.  8. 13. 21.]

要点#

值得记住以下几点:

  • 在这种情况下无法使用 SCREAMCASE,因此 `.f` 文件内容或生成的包装器 `.c` 需要转换为常规字母(小写);这可以通过 `F2PY` 的 `--lower` 选项来实现。