Makefileでヘッダファイルの依存関係を扱う


Table of Contents

1 ヘッダファイルの依存関係を扱う必要性

ヘッダファイルの依存関係を扱わないと、ヘッダファイルが更新されても、それをインクルードするソースコードがビルドされない問題があります。オブジェクトファイルをクリーンして全ビルドを実行する必要があり、コンパイル時間が膨大になります。C++の場合は特に気をつけるべきです。

例えば、以下の構成のソースツリーがあります。

$ tree.├── Makefile├── include│ └── sample.h├── sample└── sample.c1 directory, 4 files

sample.cは自分で作成したsample.hをインクルードします。

$ cat sample.c#include <stdio.h>#include <sample.h>int main(void){ return 0;}

MakefileでCFLAGSにインクルードパスを設定します。PROGにはsampleという文字列が格納されます。

$ cat MakefileCFLAGS := -I. -I./includeSRC := $(wildcard *.c)PROG := $(patsubst %.c,%,$(SRC))all: $(PROG)clean: @$(RM) $(PROG)

allターゲットの依存に$(PROG)を指定することで、Makefileはsampleを作成するための暗黙のルールを用意してくれます。Makefileで設定したCFLAGSを用いてccコマンドが実行されます。再度makeを実行してもsampleというファイルがあるので実行されません。

$ makecc -I. -I./include sample.c -o sample$ make: Nothing to be done for `all’.

なお、暗黙のルールを利用しない場合は以下の様なターゲットを定義します。

%:%.c $(CC) $(CFLAGS) $< -o $@

touchコマンドでsample.cのタイムスタンプを更新すると、sample.cとsampleのタイムスタンプを比較して、新しくsampleを作成してくれます。

$ touch sample.c$ makecc -I. -I./include sample.c -o sample

問題はsample.hが更新されても、makeは新しくsampleを作成しない点です。sample.cはsample.hをインクルードしているので、sample.cの内容が変更されたことになります。sampleは新しく作成されるべきです。

$ touch include/sample.h$ makemake: Nothing to be done for `all’.

2 $(CC)の-MMオプション

-Mオプションや-MMオプションを用いることでインクルードされているヘッダファイルを出力します。

-Mオプションの場合は標準インクルード(コンパイラに組み込まれたサーチパスにあるヘッダファイル)も含んで表示します。stdio.hの延長で多くのファイルがインクルードされています。

$ cc -M -I./include sample.c | headsample.o: sample.c /usr/include/stdc-predef.h /usr/include/stdio.h /usr/include/features.h /usr/include/sys/cdefs.h /usr/include/bits/wordsize.h /usr/include/gnu/stubs.h <snip>

-MMオプションの場合は標準インクルードを含めないで表示します。-MMオプションを用いてMakefileでヘッダファイルの依存を扱います。

$ cc -MM -I./include sample.csample.o: sample.c include/sample.h

3 依存関係を記述した%.dファイル

そこで従来から%.dファイルの自動作成が推奨されています。

  • $(CC)の-MMオプションで”sample.o: sample.c include/sample.h”を出力する。
  • sedでsample.dの依存関係を追加する。これはsample.cとsample.hが更新された場合にsample.oだけでなく、sample.dを再作成するようにするためである。

これを踏まえたMakefileは以下のようになります。allターゲットで%.dファイルの作成を依存しておき、make $(PROG)ターゲットを呼びます。$(PROG)もallの依存に記述するとうまく動作しない点に注意してください。$(PROG)を満たす為に%ターゲットが呼ばれ、%の生成に暗黙のルールが利用されます。

CFLAGS := -I. -I./includeSRC := $(wildcard *.c)OBJ := $(patsubst %.c,%.o,$(SRC))DEP := $(patsubst %.c,%.d,$(SRC))PROG := $(patsubst %.c,%,$(SRC))all: $(DEP) @$(MAKE) $(PROG)clean: @$(RM) $(DEP) $(OBJ) $(PROG)ifneq ($(filter clean,$(MAKECMDGOALS)),clean)-include $(DEP)endif%.d: %.c $(info GEN $@) @$(CC) -MM $(CFLAGS) $< | sed ‘s/($*).o[ :]*/1.o $@ : /g’ > $@%: %.d

先ほどとは異なり、sample.hを更新した場合も新しくsampleが生成されます。

$ makeGEN sample.dcc -I. -I./include -c -o sample.o sample.ccc sample.o -o sample$ makemake[1]: `sample’ is up to date.$ touch sample.c$ makeGEN sample.dcc -I. -I./include -c -o sample.o sample.ccc sample.o -o sample$ touch include/sample.h$ makeGEN sample.dcc -I. -I./include -c -o sample.o sample.ccc sample.o -o sample

Android | Linux | SDL - Narrow Escape