makefile の書き方

初版   2004.2.27
最終更新 2004.2.27

unix でプログラムを開発するときに不可欠なコマンドが make という コマンドです。make コマンドは makefile というファイルの中に 書かれた規則に従って動作します。ここでは makefile の書き方に ついて説明します。

Fortran や C でプログラムを開発する場合、通常は プログラムはいくつかのファイルに分かれています。 実行ファイルは「コンパイル」→「リンク」という手順を経て 作成されます。以下のような場合について考えます。

コンパイル リンク a.c ------(1)------> a.o ------+ b.c ------(2)------> b.o ------+-(4)----> abc c.F ------(3)------> c.o ------+

(1)(2)(3)(4) では 次のようなコマンドを実行する必要があります。

(1) cc -c a.c (2) cc -c b.c (3) f77 -c c.F (4) f77 -o abc a.o b.o c.o

なお、この例では C と fortran の混合プログラムなので、 リンクのコマンドとして f77 を使用しています。 a.c を更新した場合を考えます。この場合は、 (1) と (4) の処理を行えば良いです。(2) と (3) は行う 必要がありません。ですから、次のようなルールが あれば良いことになります。

  1. a.c と a.o の日付を比べて、a.c の方が新しいなら cc -c a.c を実行する
  2. b.c と b.o の日付を比べて、b.c の方が新しいなら cc -c b.c を実行する
  3. c.F と c.o の日付を比べて、c.F の方が新しいなら f77 -c c.F を実行する
  4. abc と a.o, b.o, c.o の日付を比べて、a.o, b.o, c.o のうち1つでも abc より新しいなら f77 -o abc a.o b.o c.o を実行する

このようなルールを記述したファイルが makefile であり、makefile で 記述したルールに従って、必要があれば「コンパイル」「リンク」を 行うのが make コマンドです。makefile の内容は次のようになります。

abc : a.o b.o c.o <--tab-->f77 -o abc a.o b.o c.o a.o : a.c <--tab-->cc -c a.c b.o : b.c <--tab-->cc -c b.c c.o : c.F <--tab-->f77 -c c.F

ここで、<--tab--> と記述してある場所は tab を1つ入れる という意味です。スペースを入れるとエラーになりますので注意 して下さい。makefile におけるルールは以下のように 記述します。

ファイル名 : そのファイルを作る元となるファイルの名前 <--tab-->そのファイルを作る方法

make コマンドには暗黙のルールというのがデフォルトで含まれて います。実は、「xxx.c をコンパイルして xxx.o を作成するときは cc -c xxx.c を実行する」 「yyy.F をコンパイルして yyy.o を作成するときは f77 -c yyy.F を 実行する」というルールはデフォルトで定義されているので、 上記の makefile 中で定義されている4つのルールのうち、 a.o : a.c 以下は全て不要です。

次に「xxx.c をコンパイルして xxx.o を作成するときは cc -c -g xxx.c を 実行する」というルールを作る方法を説明します。次のように makefile を 書きます。

.SUFFIXES: <--- この行があると全ての暗黙のルールを無効にする .SUFFIXES: .o .c .F <--- ルールを記述する拡張子 .c.o : <--tab-->cc -c -g $< .F.o : <--tab-->f77 -c -g $*.F abc : a.o b.o c.o <--tab-->f77 -o abc a.o b.o c.o

「f77 -c -g $*.F」は「f77 -c -g $<」と書いても同じです。 また、この場合は一番最初の行は不要です。暗黙のルールを 無効にする必要がある場合として、次のような場合があります。

プリプロセス コンパイル a.x ----- (1) ------> a.F -----(2)-----> a.o xypp a.x > a.F f77 -c a.F

当研究室ではグラフ描画のサブルーチンをコールするソースプログラムは 拡張子 .x を使用し、プリプロセスしてからコンパイルします。 そこで、次のようなルールを「順番に適用すること」が必要です。

  1. .x という拡張子のファイルがあるなら .x と .o の日付を調べ、(1)(2) の 手順を実行する。
  2. .F という拡張子のファイルがあるなら .F と .o の日付を調べ、(2) の 手順を実行する。
これを、実現するには次のようにルールを書く必要があります。 必要です。 .SUFFIXES: <--- 暗黙のルールを無効にする .SUFFIXES: .o .c .x .F <--- .x の順番は .F より前に .c.o : <--tab-->cc -c -g $< .x.o : <--tab-->xypp $*.x > $*.F <--tab-->f77 -c -g $*.F .F.o : <--tab-->f77 -c -g $*.F

makefile においては暗黙のルールが優先するようです。 1行目を省略すると、「暗黙の .F ---> .o」のルールが makefile 中で定義した「.x ---> .o」のルールより先に 適用されてしまうため、c.x を更新しても c.F の日付は c.o と 同じであるため、再プリプロセス、再コンパイルが行われません。

コンパイル リンク a.c ---------------> a.o ------+ b.c ---------------> b.o ------+--------> abc c.F ---------------> c.o ------+ d.c ---------------> d.o ------+ e.c ---------------> e.o ------+--------> def f.F ---------------> f.o ------+

上のような場合は次のように makefile を作ります。

all : abc def abc : a.o b.o c.o <--tab-->f77 -o abc a.o b.o c.o def : d.o e.o f.o <--tab-->f77 -o def d.o e.o f.o 変数を導入して分かりやすく記述することも出来ます。 EXE1 = abc EXE2 = def OBJ1 = a.o b.o c.o OBJ2 = d.o e.o f.o all : $(EXE1) $(EXE2) $(EXE1) : $(OBJ1) <--tab-->f77 -o $(EXE1) $(OBJ1) $(EXE2) : $(OBJ2) <--tab-->f77 -o $(EXE2) $(OBJ2)