ビルドツール#

ソースファイルの数とモジュール間の相互依存関係によっては、Fortran プロジェクトを手動でコンパイルすることは非常に複雑になる可能性があります。適切なツールを使用してこれらのタスクを自動的に実行しない限り、異なるコンパイラやリンカ、または異なるプラットフォームのサポートはますます複雑になります。

プロジェクトの規模と目的によっては、ビルド自動化の選択肢が異なります。

まず、統合開発環境はプログラムをビルドする方法を提供している可能性があります。人気のクロスプラットフォームツールには、Microsoft の Visual Studio Code がありますが、AtomEclipse PhotranCode::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.otabulate.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.ofunctions.oの両方よりも新しく、それらはtabulate.f90functions.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種類の変数の違いとそれぞれの意味を理解することが重要です。

コメントと空白#

makeを使用する際に時々発生する可能性のある、空白とコメントに関するいくつかの注意点があります。まず、makeは文字列以外のデータ型を認識せず、デフォルトのセパレータは単なるスペースです。つまり、ファイル名にスペースがあるプロジェクトをビルドしようとすると、makeは困難な状況になります。このような場合に遭遇した場合は、ファイルを名前変更するのが最も簡単な解決策です。

もう1つの一般的な問題は、先頭と末尾の空白です。一度導入されると、makeは喜んでそれを運び、実際makeで文字列を比較する際に違いが生じます。

これらは次のようなコメントによって導入される可能性があります。

prefix := /usr  # path to install location
install:
	echo "$(prefix)/lib"

makeによってコメントは正しく削除されますが、末尾の2つのスペースは変数の内容の一部になります。makeを実行して、これが実際の場合であることを確認してください。

echo "/usr  /lib"
/usr  /lib

この問題を解決するには、コメントを移動するか、代わりにstrip関数を使用して空白を削除するか、または文字列をjoinすることができます。

prefix := /usr  # path to install location
install:
	echo "$(strip $(prefix))/lib"
	echo "$(join $(join $(prefix), /), lib)"

いずれにしても、これらの解決策のどれもMakefileの可読性を向上させるものではありません。したがって、makeを作成および使用するときは、空白とコメントに特別な注意を払うことが賢明です。

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.f90functions.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.f90functions.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)

ヒント

SConsの公式ウェブサイトには、ビルドプロセスの処理方法のさまざまな側面に関する詳細な説明を提供するユーザーガイドよくある質問ページ、およびmanページがあります。