UNIX/Linux及びシェルにおいてmakefileとは、makeコマンドによって実行されることを想定してファイル間の依存関係とタイムスタンプに基づくルールを羅列またはパターンによって記述したテキストファイルです。
makeコマンドとmakefileとmakefileを書くでターゲットを羅列したmakefileを実際に書くところまでいきましたが、ここでは更に劇的に効率のよい書き方ができる便利なパターンによる記述、自動変数、変数を使った記述をし、その上でルールの優先順位について考えてみます。
尚、特別な記述がない限り、C言語用のmakefile作成を想定しているものとします。
foo.o : foo.c bar.o hoge.o
cc -Wall -o foo.c
bar.o : bar.c barbar.h
cc -Wall -o bar.c
hoge.o : hoge.c hoge.h hogefoo.h
cc -Wall -o hoge.c
hogehoge.o : hogehoge.c
cc -Wall -o hogehoge.c
SRCFILES = foo.c bar.c hoge.c
fbhoge : $(SRCFILES)
cc -Wall -c -o fbhoge $(SRCFILES)
SRCFILES = foo.c bar.c hoge.c
fbhoge : $(SRCFILES)
cc -Wall -c -o $@ $^
前回はこんな羅列によるmakefileを書くところまでいきました、少し内容を変えますが、もっと効率的に記述を減らす書き方があるわけですが、このSRCFILESは変数でmakefileではUNIX/Linuxの慣例同様に通常小文字を使うので区別しやすいようにという意味で変数には大文字を使い、bash同様、代入時には$を付けず、参照時には$を付け変数を( )または{ }で括ります。
2つめの例は、1つめの記述をさらに簡略化したものですが、$@と$^は自動変数で前者がターゲットラベル名(ここではfbhoge)、後者がターゲットが依存するリストである$(SRCFILES)の値が入ります。
尚、SRCFILESはSOURCESと書かれる場合もあるかもしれませんし、依存を意味するDEPENDS、OBJSとかOBJECTS、その他、HEADERSとかINCLUDESなど利用する変数が状況によって異なる場合もあるように変数名は任意であるものの、CC/CFLAGS/CPPFLAGS/LDFLAGS/LDLIBSなどある程度慣習として利用される変数も多く、業務プロジェクトなどではこれらの値があらかじめ設定されている場合もあれば、開発者が設定しなければならない場合もあり、汎用性を意識すればするほど慣習に沿っていた方が都合が良いことはUNIX/Linuxを使う人たちの間では周知の事実なのでこれに倣うとよいでしょう。
...
depend :
cc -M $(SOURCES) > depend_file
...
include depend_file
もし、依存ファイルを変数に代入する際、大量に列挙しなければならないような場合は特にcc/gccに-Mオプションを渡して依存関係リストを生成するとよいかもしれません。
makefileでは、より末尾に記述、シャープ記号#を付けない点で異なるもののC/C++のようにファイルをincludeすることもできるので後述のフォニーターゲットとして依存関係リスト作成ターゲットを記述することもできます。
%.o : %.c
cc -Wall -c -o $@ $<
.SUFFIXES: .c .o
一方、この例はコマンドラインからmakeコマンドにボディファイル/ソースファイル(.c)を引数として渡す必要がありますがオブジェクトファイル(.o)と実行ファイルを生成します。
ここで使われる%(パーセント記号)は、自動変数でmake実行時の引数に任意のボディファイル(.c)が渡された時に同じ文字列である拡張子を除いたボディファイルの名前が入り、ボディファイルとオブジェクトファイルが同名の場合には汎用的に利用できる記述方法です。
次に$<も自動変数でターゲットが依存するファイル名が入り、「.SUFFIXES:」はmakefileで処理すべきファイルの拡張子を指定するものでSUFFIXとは後置詞(対語は前置詞/PREFIX)を表す英語で「.」ピリオド・ドットに続く後置詞ということでmakefileでは拡張子を表し、コロンに続けて対象となる拡張子をピリオド・ドットに続けて記述します。
%.o : %.c
cc -c $<
.SUFFIXES: .c
foo.o : foo.c foo.h
cc -c $<
makefile内で明示的に記述されルールとパターンルールがあった場合、明示的に記述されたルールが優先されます。
この場合、make foo.oが実行された場合は、ボディファイルだけでなくヘッダファイルも依存関係にあるのでいずれかが変更されていればコンパイルされfoo.oが生成されるわけですが、それ以外のボディファイルとオブジェクトファイルはパターンによってコンパイルされます。
%.o : %.c
$(CC) -c $<
.SUFFIXES: .c
foo.o : foo.h
実際には、この.cと.oのパターンを利用するとfoo.oとfoo.cの2つのファイル間でもパターンで処理できることになるのでターゲットfoo.oにはコマンドを書かずにターゲットラベルと残る依存関係(ここではfoo.h)だけを書き、更にccでもgccでも環境に対応できるように設定されているはずの変数も使うことができます(もっともgccがインストールされていればccはgccへのシンボリックリンクであることも珍しくないでしょう)。
makefileの一般ルールでは、必要なファイルはカレントディレクトリから検索しますが、システムの規模が大きくなるとパスを分けてファイルを管理するようになり、makefileでも必然的に他のパスを検索する必要が出てきます。
gmake(GNU make)の場合には、その際、環境変数VPATHを利用するか、vpathディレクティブを利用する方法があります。
VPATH = src : ../include : ...
環境変数(make変数)VPATHが利用できる場合には、コロン区切りまたはスペース区切り(Windowsではパスにコロンを含むのでセミコロン区切り)でパス指定をするか、既になされているはずです。
vpath %.c src
vpath %.h ../incluede
gmake(GNU make)のvpathが利用できる場合には、makefileのより先頭に小文字でvpath、スペースに続けて前述のパーセント記号%パターンで拡張子、更にスペースに続けてディレクトリパスを記述します。
但し、何れの場合も、まず、カレントディレクトリを検索してから、ソースファイル(.c)やヘッダ(.h)については存在しなければ指定パスを見に行きますが、オブジェクトファイル(.o)については存在しない場合には古いオブジェクトファイルがあるものとしてコンパイルが実行されるというその仕組みから、仮にVPATHやvpathにパスを記述してもカレントディレクトリに生成されることになりますので必然的にオブジェクトファイル(.o)に関してはmakefile内で個々にパス指定するか、makefileをオブジェクトファイルのディレクトリに置くかの二択になると思います。
ところでターゲットだけを記述する方法は以前makefileを書き始める段階でも出てきましたが、コンパイルコマンドだけではなく、例えばmakefileに書いた処理を一発で処理したいとか、ファイル生成だけじゃなくて不要なものをいっぺんに削除したりできると便利なことがよくありますが、makefileには、そんな時に役立つフォニーターゲットというターゲットとその記述方法があります。