TL;DR
- gfortranでモジュールファイルをコンパイルする時、インターフェースが変わらなければmodファイルのタイムスタンプは更新されない。
- *.modファイルは*.oファイルと*.f90ファイルに依存する形でMakefileなどに書き込もう。
- *.modファイルを更新すべきときは何も実行しない。
まえおき
自分の研究室には、Fortranで書かれている自家製プログラムがあるんですよ。 今どきFortranかよって思いますけど、シミュレーション界隈ではまだまだFortranが現役で使われてるところが結構あるらしいので、まぁうちの研究室もご多分に漏れずって感じなんですかね。
このプログラムはお世辞にも綺麗に書けてるとは言えず、色々文句を言いたい点があります。 そもそもFortranで書かれているっていうのは百歩譲って許しますが、Makefileの書き方が雑なんですよ。
なにが雑って、Fortranのコンパイラを切り替えるためにMakefileを直接編集しなきゃいけないところとか(autotools使えよ)、 ソースコードの依存関係をちゃんと解決できていないことが原因で、コードを編集した後、再度コンパイルしようとしたらコンパイルエラーになるとか、色々です。
そこで、後者の依存関係を解決するために書き直そうとしたところ、ちょっと躓いたっていうのが主題です。
サンプルプログラム
研究室のプログラム全体をここに示すのは冗長すぎるので、サンプルプログラムを書きました。
Makefile
におけるモジュールの依存関係を書いてないところ以外は普通だと思います。
! math_const.f90 module math_const implicit none real(4) :: pi = 3.14159265359 end module
! main.f90 program main use math_const implicit none write(*,*) 'pi = ', pi end program
# Makefile FC = gfortran .PHONY: all all: main main: main.o math_const.o $(FC) -o $@ $^ main.o: main.f90 $(FC) -c -o $@ $< math_const.o: math_const.f90 $(FC) -c -o $@ $< .PHONY: clean clean: rm -f main *.o *.mod
これと同じディレクトリでmake
すると、math_const.f90
よりも先にmain.f90
をコンパイルしようとするので、コンパイルエラーになります。
gfortran -c -o main.o main.f90 main.f90:2:6: use math_const 1 Fatal Error: Can't open module file ‘math_const.mod’ for reading at (1): No such file or directory compilation terminated. make: *** [Makefile:10: main.o] Error 1
これを回避するには先にmath_const.f90
をコンパイルしてmath_const.mod
を作成しておかないといけません。
この例で手っ取り早いのはMakefile
中のmain.o
とmath_const.o
に関する項目の順番を入れ替えることですが、
大きなプロジェクトになるといちいちMakefile
内の順番を気にしてなんていられません。
それに依存関係が関連付けられるわけでもないので、依存してるファイルが再コンパイルされない問題も起きます。
ですので、依存関係を記さなければいけません。
ダメなケース1
main.f90
をコンパイルしてmain.o
を作成する前に、math_const.mod
を作成しなくてはいけないので、
main.o
の依存ファイルにmath_const.mod
を追加しなければいけません。
ですが、math_const.mod
に関する項目はMakefile
には無いので、サボってmath_const.o
を追加してしまいましょう。
コンパイルしてmath_const.o
が作成されるときには同時にmath_const.mod
が作成されるのでコンパイルできると思います。
# Makefile ... main.o: main.f90 math_const.o ...
こうすると、コンパイルは通るようになります。
$ make gfortran -c -o math_const.o math_const.f90 gfortran -c -o main.o main.f90 gfortran -o main main.o math_const.o
ですが、math_const.f90
が更新された時にすこし不具合が生じます。
$ touch math_const.f90 $ make gfortran -c -o math_const.o math_const.f90 gfortran -c -o main.o main.f90 gfortran -o main main.o math_const.o
一見すると、問題ないように見えますが、touch
しただけではmath_const
モジュールのインターフェースには変化がないのでmain.f90
を再コンパイルする必要はないはずです。
ダメなケース2
では、諦めてmath_const.mod
の項目をMakefile
に追加したいところですが、Makefile
には一つの処理に複数の出力ファイルがある場合の対処方法が一般にはありません。
ですが、ここで思い出してください、当初の目的はmain.f90
よりも先にmath_const.f90
をコンパイルするでした。
これを実現するだけなら、math_const.o
の項目をmath_const.mod
に変えるだけでできますが、main
の依存ファイルにconst_math.o
があるので、
この項目を消すと厄介なことになるかも知れません。
そこで、この項目をコピペしちゃいましょう。
# Makefile ... main.o: main.f90 math_const.mod $(FC) -c -o $@ $< math_const.o: math_const.f90 $(FC) -c -o $@ $< math_const.mod: math_const.f90 $(FC) -c -o math_const.o $< ...
ここでmake clean && make
するとちゃんとコンパイルできます。
$ make clean && make rm -f main *.o *.mod gfortran -c -o math_const.o math_const.f90 gfortran -c -o main.o main.f90 gfortran -o main main.o math_const.o
math_const.f90
を更新してみます。
$ touch math_const.f90 $ make gfortran -c -o math_const.o math_const.f90 gfortran -o main main.o math_const.o
main.f90
が再コンパイルされていません。うまくいったように見えます。
が、実は問題があります。もう一度make
すると、
$ make gfortran -c -o math_const.o math_const.f90 gfortran -o main main.o math_const.o
再度、math_const.f90
のコンパイルが走ってしまいます。この後何度繰り返しても再コンパイルし続けます。
modファイルのタイムスタンプ
これが何故起きるのかと言うと、gfortran
でモジュール定義を含むファイルをコンパイルした時、
モジュールのインターフェースが更新されなかったら、mod
ファイルも更新されません。
つまり、mod
ファイルのタイムスタンプが更新されないのです。
make
などの主要なビルドシステムはファイルのタイムスタンプで更新するべきかどうかを判断しています。
例えば、math_const.o
のタイムスタンプよりもmath_const.f90
のタイムスタンプの方が新しい場合、ソースコードが更新されたから再コンパイルするべきだと判断されます。
# math_const.f90の方が新しい場合 $ make math_const.o gfortran -c -o math_const.o math_const.f90
# math_const.oの方が新しい場合 $ make math_const.o make: 'math_const.o' is up to date.
ここで、math_const.mod
の例に戻ると、math_const.f90
は更新されましたが、モジュールのインターフェースが変わっていません。
ですので再コンパイル時にmath_const.mod
のタイムスタンプは更新されず、math_const.f90
のタイムスタンプよりも古いままになっています。
何度make
してもこのタイムスタンプの新旧は変化しないため、毎度再コンパイルされる羽目になっているのです。
最終的なMakefile
そこで、この無駄な再コンパイルを阻止するためには、以下のようにします。
このようなMakefile
の書き方は検索すれば出てくるやり方と大体同じです。
# Makefile FC = gfortran .PHONY: all all: main main: main.o math_const.o $(FC) -o $@ $^ main.o: main.f90 math_const.mod $(FC) -c -o $@ $< math_const.o: math_const.f90 $(FC) -c -o $@ $< math_const.mod: math_const.o .PHONY: clean clean: rm -f main *.o *.mod
math_const.mod
の項目は依存関係だけを記して、特に何もしません。
math_const.o
を作成する時にコンパイルが走るので、そちらに依存するだけです。
こうすることで、math_const.mod
とmath_const.o
とのタイムスタンプの比較になるのですが(math_const.f90
との比較では無いですが問題ないです)、
もしmath_const.mod
のタイムスタンプの方が古くても、ここでは何もせず、math_const.o
が更新すべきかどうかの判定に移ります。
math_const.o
はコンパイルされるごとにタイムスタンプが更新されるので、一度コンパイルされればmath_const.f90
が更新されない限り、
再コンパイルされることはなくなります。
確認すると、ちゃんと機能していることが分かります。
$ make clean && make rm -f main *.o *.mod gfortran -c -o math_const.o math_const.f90 gfortran -c -o main.o main.f90 gfortran -o main main.o math_const.o $ touch math_const.f90 $ make gfortran -c -o math_const.o math_const.f90 gfortran -o main main.o math_const.o $ make make: Nothing to be done for 'all'.