ビルドツール#
ソースファイルの数とモジュール間の相互依存関係によっては、Fortran プロジェクトを手動でコンパイルすることは非常に複雑になる可能性があります。適切なツールを使用してこれらのタスクを自動的に実行しない限り、異なるコンパイラやリンカ、または異なるプラットフォームのサポートはますます複雑になります。
プロジェクトの規模と目的によっては、ビルド自動化の選択肢が異なります。
まず、統合開発環境はプログラムをビルドする方法を提供している可能性があります。人気のクロスプラットフォームツールには、Microsoft の Visual Studio Code がありますが、Atom、Eclipse Photran、Code::Blocks など、他にもあります。これらはグラフィカルユーザーインターフェースを提供しますが、コンパイラとプラットフォームに非常に固有であることがよくあります。
小規模なプロジェクトでは、ルールベースのビルドシステムである make
が一般的な選択肢です。定義されたルールに基づいて、更新されたソースファイルからのオブジェクトファイルの(再)コンパイル、ライブラリの作成、実行ファイルのリンクなどのタスクを実行できます。make
をプロジェクトで使用するには、最終プログラム、中間オブジェクトファイルまたはライブラリ、および実際のソースファイルの相互依存関係を定義する Makefile
にこれらのルールをエンコードする必要があります。簡単な紹介については、make
に関するガイド を参照してください。
autotools や CMake などのメンテナンスツールは、高レベルの説明を介して Makefile や Visual Studio プロジェクトファイルを生成できます。これらはコンパイラやプラットフォームの仕様から抽象化されています。
これらのツールのどれがプロジェクトに最適な選択であるかは、多くの要因によって異なります。使い慣れたビルドツールを選択してください。開発中に邪魔になるべきではありません。ビルドツールに対処するのに実際の開発作業よりも多くの時間を費やすことは、すぐにイライラする可能性があります。
また、ビルドツールのアクセシビリティを考慮してください。特定の統合開発環境に制限されている場合、プロジェクトのすべての開発者がアクセスできますか?特定のビルドシステムを使用している場合、開発対象のすべてのプラットフォームで機能しますか?ビルドツールの参入障壁はどの程度ですか?ビルドツールの学習曲線を考慮してください。完璧なビルドツールでも、新しいソースファイルを追加するためにまず複雑なプログラミング言語を学習する必要がある場合は役に立ちません。最後に、他のプロジェクトが使用しているもの、依存しているもの、プロジェクトを依存関係として使用している(または使用する予定の)ものを考慮してください。
make をビルドツールとして使用#
最もよく知られており、一般的に使用されているビルドシステムは make
と呼ばれています。これは、通常、提供されたソースコードからプログラムをコンパイルすることにつながる Makefile
または makefile
と呼ばれる設定ファイルに定義されているルールに従ってアクションを実行します。
ヒント
詳細な make
チュートリアルについては、その情報ページを参照してください。この 情報ページ のオンラインバージョンがあります。
クリーンなソースディレクトリから始めます。Makefile
ファイルを作成して開き、all と呼ばれる単純なルールから始めます。
all:
echo "$@"
Makefile
を保存したら、同じディレクトリで make
を実行します。次の出力が表示されます。
echo "all"
all
まず、make
がルールの名前である $@
を置換していることに注意してください。次に、make
は常に実行しているコマンドを出力していることに注意してください。最後に、echo "all"
を実行した結果が表示されます。
注意
慣例により、Makefile
のエントリポイントは常に all と呼びますが、任意の名前を選択できます。
注意
エディタが正しく動作している場合は気付かないかもしれませんが、ルールの内容はタブ文字でインデントする必要があります。上記の Makefile
の実行に問題があり、次のようなエラーが表示された場合は
Makefile:2: *** missing separator. Stop.
インデントが正しくない可能性があります。この場合は、2 行目のインデントをタブ文字に置き換えます。
今度はルールをより複雑にするために、別のルールを追加します。
PROG := my_prog
all: $(PROG)
echo "$@ depends on $^"
$(PROG):
echo "$@"
make
で変数を宣言する方法に注意してください。ローカル変数は常に :=
で宣言する必要があります。変数の内容にアクセスするには $(...)
を使用します。変数名を括弧で囲む必要があることに注意してください。
注意
変数の宣言は通常 :=
を使用して行われますが、make
は =
を使用して再帰的に展開される変数もサポートしています。通常、最初の種類の宣言の方が予測可能性が高く、再帰的な展開によるランタイムオーバーヘッドがないため、望ましいものです。
ルール all の依存関係、つまり変数 PROG
の内容を導入しました。また、出力も変更しました。このルールのすべての依存関係を表示したいと考えています。それらは変数 $^
に格納されています。変数 PROG
の値にちなんで名付けた新しいルールでは、ルール all で行ったことと同じことを行います。$@
の値は使用されているルールによって異なることに注意してください。
再び make
を実行して確認します。次のようになります。
echo "my_prog"
my_prog
echo "all depends on my_prog"
all depends on my_prog
ルール all でアクションを実行する前に、依存関係が正しく解決および評価されています。2 番目のルールだけを実行してみましょう。make my_prog
と入力すると、ターミナルに最初の 2 行しか表示されません。
make
で実際のアクションを実行する次の手順では、前の章のソースコードをここで使用し、Makefile
に新しいルールを追加します。
OBJS := tabulate.o functions.o
PROG := my_prog
all: $(PROG)
$(PROG): $(OBJS)
gfortran -o $@ $^
$(OBJS): %.o: %.f90
gfortran -c -o $@ $<
オブジェクトファイルを表すOBJS
を定義します。私たちのプログラムはこれらのOBJS
に依存しており、各オブジェクトファイルに対して、ソースファイルからそれらを作成するためのルールを作成します。最後に導入したルールはパターンマッチングルールで、tabulate.o
とtabulate.f90
の間の共通パターンである%
は、オブジェクトファイルtabulate.o
とソースファイルtabulate.f90
を結び付けています。この設定で、コンパイラ(ここではgfortran
)を実行し、ソースファイルをオブジェクトファイルに変換します。-c
フラグのため、まだ実行ファイルは作成しません。依存関係の最初の要素に$<
を使用することに注意してください。
すべてのオブジェクトファイルをコンパイルした後、プログラムをリンクしようとします。リンカを直接使用せず、実行ファイルを作成するためにgfortran
を使用します。
これで、make
を使用してビルドスクリプトを実行します。
gfortran -c -o tabulate.o tabulate.f90
tabulate.f90:2:7:
2 | use user_functions
| 1
Fatal Error: Cannot open module file ‘user_functions.mod’ for reading at (1): No such file or directory
compilation terminated.
make: *** [Makefile:10: tabulate.f90.o] Error 1
ソースファイル間に依存関係があることを思い出してください。そのため、この依存関係をMakefile
に明示的に追加します。
tabulate.o: functions.o
これで再試行すると、ビルドが正しく機能していることがわかります。出力は次のようになります。
gfortran -c -o functions.o functions.f90
gfortran -c -o tabulate.o tabulate.f90
gfortran -o my_prog tabulate.o functions.o
ディレクトリに4つの新しいファイルが作成されているはずです。my_prog
を実行して、すべてが期待通りに動作することを確認してください。make
をもう一度実行してみましょう。
make: Nothing to be done for 'all'.
実行ファイルmake
のタイムスタンプを使用して、tabulate.o
とfunctions.o
の両方よりも新しく、それらはtabulate.f90
とfunctions.f90
よりも新しいことが判明しました。したがって、プログラムは最新のコードと既に最新の状態であり、何のアクションも実行する必要はありません。
最後に、完全なMakefile
を見てみましょう。
# Disable all of make's built-in rules (similar to Fortran's implicit none)
MAKEFLAGS += --no-builtin-rules --no-builtin-variables
# configuration
FC := gfortran
LD := $(FC)
RM := rm -f
# list of all source files
SRCS := tabulate.f90 functions.f90
PROG := my_prog
OBJS := $(addsuffix .o, $(SRCS))
.PHONY: all clean
all: $(PROG)
$(PROG): $(OBJS)
$(LD) -o $@ $^
$(OBJS): %.o: %
$(FC) -c -o $@ $<
# define dependencies between object files
tabulate.f90.o: functions.f90.o user_functions.mod
# rebuild all object files in case this Makefile changes
$(OBJS): $(MAKEFILE_LIST)
clean:
$(RM) $(filter %.o, $(OBJS)) $(wildcard *.mod) $(PROG)
make
を使い始めるのであれば、Fortranのimplicit none
のように、最初の行を必ず含めることを強くお勧めします。暗黙のルールがMakefile
を予期せず有害な方法で混乱させるのを避けたいからです。
次に、変数を定義する構成セクションがあります。コンパイラを切り替えたい場合は、ここで簡単に実行できます。また、すべてのソースファイルを含むSRCS
変数を導入しました。これは、オブジェクトファイルを指定するよりも直感的です。addsuffix
関数を使用して.o
サフィックスを追加することで、オブジェクトファイルを簡単に作成できます。.PHONY
は特別なルールで、Makefile
のすべてのエントリポイントに使用すべきです。ここでは、既に知っているallと、新しいcleanルールを定義します。cleanルールは、クリーンなディレクトリから開始できるように、すべてのビルド成果物を削除します。
また、.o
サフィックスを置換する代わりに追加することに対応するために、オブジェクトファイルのビルドルールをわずかに変更しました。コンパイラを変更した場合に安全に再構築できるように、Makefile
自体に対するオブジェクトファイルの依存関係も追加しました。
これで、小さなプロジェクトのビルドにmake
を使用するのに十分な知識が得られました。make
をより広範囲に使用することを計画している場合は、いくつかのヒントもまとめています。
ヒント
このガイドでは、正しく使用されない場合に特に問題となる可能性のある、一般的に使用されるmake
機能の多くを回避し、無効にしました。make
での作業に自信がない場合は、組み込みのルールと変数を避けて、すべての変数とルールを明示的に宣言することを強くお勧めします。
make
は、短く相互依存するワークフローを自動化し、小さなプロジェクトを構築するための強力なツールです。しかし、大規模なプロジェクトでは、すぐにいくつかの制限に遭遇する可能性があります。そのため、通常make
は単独で使用されるのではなく、他のツールと組み合わせてMakefile
全体または一部を生成します。
再帰的に展開される変数#
多くのプロジェクトでよく見られるのが、再帰的に展開される変数です(:=
ではなく=
で宣言)。変数の再帰的な展開により、実行時に展開されるルールとして定義されるため、解析時に定義されるのではなく、make
で順番の異なる宣言やその他の便利なテクニックが可能になります。
たとえば、このスニペットを使用してFortranフラグを宣言して使用すると、完全に正常に動作します。
all:
echo $(FFLAGS)
FFLAGS = $(include_dirs) -O
include_dirs += -I./include
include_dirs += -I/opt/some_dep/include
make
を実行した後、期待される(または予期しない)出力が表示されます。
echo -I./include -I/opt/some_dep/include -O
-I./include -I/opt/some_dep/include -O
ヒント
定義されていない変数に+=
を使用して追加すると、その状態が以降の追加すべてに継承される再帰的に展開される変数が生成されます。
興味深い機能のように思えますが、驚くべき予期せぬ結果につながる傾向があります。通常、コンパイラなどの変数を定義する場合、再帰的な展開を実際使用する理由はほとんどありません。
:=
宣言を使用して、簡単に同じことを実現できます。
all:
echo $(FFLAGS)
include_dirs := -I./include
include_dirs += -I/opt/some_dep/include
FFLAGS := $(include_dirs) -O
重要
Makefile
は常にルールの完全なセットとして考えてください。ルールを評価する前に、完全に解析する必要があります。
好きな種類の変数を自由に使用できます。もちろん、それらを混ぜ合わせる場合は注意深く行う必要があります。2種類の変数の違いとそれぞれの意味を理解することが重要です。
Mesonビルドシステム#
低レベルビルドシステムと呼ぶmake
の基本を学習した後、高レベルビルドシステムであるmeson
を紹介します。低レベルビルドシステムではプログラムのビルド方法を指定しますが、高レベルビルドシステムを使用してビルドするものを指定できます。高レベルビルドシステムは、方法を処理し、低レベルビルドシステムのビルドファイルを生成します。
使用可能な高レベルビルドシステムはたくさんありますが、特にユーザーフレンドリーであるように構築されているため、meson
に焦点を当てます。meson
のデフォルトの低レベルビルドシステムはninja
と呼ばれます。
完全なmeson.build
ファイルを見てみましょう。
project('my_proj', 'fortran', meson_version: '>=0.49')
executable('my_prog', files('tabulate.f90', 'functions.f90'))
これで完了です。次のステップは、meson setup build
を使用して低レベルビルドシステムを構成することです。次のような出力が表示されます。
The Meson build system
Version: 0.53.2
Source dir: /home/awvwgk/Examples
Build dir: /home/awvwgk/Examples/build
Build type: native build
Project name: my_proj
Project version: undefined
Fortran compiler for the host machine: gfortran (gcc 9.2.1 "GNU Fortran (Arch Linux 9.2.1+20200130-2) 9.2.1 20200130")
Fortran linker for the host machine: gfortran ld.bfd 2.34
Host machine cpu family: x86_64
Host machine cpu: x86_64
Build targets in project: 1
Found ninja-1.10.0 at /usr/bin/ninja
この時点での情報は、Makefile
で提供できるものよりも既に詳細です。ninja -C build
を使用してビルドを実行すると、次のような表示になります。
[1/4] Compiling Fortran object 'my_prog@exe/functions.f90.o'.
[2/4] Dep hack
[3/4] Compiling Fortran object 'my_prog@exe/tabulate.f90.o'.
[4/4] Linking target my_prog.
作成されたプログラムを build/my_prog
で探し、正しく動作することを確認してください。 ninja
が実行した手順は、Makefile
で記述した場合と同じ(依存関係を含む)ですが、明示的に指定する必要はありませんでした。meson.build
ファイルをもう一度確認してください。
project('my_proj', 'fortran', meson_version: '>=0.49')
executable('my_prog', files('tabulate.f90', 'functions.f90'))
Fortranプロジェクトであること(Fortranサポートのために特定のバージョンのmeson
が必要となる)と、my_prog
という実行ファイルを tabulate.f90
と functions.f90
からビルドするようにmeson
に指示しただけです。meson
にプロジェクトのビルド方法を伝える必要はありませんでした。それは自動的に判断しました。
注意
meson
はクロスプラットフォームのビルドシステムです。このプログラムで指定したプロジェクトは、ネイティブオペレーティングシステム用のバイナリをコンパイルしたり、他のプラットフォーム用にクロスコンパイルしたりするために使用できます。meson.build
ファイルも同様にポータブルであり、さまざまなプラットフォームで動作します。
meson
のドキュメントは、meson-build ウェブページにあります。
CMakeプロジェクトの作成#
meson
と同様に、CMakeも高レベルのビルドシステムであり、Fortranプロジェクトのビルドによく使用されます。
注意
CMakeは少し異なる戦略を採用しており、ビルドファイルの作成に完全なプログラミング言語を提供します。これは、CMakeでほぼすべてを行うことができるという利点がありますが、CMakeビルドファイルは、ビルドするプログラムと同じくらい複雑になる可能性もあります。
まず、以下の内容で CMakeLists.txt
ファイルを作成します。
cmake_minimum_required(VERSION 3.7)
project("my_proj" LANGUAGES "Fortran")
add_executable("my_prog" "tabulate.f90" "functions.f90")
meson
と同様に、CMakeビルドファイルの作成はこれで完了です。cmake -B build -G Ninja
で低レベルのビルドファイルを構成します。次のような出力が表示されます。
-- The Fortran compiler identification is GNU 10.2.0
-- Detecting Fortran compiler ABI info
-- Detecting Fortran compiler ABI info - done
-- Check for working Fortran compiler: /usr/bin/f95 - skipped
-- Checking whether /usr/bin/f95 supports Fortran 90
-- Checking whether /usr/bin/f95 supports Fortran 90 - yes
-- Configuring done
-- Generating done
-- Build files have been written to: /home/awvwgk/Examples/build
CMakeが f95
コンパイラを使用しようとしていることに驚くかもしれません。幸いにも、これはほとんどのシステムで gfortran
へのシンボリックリンクであり、実際の f95
コンパイラではありません。CMakeに適切なヒントを与えるために、環境変数 FC=gfortran
をエクスポートできます。再実行すると、正しいコンパイラ名が表示されます。
-- The Fortran compiler identification is GNU 10.2.0
-- Detecting Fortran compiler ABI info
-- Detecting Fortran compiler ABI info - done
-- Check for working Fortran compiler: /usr/bin/gfortran - skipped
-- Checking whether /usr/bin/gfortran supports Fortran 90
-- Checking whether /usr/bin/gfortran supports Fortran 90 - yes
-- Configuring done
-- Generating done
-- Build files have been written to: /home/awvwgk/Example/build
同様に、Intel Fortranコンパイラを使用してプロジェクトをビルドすることもできます(FC=ifort
を設定します)。
CMakeはいくつかの低レベルビルドファイルをサポートしています。デフォルトはプラットフォーム固有であるため、meson
と一緒に使用した ninja
を使用します。前と同様に、ninja -C build
でプロジェクトをビルドします。
[1/6] Building Fortran preprocessed CMakeFiles/my_prog.dir/functions.f90-pp.f90
[2/6] Building Fortran preprocessed CMakeFiles/my_prog.dir/tabulate.f90-pp.f90
[3/6] Generating Fortran dyndep file CMakeFiles/my_prog.dir/Fortran.dd
[4/6] Building Fortran object CMakeFiles/my_prog.dir/functions.f90.o
[5/6] Building Fortran object CMakeFiles/my_prog.dir/tabulate.f90.o
[6/6] Linking Fortran executable my_prog
作成されたプログラムを build/my_prog
で探し、正しく動作することを確認してください。ninja
が実行した手順は多少異なります。プロジェクトをビルドするというタスクを実行するために、低レベルのビルドファイルを記述する方法は通常複数あるためです。幸いなことに、私たちはそれらを気にする必要はなく、ビルドシステムがその詳細を処理してくれます。
最後に、プロジェクトを指定するための完全な CMakeLists.txt
を簡単に要約します。
cmake_minimum_required(VERSION 3.7)
project("my_proj" LANGUAGES "Fortran")
add_executable("my_prog" "tabulate.f90" "functions.f90")
Fortranプロジェクトであることと、my_prog
という実行ファイルを tabulate.f90
と functions.f90
から作成するようにCMakeに指示しました。CMakeは指定されたソースから実行ファイルをビルドする方法の詳細を知っているので、ビルドプロセスの実際の手順を心配する必要はありません。
ヒント
CMakeの公式リファレンスは、CMake ウェブページにあります。これはmanページで構成されており、man cmake
を使用してローカルのCMakeインストールでも利用できます。CMakeのすべての機能を網羅していますが、場合によっては非常に簡単にしか説明されていないこともあります。
SConsビルドシステム#
SConsは、Fortranプロジェクトをサポートする、自動依存関係分析機能を備えたもう1つの高レベルのクロスプラットフォームビルドシステムです。SConsの設定ファイルはPythonスクリプトとして実行されますが、Pythonプログラミングの知識がなくても成功裏に使用できます。必要に応じてPythonスクリプトを使用することで、ビルドプロセスとファイル名をより高度な方法で処理できます。
注意
SConsはPATH
などの外部環境変数を自動的に渡さないため、標準以外の場所にインストールされているプログラムやツールは、指定されていない限り、または適切な変数を介して渡されない限り、検出しません。これにより、ビルドが外部(特にユーザーの)環境変数の影響を受けず、ビルドが再現可能になります。このような変数、コンパイラオプション、フラグのほとんどは、特別な「分離された」Environments
内で構成する必要があります(詳細はユーザーガイドを参照してください)。
SConsは外部の低レベルビルドシステムを使用せず、独自のビルドシステムに依存しています。ninja.build
ファイルを生成するための外部ツールとしてのninja
のサポートは非常に実験的です(SCons 4.2以降で利用可能)追加の設定によって明示的に有効にする必要があります。
単純なSConsプロジェクトは、以下を含むSConstruct
ファイルです。
Program('my_proj', ['tabulate.f90', 'functions.f90'])
scons
コマンドでプロジェクトをビルドします。
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
gfortran -o functions.o -c functions.f90
gfortran -o tabulate.o -c tabulate.f90
gfortran -o my_proj tabulate.o functions.o
scons: done building targets.
または、詳細な出力を無効にするにはscons -Q
を使用します。
gfortran -o functions.o -c functions.f90
gfortran -o tabulate.o -c tabulate.f90
gfortran -o my_proj tabulate.o functions.o
作成されたプログラムmy_prog
を、ソースファイルと同じディレクトリ(デフォルト)で探し、正しく動作することを確認してください。
ビルド成果物をクリーンアップするには、scons -c
(またはscons -Qc
)を実行します。
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Cleaning targets ...
Removed functions.o
Removed user_functions.mod
Removed tabulate.o
Removed my_proj
scons: done cleaning targets.
SConsのSConstruct
ファイルでは
Program('my_proj', ['tabulate.f90', 'functions.f90'])
実行可能ファイルのターゲット名my_proj
(省略可能、省略した場合は最初のソースファイル名が使用されます)と、ソースファイルのリスト['tabulate.f90', 'functions.f90']
を指定しました。プロジェクトのソースファイルの言語を指定する必要はありません。SConsによってサポートされている言語の場合は自動的に検出されます。
ソースファイルのリストは、SConsのGlob
関数を使用して指定できます。
Program('my_proj', Glob('*.f90'))
または、SConsのSplit
関数を使用して
Program('my_proj', Split('tabulate.f90 functions.f90'))
より読みやすい形式で変数を割り当てることで
src_files = Split('tabulate.f90 functions.f90')
Program('my_proj', src_files)
Split
関数の場合は、Pythonの「トリプル引用符」構文を使用して複数行を使用できます。
src_files = Split("""tabulate.f90
functions.f90""")
Program('my_proj', src_files)
コメントと空白#
make
を使用する際に時々発生する可能性のある、空白とコメントに関するいくつかの注意点があります。まず、make
は文字列以外のデータ型を認識せず、デフォルトのセパレータは単なるスペースです。つまり、ファイル名にスペースがあるプロジェクトをビルドしようとすると、make
は困難な状況になります。このような場合に遭遇した場合は、ファイルを名前変更するのが最も簡単な解決策です。もう1つの一般的な問題は、先頭と末尾の空白です。一度導入されると、
make
は喜んでそれを運び、実際make
で文字列を比較する際に違いが生じます。これらは次のようなコメントによって導入される可能性があります。
make
によってコメントは正しく削除されますが、末尾の2つのスペースは変数の内容の一部になります。make
を実行して、これが実際の場合であることを確認してください。この問題を解決するには、コメントを移動するか、代わりに
strip
関数を使用して空白を削除するか、または文字列をjoin
することができます。いずれにしても、これらの解決策のどれも
Makefile
の可読性を向上させるものではありません。したがって、make
を作成および使用するときは、空白とコメントに特別な注意を払うことが賢明です。