在Meson中使用f2py编译Python扩展

Python Fortran
Article Directory
  1. 1. Meson 设置

This article was last updated on <span id="expire-date"></span> days ago, the information described in the article may be outdated.

最近想把自己修改过的一些 Fortran 代码放进自己的工具包 syize 中。因为一直用的是 meson 来打包 Python 包,所以研究了一下如何利用 meson 配合 f2py 来将 Fortran 代码编译成 Python 扩展。

f2py 的官网上有一个简单的示例,但是这个示例太过于简单了,导致根本不适用于我的情况。我的 Fortran 代码由两部分组成,一部分是很多个从 PALM 模式中摘出来的源码,这部分源码依赖于 netcdf-fortran 库;另一部分是我自己写的一个简单的 Fortran 文件来调用源码中的函数。由于某些未知的原因,直接都扔给 py.extension_module 的话会导致在编译 Fortran 文件时找不到 netcdf.mod 头文件。经过多方查找和咨询 ChatGPT,最终以以下流程进行设置并成功打包出了 Python 包。

graph TB
	A[PALM源码]
	B[Fortran wrapper]
	C((PALM lib))
	D((palm.pyf))
	E(module.c和wrappers.f90)
	F[Python extension]
	A -- 直接编译为静态库 --> C
	B -- f2py --> D
	B -- f2py --> E
	D -- f2py --> E
	C -- meson --> F
	E -- meson --> F

Meson 设置

这里只贴出有关扩展编译的部分。如果你想看全部内容,可以在我的仓库中查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# netcdf-fortran 依赖库
netcdf_dep = dependency('netcdf-fortran', required : true)

# include path,除了标准路径,还将源码路径添加了进来
fortran_include = include_directories('/usr/include', 'palm/src')

# 设置 PALM 源码文件列表
src_dir = meson.current_source_dir() / 'palm/src'
palm_sources = files(
join_paths(src_dir, 'kinds.f90'),
join_paths(src_dir, 'control_parameters.f90'),
join_paths(src_dir, 'indices.f90'),
join_paths(src_dir, 'pegrid.f90'),
join_paths(src_dir, 'arrays_3d.f90'),
join_paths(src_dir, 'basic_constants_and_equations_mod.f90'),
join_paths(src_dir, 'boundary_settings_mod.f90'),
join_paths(src_dir, 'exchange_horiz_mod.f90'),
join_paths(src_dir, 'general_utilities.f90'),
join_paths(src_dir, 'grid_variables.f90'),
join_paths(src_dir, 'nc_input.f90'),
join_paths(src_dir, 'topo.f90'),
)

# wrapper Fortran 代码文件
wrapper_source = files(join_paths(src_dir, 'palm-test.f90'))

# 先将 PALM 源码编译成静态库
palm_lib = static_library(
'palmcore',
palm_sources,
include_directories: fortran_include,
dependencies: netcdf_dep,
install: false
)

# 利用 f2py 生成 pyf 头文件
palm_pyf = custom_target(
'palm_pyf',
input: wrapper_source,
output: 'palm.pyf',
command: [
py3, '-m', 'numpy.f2py',
'@INPUT@',
'-m', 'palm',
'-h', meson.current_build_dir() / 'palm.pyf'
]
)

# 利用 f2py 和头文件生成两个 wrapper 文件
palm_f2py_wrapper = custom_target(
'palmcore',
input: [palm_pyf, wrapper_source],
output: ['palmmodule.c', 'palm-f2pywrappers2.f90'],
command: [
py3, '-m', 'numpy.f2py',
'@INPUT@',
'-m', 'palm',
'--lower', '--build-dir', meson.current_build_dir()
]
)

# 最终进行编译时需要的头文件和依赖
py_dep = py3.dependency()

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

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

include_numpy = include_directories(include_dir_numpy, include_dir_f2py)

# 编译 Python 扩展,并安装到指定路径下
py3.extension_module(
'palm',
[
wrapper_source,
palm_f2py_wrapper,
include_dir_f2py / 'fortranobject.c'
],
include_directories: include_numpy,
dependencies: py_dep,
link_with: palm_lib,
install: true,
install_dir: join_paths(py3.get_install_dir(), 'syize/fortran/palm')
)

Author: Syize

Permalink: https://blog.syize.cn/2026/03/09/f2py-with-meson/

本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Syizeのblog

Comments