UNIX/Linux及びシェルにおいてmakefileとは、makeコマンドによって実行されることを想定してファイル間の依存関係とタイムスタンプに基づくルールを羅列またはパターンによって記述したテキストファイルです。
makeコマンドとmakefileではmakeコマンドとは何か、makeとgmakeがあること、makeとmakefileは何に使うのか、どんな点で有用なのか、既定でmakeはmakefile、Makefileの順に検索するけどここではmakefileで統一しますよなどと書きましたが、このページでは一例として実際にmakefileを作ってみます。
尚、特別な記述がない限り、C言語用のmakefile作成を想定しているものとします。
makefileは
ファイル間の依存関係とタイムスタンプに基づくルールの羅列またはパターン
であるわけですが、Cで依存関係と言えばボディファイルやソースファイル、ヘッダファイル、オブジェクトファイルなどの相互関係を指し、タイムスタンプとはこれらのファイルのタイムスタンプのことであり、そのルールは、
* ターゲットとなるファイル名などのラベルにコロンを付けて書く(TARGET:)
最初に書くターゲットはmakeコマンドで引数を省略した場合にも実行される
* コロンに続けてTARGETに「依存する」ファイルやターゲットを書く
複数ある場合にはスペース区切りで書く
実は省略も可(だからフォニーターゲットとしても使える*後述)
* 必要に応じて改行、1つのタブの後TARGETごとのコンパイルコマンドを書く
強いて言えばあらゆるUNIX・Linuxコマンド、当然makeコマンドも記述可
もちろん必要なければ書かなくてもよいし、複数あれば複数記述できる
* コマンドを書く行の先頭は、誰が何と言おうと必ず絶対どうしても
「1つのタブ」で始めなければならない
間違えて空白にしようもんなら(意外と気付かないから)ショッキング
ということでコマンドとは主にcc/gccのコンパイルコマンドを書くことが多いので要するにテキストファイルを新規で開いて、いきなり
TARGET : foo.c
cc -Wall -c foo.c
と書いてmakefileという名前で保存すればOKということですが、ターゲットも小文字で書かれるのが普通なので
target : foo.c
cc -Wall -c foo.c
またターゲットだからと言ってTARGETやtargetである必要は全くないので
that_is_not_target : foo.c
cc -Wall -o foo.c
と言い張ってみても残念ながらターゲットなわけですが、但し、makefileでは存在の如何に関わらずターゲットには一応ファイルが想定されているので少なくともカレントディレクトリにもっと言えば同じファイルシステム内に同名のファイルが存在しないことが前提です。
実は、こうした記述方法は普通一般のコンパイル用ターゲットとしてではなく、フォニーターゲットという便利で手放せない機能で利用される書き方で慣例として使用頻度の高そうなフォニーターゲットとしてall/install/clean/varycleanなどがあります。
そこで「ファイル名などのラベルにコロン」と言うことからしてもターゲットにはファイル名を書くのが、より一般的なのではないかと、この強引な誘導作戦によってなんとなく察することができてしまったと思いますが、Cではオブジェクトファイルはボディファイル(と関連するソースファイル)なくして存在しない、つまり依存する「依存関係」にあるのでボディファイル(や関連するソースファイル)はオブジェクトファイルのターゲットと考えることができ
foo.o : foo.c
cc -Wall -c foo.c
結果このようにシンプルなmakefileを書くことができました。
ところでCコンパイル時、cc/gccに-cオプション付きで敢えて残した1つのボディファイルから成るオブジェクトファイルの更新が必要なのはmakefileで言うところのオブジェクトファイルが依存する元になるボディファイル(や関連するソースファイル)とそれがincludeしているかもしれない(C標準ライブラリ以外の)ヘッダファイルが更新された時だけですが、まさにmakeは、そういう場合に力を発揮します。
先の例でも良いのですが、makefileの基本動作であり特徴の1つでもあるタイムスタンプ比較の有用性をよりわかりやすくする為にもう少し複雑に
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
として、この場合の1行めを見ると「foo.oは、foo.c、bar.o、hoge.oに依存する」と表現されるわけですが、実は、この時、makeはmakefileに書かれた依存するファイルを芋づる式に手繰っていきタイムスタンプを比較した上でコンパイルが必要なファイルだけ、つまり、依存しているファイル(群)より古いターゲットだけをコンパイルします。
例えば、(引数を省略するとmakefile冒頭に書かれたターゲットを実行するので)make、または、make foo.oを実行する際に、bar.oが依存しているbarbar.hだけが変更されていた場合、依存関係とタイムスタンプを調べて依存するファイルに変更のあったbar.oターゲットのコマンドを実行しますが、それによって必然的にbar.o自体が更新されるので、bar.oに依存しているfoo.oターゲットも更新が必要であると判断され、そのコマンドを実行するということです。
仮にその直後に再度makeまたはmake foo.oと実行するとmakefile内のコマンドは1つも実行されず、foo.oが最新であることを伝えるメッセージが表示されます。
もちろん、ターゲットを指定してmakeを実行することもできるのでmake hogehoge.oとすればhogehoge.cだけがコンパイルされます。
これは、かなり効率よく便利である一方、この方法だとファイルが増えるに連れmakefileの行もファイルサイズも比例して大きくなってしまいますが、更に効率的な冒頭の
ファイル間の依存関係とタイムスタンプに基づくルールの羅列またはパターン
としてまだ触れていないキーワードでもある「パターン」と自動変数や変数を利用したmakefileの記述方法があります。