Make

最近だと、プログラムをコンパイルするときには jakarta-ant を使うことが多いのですが、仕事の都合で make を使うことも稀にあります。
と言うか、今回必要に迫られて使うことになったので、備忘録程度に。

make とは何かと言うと、依存関係に基づいて順番にコマンドを呼び出してくれるプログラムの名前である。
で、依存関係を記述したファイルがMakefile。
Makefileは、以下の3種類の行からなる。(厳密にはもっとある)
  1. マクロ定義
  2. 依存関係
  3. 生成コマンド
この他に、#で始まる行は行末までコメントとみなされる。

1. マクロ定義

マクロ定義は、以下の形の行である。
HOGE = moge
上の例では、HOGE と言う名前のマクロを定義する。値は moge になる。
マクロの参照は、$(HOGE) または ${HOGE} と書く。$HOGE だと、H と言うマクロを展開した後に"OGE"が付くので注意。
繰り返し出てくる文字列などを定義したりするものだが、CFLAGSなどのようにデフォルトの規則に関連する特別なマクロも存在する。

2. 依存関係

依存関係は、以下の形の行である。
hoge.o : hoge.c hoge.h
上の例では、hoge.o と言うファイルは、hoge.c と hoge.h に依存していると言う意味になる。

3. 生成コマンド

生成コマンドは、依存関係の直後で、TABで始まる行である。
	gcc -c hoge.c

4. 実例

Cのソースファイル hoge.c があり、hoge.c は hoge.h をinclude しているとする。
このとき、以下のようなMakefileを作成してみる。
CC = gcc

all: hoge

hoge: hoge.o
	$(CC) -o hoge hoge.o

hoge.o: hoge.c hoge.h
	$(CC) -c hoge.c
すべてが同じディレクトリにあるとして、そのディレクトリで makeと打てば hoge.c と hoge.h から hoge.o が生成され、hoge.o から hoge が生成されるはずだ。
% make
gcc -c hoge.c
gcc -o hoge hoge.o
この例のように、Makefileには複数の依存関係を含むことができ、makeは再起的に依存関係を調査してくれる。
makeコマンドを実行すると、Makefileの最初の依存関係のターゲット(:の左辺)を生成する。make コマンドに引数としてターゲットを与えてやると、そのターゲットを生成する。
たとえば、一度 make と打って hoge が生成されたとして、そのまま make をもう一度実行すると、今度は何もおきない。最新の hoge があるからだ。
ここで、hoge を削除して make してみると、gcc -o hoge hoge.c の行だけが実行される。
hoge.h を編集すると、コンパイルからやり直すはずだ。
makeを使うと、複数のソースからなる大きなプロジェクトなどでも、必要なファイルだけをコンパイルしなおしてくれるので、作業効率が上がる。もちろん、そのためには正確な依存関係の記述が必須であるが。
# ちなみに、gccが使える環境であれば、gcc -MM hoge.c などとやると依存関係を出力してくれる。

最低限ここまで知っていれば、後は依存関係を書くだけでmakeは使えるはずである。が、以下のことを知っていればさらに便利に使うことができる。

5. 特殊なマクロ

makefileには、依存関係などを記述するのに便利な特殊なマクロがいくつかある。
$@
ターゲット名に置換される。
hoge: hoge.o
	$(CC) -o $@ hoge.o
$<
依存ファイル名に置換される。
hoge: hoge.o
	$(CC) -o hoge $<
$*
ターゲット名から拡張子を取り除いたものに置換される。
hoge.o: $*.c $*.h
	$(CC) -c hoge.c
$?
依存ファイルのうちで更新されたもののリストに置換される。
hoge.o: $hoge.c $hoge.h
	echo recompile for $?
	$(CC) -c hoge.c
これらを使って最初のMakefileを書き換えると、以下のようになる。
CC = gcc

all: hoge

hoge: $*.o
	$(CC) -o $@ $<

hoge.o: $*.c $*.h
	$(CC) -c $<
これらのマクロをうまく使うと、タイプミスなどによる原因不明のバグから解放されるだろう。
しかし、これらのマクロが本領を発揮するのは、以下のサフィックスルールである。

6. サフィックスルール

ここまでのMakefileの書き方では、プロジェクトで使用する全てのファイルについて、厳密な依存関係と生成コマンドを記述する必要があった。
ここで説明するのは、言わば暗黙の依存ルールである。
.SUFFIXES: .o .c

CC = gcc

.c.o:
	$(CC) -c $<

all: hoge

hoge: $*.o
	$(CC) -o $@ $<

hoge.o: $*.c $*.h
このように書くと、.c のファイルから .o のファイルを生成するときに、$(CC) -c $< が使われるようになります。
この生成コマンドが使われるのは、依存関係に対して生成コマンド行がない場合です。生成コマンド行がある場合はサフィックスルールは使用されません。
サフィックスルールを使用する場合には、あらかじめ .SUFFIXES: の行に使用するサフィックスを列挙しておきます。

7. デフォルトのルール

ここまで規則やコマンドなどを自分で書いて来たが、実際にはプログラムのコンパイルなどはたいていのプロジェクトで一緒である。
そこで、makeはもともといろいろなプログラムのコンパイルの仕方を知っているのだ。
ためしに、make -n -p などと打ってみると、makeが知っているデフォルトの規則がずらずら〜っと出てくる。
※ FreeBSDの標準のmakeには、-pオプションがなくなってしまって -dA とかやるとずらずらと出てきた。
このルールを良く見ると、CC や CFLAGSなどのマクロの意味が理解できるようになるだろう。デフォルトのルールはmakeによって違うので、ここでサンプルを上げることはしないが、簡単なプロジェクトであれば定義済みマクロの再定義と、依存関係を少し書いてやるだけでMakefileは出来上がるはずだ。

8. nmakeの補足

そもそも、このページに書いたような内容は今現在ではかなり古く、FreeBSDの標準のmakeでも、gmakeでももっといろいろなことができる。
さらに言えば、上に書いた特殊マクロなどは、FreeBSDのマニュアルには「互換性のために残してあるが、推奨しない」と書いてある。
しかし、ここはあえて古い方法を紹介することで、どんな環境でも使えるMakefileを目指したいところである。

が、今回仕事で使用しているのはMicrosoftのnmakeなので、上のMakefileをnmakeでも動くか実験してみた。Windowsなので拡張子を変えると、以下のような感じになる。
.SUFFIXES: .obj .c

.c.obj:
	$(CC) /c $<

all: hoge.exe

hoge.exe: $*.obj
	$(CC) -o $@ $<

hoge.obj: $*.c $*.h
これで動いてくれれば万万歳・・・と思ったら、exeを作るところで
NMAKE : warning U4006: 特殊マクロは定義されていません。 : '$<'
などと言われてしまい、うまく行かない。どうも、$<の意味がちょっと違うようだ。
で、調べてみたところ、「ファイル名マクロ」と言うことらしい。$*の意味も違うようだ。

false@wizard-limit.net