MesonでFortran 2018をビルドする ― f2py・Ninja・OpenMPまでつなぐ開発環境
はじめに
Fortranのプログラムが小さいうちは、gfortranを直接実行するだけで十分です。ただ、ソースが増えてくると事情が変わります。計算モジュールの依存関係を考えたり、DebugとReleaseでオプションを切り替えたり、テストをまとめて実行したりと、コンパイル以外の作業も増えてきます。
こうしたビルド作業を整理するのに便利なのが Meson です。
前回は、NumPyに含まれるf2pyを使って、PythonからFortran 2018の数値計算を呼び出す方法を紹介しました。今回はその続きとして、Mesonそのものの使い方から、Fortranのビルド、f2pyとの連携、OpenMPの設定まで整理します。
例はWindows 11 + WSL2 Ubuntu 24.04を想定しています。Dockerで同じ構成を動かす方法も最後に紹介します。
この記事の確認時点:2026年10月15日
NumPyは2.5.4を例にしています。NumPy 2.5以降では、f2pyの旧distutilsバックエンドが削除され、Mesonが使われます。
1. Mesonとは
Mesonは、C、C++、Fortranなどのソースコードをビルドするためのツールです。ソースファイル、コンパイラ、依存ライブラリ、テストなどの設定をmeson.buildにまとめて記述します。
Meson自身がFortranのコンパイラになるわけではありません。Mesonがビルド手順を組み立て、通常はNinjaがその手順を実行し、実際のコンパイルをgfortranなどが担当します。
| ツール | 担当する処理 |
|---|---|
| Meson | ビルド設定、ソース・依存関係の管理 |
| Ninja | ビルド手順の実行、必要なファイルの再コンパイル |
| gfortran | Fortranのコンパイルとリンク |
| f2py | FortranをPythonから呼び出すためのラッパー生成 |
| meson-python | PythonパッケージやWheelのビルド |
ここで覚えておきたいのは、Mesonとf2pyは別の役割を持つという点です。f2pyがインターフェースを作り、Mesonがビルド全体を管理します。
2. WSL2 Ubuntu 24.04にインストールする
まずはWSL2のUbuntuで必要なパッケージを準備します。
sudo apt update
sudo apt install -y \
python3 \
python3-venv \
python3-dev \
build-essential \
gfortran \
ninja-build \
pkg-config
開発用ディレクトリを作成し、Python仮想環境を有効にします。
mkdir -p ~/meson_fortran
cd ~/meson_fortran
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install meson numpy==2.5.4 pytest
確認します。
meson --version
ninja --version
gfortran --version
python -m numpy.f2py -v
UbuntuのaptからMesonをインストールする方法もありますが、上記ではプロジェクトのPython仮想環境にMesonを入れています。複数の開発環境でバージョンを揃えたい場合はこちらが扱いやすいです。
以降の操作は、特に断りのない限り.venvを有効にした状態で実行します。
3. 最初のmeson.buildを書く
まずはf2pyを使わず、FortranのプログラムをMesonでコンパイルします。
3.1 ディレクトリ構成
meson_fortran/
├── .venv/
├── meson.build
├── src/
│ └── add_vectors.f90
├── tests/
│ └── test_add.f90
└── build/ ← Mesonが生成する
3.2 Fortranの計算プログラム
2つの配列を足すだけの小さなサブルーチンです。
src/add_vectors.f90 を表示
subroutine add_vectors(n, a, b, c)
implicit none
! 配列の要素数
integer, intent(in) :: n
! 入力配列と出力配列
double precision, intent(in) :: a(n), b(n)
double precision, intent(out) :: c(n)
integer :: i
! 要素ごとの加算処理
do concurrent (i = 1:n)
c(i) = a(i) + b(i)
end do
end subroutine add_vectors
この例では、f2pyに公開しやすいよう、サブルーチンの入出力にdouble precisionを使っています。計算コアを大きくする場合にはreal(real64)などを使う設計もできますが、f2pyのKINDの解釈には別途注意が必要です。
3.3 テスト用のFortranプログラム
期待する配列と計算結果を比較するテストを用意します。
tests/test_add.f90 を表示
program test_add
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: n = 3
real(real64) :: a(n), b(n), c(n)
! 検証しやすい値を用意する
a = [1.0_real64, 2.0_real64, 3.0_real64]
b = [10.0_real64, 20.0_real64, 30.0_real64]
call add_vectors(n, a, b, c)
! 計算結果を許容誤差付きで比較する
if (any(abs(c - [ &
11.0_real64, 22.0_real64, 33.0_real64 &
]) > 1.0e-12_real64)) then
error stop 'add_vectors: NG'
end if
print *, 'PASS: add_vectors'
end program test_add
3.4 ビルド設定
プロジェクトのルートにmeson.buildを作成します。
project(
'fortran_demo',
'fortran',
version: '0.1.0',
default_options: [
'fortran_std=f2018',
'warning_level=2'
]
)
# Fortranの計算処理とテスト処理をリンクする
check_add = executable(
'check_add',
[
'src/add_vectors.f90',
'tests/test_add.f90'
]
)
# Mesonから実行できるテストとして登録する
test('vector-addition', check_add)
project()では使用する言語と基本設定を指定します。executable()は実行ファイルの作成、test()はテストの登録です。Fortran 2018への準拠を確認するため、fortran_std=f2018を指定しています。
4. Mesonの基本操作
最初にビルドディレクトリを作成します。
meson setup build
次にコンパイルします。
meson compile -C build
成功すると、buildディレクトリにcheck_addが生成されます。
./build/check_add
期待する出力は次のとおりです。
PASS: add_vectors
登録したテストをまとめて実行する場合は、次のコマンドを使います。
meson test -C build --print-errorlogs
meson.buildやソースコードを変更した後も、基本的にはmeson compile -C buildを実行すれば必要な部分が再ビルドされます。
よく使うコマンドをまとめておきます。
| コマンド | 内容 |
|---|---|
meson setup build |
初回のビルド設定 |
meson compile -C build |
コンパイル |
meson test -C build |
テスト実行 |
meson configure build |
現在の設定を確認 |
meson setup --reconfigure build |
設定の再構成 |
meson setup --wipe build |
ビルドディレクトリの設定を初期化して再構成 |
meson install -C build |
インストール対象を配置 |
meson installを利用するには、インストールするターゲットや配置先をmeson.buildで定義しておく必要があります。今回の単純なテストプログラムではインストールを設定していません。
5. DebugとReleaseを切り替える
数値計算プログラムでは、デバッグ用と実行速度を重視するビルドを分けておくと便利です。Mesonはソースとビルドディレクトリを分離しているため、同じソースに対して複数のビルド設定を持てます。
| ビルドタイプ | 最適化の目安 | 主な用途 |
|---|---|---|
debug |
最適化なし | 不具合の調査 |
debugoptimized |
-O2相当 |
デバッグ情報を残した性能確認 |
release |
-O3相当 |
本番計算 |
Debugビルド
meson setup build-debug \
--buildtype=debug \
-Dfortran_args=-fcheck=all
meson compile -C build-debug
meson test -C build-debug --print-errorlogs
-fcheck=allはgfortranの実行時チェックを有効にします。配列の範囲外参照などを調査するときに役立ちます。
Releaseビルド
meson setup build-release --buildtype=release
meson compile -C build-release
meson test -C build-release --print-errorlogs
CPU固有の命令を使う場合は、以下のように指定できます。
meson setup build-native \
--buildtype=release \
-Dfortran_args=-march=native
meson compile -C build-native
ただし、-march=nativeを付けたバイナリは別のCPUで動作しない場合があります。配布用ビルドでは移植性を優先します。また、水収支などの検証が必要な計算で-ffast-mathを無条件に有効にするのは避けた方がよいです。
6. f2pyからMesonを使う
ここからPythonとの連携に進みます。
f2pyを使うだけなら、meson.buildを自分で書く必要はありません。NumPy 2.5系では次のコマンドだけでMesonを使って拡張モジュールをビルドできます。
python -m numpy.f2py \
-c \
-m fastcalc \
src/add_vectors.f90
正常に完了すると、Linuxではfastcalc.cpython-312-...soのような共有ライブラリが生成されます。ファイル名は使用するPythonのバージョンや実行環境によって変わります。
生成したモジュールがあるディレクトリで、Pythonから呼び出します。
import numpy as np
import fastcalc
a = np.array([1.0, 2.0, 3.0], dtype=np.float64)
b = np.array([10.0, 20.0, 30.0], dtype=np.float64)
result = fastcalc.add_vectors(a, b)
print(result)
期待する出力は次のとおりです。
[11. 22. 33.]
f2pyが内部で生成するMesonの設定を確認したければ、--build-dirを指定します。
python -m numpy.f2py \
-c \
-m fastcalc \
src/add_vectors.f90 \
--build-dir f2py_generated
この方法は、まずf2pyを動かしてみたい場合に適しています。小規模な計算モジュールなら、これだけでも十分です。
7. meson.buildでf2pyのラッパー生成まで管理する
Fortranソースが増えたり、Pythonへの公開APIを継続して保守したりする場合は、ラッパー生成もMesonに組み込む方法があります。
以下は前節とは別の新しいプロジェクトとして作成します。
f2py_meson/
├── .venv/
├── meson.build
├── src/
│ └── add_vectors.f90
└── tests/
└── test_fastcalc.py
src/add_vectors.f90には前のサブルーチンをコピーします。新しいプロジェクトでPython仮想環境を使用する場合も、NumPyとMesonのインストールが必要です。
7.1 meson.buildを作る
この方法では、Mesonのcustom_target()でf2pyを起動し、py.extension_module()で共有ライブラリを生成します。
meson.build(f2py連携用)を表示
project(
'f2py_meson',
['c', 'fortran'],
version: '0.1.0',
meson_version: '>=1.1.0',
default_options: [
'buildtype=release',
'fortran_std=f2018'
]
)
# Pythonの拡張モジュールをビルドするための機能
py = import('python').find_installation(pure: false)
# NumPyのCヘッダーを取得する
np_dep = dependency('numpy')
# f2py付属のfortranobject.c / fortranobject.hを探す
f2py_inc = run_command(
py,
[
'-c',
'import numpy.f2py; print(numpy.f2py.get_include())'
],
check: true
).stdout().strip()
f2py_dep = declare_dependency(
include_directories: f2py_inc
)
# Fortranソースからf2pyのCラッパーを生成する
wrapper = custom_target(
'fastcalc_wrapper',
input: 'src/add_vectors.f90',
output: [
'fastcalcmodule.c',
'fastcalc-f2pywrappers.f'
],
command: [
py,
'-m', 'numpy.f2py',
'@INPUT@',
'-m', 'fastcalc',
'--lower'
]
)
# Fortran本体とラッパーを一緒にビルドする
py.extension_module(
'fastcalc',
[
'src/add_vectors.f90',
wrapper,
f2py_inc / 'fortranobject.c'
],
dependencies: [
np_dep,
f2py_dep
],
install: false
)
custom_target()は、ソースから別のソースを生成するときに便利な機能です。ここではFortranファイルをf2pyに渡して、Cラッパーなどを作っています。
今回の例はモジュール外に定義した単純なサブルーチンを対象にしています。Fortranのmoduleや複雑なシグネチャを扱う場合には、追加の.f90ラッパーが生成される場合があるため、outputの定義も合わせて確認してください。
7.2 ビルドする
meson setup build
meson compile -C build
ビルドに成功すると、buildディレクトリ以下にfastcalcの拡張モジュールが生成されます。
7.3 Pythonから動作確認する
テストプログラムを作成します。
tests/test_fastcalc.py を表示
"""f2pyで公開した配列加算処理の動作確認。"""
import numpy as np
import fastcalc
def test_add_vectors() -> None:
"""Fortranの加算結果が期待値と一致することを確認する。"""
a = np.array([1.0, 2.0, 3.0], dtype=np.float64)
b = np.array([10.0, 20.0, 30.0], dtype=np.float64)
# 配列長nはf2pyが入力配列から求める
actual = fastcalc.add_vectors(a, b)
expected = np.array([11.0, 22.0, 33.0])
np.testing.assert_allclose(actual, expected, rtol=0.0, atol=1.0e-12)
Linux上では、拡張モジュールのあるディレクトリをPYTHONPATHに加えてテストします。
PYTHONPATH=build python -m pytest -q tests/test_fastcalc.py
テスト時にModuleNotFoundErrorが出る場合は、buildディレクトリ内の.soファイルの場所と、実行しているPythonのバージョンを確認してください。
なお、Mesonとf2pyを自分で連携させる方法は、公式ドキュメントにある構成を参考にしています。複数モジュールへの拡張や追加ライブラリのリンクは、扱うFortranコードに合わせて調整が必要です。
8. OpenMPを有効にする
MesonではOpenMPの依存関係を次のように指定できます。
openmp_dep = dependency('openmp', required: true)
Fortranの実行ファイルなら、次のように設定します。
solver = executable(
'solver',
'src/solver.f90',
dependencies: openmp_dep
)
f2pyの拡張モジュールに組み込む場合には、py.extension_module()のdependenciesにopenmp_depを追加します。
dependencies: [
np_dep,
f2py_dep,
openmp_dep
]
OpenMPのコンパイルとリンクに必要なオプションを、コンパイラに応じてMesonが扱います。もちろん、これだけで計算が自動的に並列化されるわけではありません。Fortran側にOpenMPの指示行が必要です。
!$omp parallel do private(i) schedule(static)
do i = 1, n
c(i) = a(i) + b(i)
end do
!$omp end parallel do
また、do concurrentとOpenMPは別の機能です。どちらを使う場合でも、実際の並列化状況や結果の再現性を確認します。
9. Dockerでビルド環境を揃える
開発用PCとCI環境でコンパイラやPythonパッケージのバージョンが異なると、同じソースでも結果が変わったり、ビルドに失敗したりすることがあります。そのため、MesonとDockerの組み合わせも便利です。
以下は第3章のFortran単独ビルド用プロジェクトをコンテナ内でテストする例です。
Dockerfile を表示
FROM python:3.12-slim
# FortranとMeson/Ninjaのビルドに必要なツール
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
gfortran \
ninja-build \
pkg-config && \
rm -rf /var/lib/apt/lists/*
# f2pyにも対応できるようにNumPyを導入
RUN python -m pip install --no-cache-dir \
meson \
numpy==2.5.4 \
pytest
WORKDIR /work
COPY meson.build ./
COPY src/ ./src/
COPY tests/ ./tests/
# ビルドとテストをイメージ作成時に確認する
RUN meson setup build --buildtype=debug && \
meson compile -C build && \
meson test -C build --print-errorlogs
CMD ["/bin/sh"]
同じ階層にDockerfileを保存した後、次のように実行します。
docker build -t meson-fortran-demo .
docker run --rm -it meson-fortran-demo
この例は、ローカル側で作成したmeson.build、src、testsをコンテナへコピーする構成です。開発中に毎回イメージを作り直したくない場合は、Docker Composeでソースをマウントする構成へ拡張できます。
10. meson-pythonとの違い
Mesonと名前が似ていますが、meson-pythonは用途が少し異なります。
- Meson:CやFortranなどをコンパイルするビルドシステム
-
meson-python:Mesonで作るPython拡張を、
pip installやWheelの仕組みに接続するビルドバックエンド
例えば、f2pyの拡張モジュールをPythonパッケージとして配布する場合は、pyproject.tomlを用意します。
[build-system]
requires = [
"meson-python>=0.15.0",
"numpy>=2.0"
]
build-backend = "mesonpy"
[project]
name = "hydraulic-core"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["numpy>=2.0"]
ただし、これを記述するだけでWheelが完成するわけではありません。meson.build側でも拡張モジュールのインストール先(py.extension_module(..., install: true, ...)など)を設定する必要があります。
パッケージ構成まで用意したら、次のようにWheelを作成できます。
python -m pip install build meson-python
python -m build --wheel
プログラムを手元で実行するだけなら、ここまでの仕組みは必須ではありません。複数のPCへ配布したい場合や、Pythonパッケージとして管理したい場合に検討するとよいです。
11. よくあるエラー
| 状況 | 確認するところ |
|---|---|
meson: command not found |
仮想環境の有効化とMesonのインストール |
Unknown compiler(s) |
gfortran --versionやgcc --version
|
ninja: command not found |
ninja-buildのインストール |
| NumPyが見つからない | Mesonが使うPythonと仮想環境が一致しているか |
ModuleNotFoundError: fastcalc |
.soの場所、Pythonのバージョン、PYTHONPATH
|
undefined symbol |
Fortran本体・ラッパー・外部ライブラリのリンク漏れ |
| ラッパー生成ファイルが不足 | f2pyの出力名とcustom_target()のoutput
|
| OpenMPでリンクエラー |
dependency('openmp')の設定とコンパイラの対応 |
特に、f2pyとMesonを組み合わせる際は、Pythonの仮想環境が混在していないかを最初に確認した方がよいです。
which python
which meson
python -c "import numpy; print(numpy.__version__)"
python -c "import numpy; print(numpy.get_include())"
python -c "import numpy.f2py; print(numpy.f2py.get_include())"
12. 実際の数値計算ソルバーに取り入れるなら
河川の1次元流れ計算や2次元氾濫計算など、ある程度大きなFortranプログラムでは、数値計算とPythonとの接続部分を分けておくと管理しやすくなります。
hydraulic_solver/
├── meson.build
├── pyproject.toml
├── src/
│ ├── continuity.f90
│ ├── momentum.f90
│ ├── boundary.f90
│ ├── coupling.f90
│ └── hydraulic_api.f90
├── python/
│ └── hydraulic/
│ └── __init__.py
└── tests/
├── test_mass_balance.py
├── test_boundary.py
└── test_solver.py
Fortran側は計算コアとして独立させ、Pythonから呼び出すためのhydraulic_api.f90を別に用意します。この境界部分をf2pyで公開する形です。
こうしておけば、数値計算の内部実装を変更しても、Python側への影響を抑えやすくなります。また、水収支や境界条件のテストを自動実行すれば、変更による計算結果の差も追跡しやすくなります。
個人的には、最初から複雑なビルド設定にする必要はないと思います。まずはMesonでFortranを単独実行し、次にf2pyでPythonから呼び出し、必要になったところでOpenMPやWheel配布を追加していくのが分かりやすい進め方です。
13. 動作確認について
掲載した配列加算のFortranソースとテストプログラムは、gfortran 14.2.0で-std=f2018 -Wall -Wextra -fcheck=allを指定してコンパイルし、PASS: add_vectorsを確認しました。また、NumPy 2.3.5に含まれるf2pyで、同じFortranソースからCラッパー(fastcalcmodule.c)を生成できることを確認しています。
一方、この記事の作成環境にはMesonがインストールされておらず、ネットワーク制限のため追加導入もできませんでした。そのため、Mesonを通した最終ビルド、f2py拡張モジュールの実行、Dockerビルドは未検証です。Mesonの設定は公式資料を基に記述していますが、実行環境に合わせて確認してください。掲載した結果をMesonの実行結果として扱わないようにしています。
参考資料
- Meson公式:Quick guide
- Meson公式:Built-in options
- Meson公式:Unit tests
- NumPy公式:F2PYとビルドシステム
- NumPy公式:Using via Meson
- NumPy公式:Migrating to Meson
- NumPy公式:Distributing F2PY extensions with meson-python
