コンパイラでヘッダファイルの依存関係を出力してMakeで必要なファイルだけをコンパイルする
はじめに
GCC等のコンパイラにはコンパイル対象のソースが依存するヘッダファイルの一覧をMakefileのルールの形で出力する機能がある。この機能を使ってヘッダファイルに変更があった場合にMakeで必要なファイルだけをコンパイルする方法を説明する。Makeにあまり慣れていない人向け。
環境
- OS: macOS 11.6.8
- Make: GNU Make 3.81
- コンパイラ: Apple clang version 12.0.5 (clang-1205.0.22.9)
- この記事では
gcc
というコマンドで起動される
- この記事では
やりたいこと
以下のようなディレクトリにいるとする。
$ ls
a.c lib.h Makefile
それぞれのファイルの中身は以下の通り。
#include "lib.h"
int main(void) {
return ANSWER;
}
#define ANSWER 42
CC=gcc
program: a.o
$(CC) -o $@ $^ $(LDFLAGS)
.PHONY: clean
clean:
rm -f program *.o
このディレクトリでmake
するとa.o
とprogram
ができる。
$ make
gcc -c -o a.o a.c
gcc -o program a.o
このような状況で、lib.h
の内容が変わったとき、make
でa.c
が再度コンパイルされるようにしたい。それだけであればa.o: lib.h
のようなルールを書けば実現できるのだが、この方法では将来ソースファイルやヘッダファイルがもっとたくさんになったときに、それらの依存関係が常に正しく反映されるようにMakefileを更新し続けなければならない。もっと楽に、Makefileをいちいち更新しなくても、ヘッダファイルの更新に応じて必要なファイルだけがコンパイルされるようにしたい。
実現方法
結論から言うと、Makefileを次のように変更すればよい。
CC=gcc
+ CFLAGS += -MMD -MP
program: a.o
$(CC) -o $@ $^ $(LDFLAGS)
.PHONY: clean
clean:
- rm -f program *.o
+ rm -f program *.o *.d # *.dの削除はお好みでいいかも
+ -include *.d
-MMD
はコンパイルと同時にヘッダファイルの依存情報を*.d
ファイルに出力する。例を見た方が早いだろう。
$ gcc -MMD -c -o a.o a.c
$ cat a.d
a.o: a.c lib.h
さらに-MP
をつけると結果が次のように変わる。
$ gcc -MMD -MP -c -o a.o a.c
$ cat a.d
a.o: a.c lib.h
lib.h:
違いはlib.h
がダミーのターゲット[1]として定義されていることである。これはlib.h
が存在しないときのMakeの挙動を変える。
- ダミーのターゲットがない場合、
lib.h
が存在しないとエラーが発生する。 - ダミーのターゲットがある場合、
lib.h
が存在しなくてもエラーは発生しない。また、lib.h
が存在しないときa.o
が更新の対象となる。
つまりlib.h
がいらなくなって削除したときに、それまでlib.h
をインクルードしていたファイルが再コンパイルの対象となる。これは望ましい挙動と言えそうだ。もっともlib.h
を削除したのならそのインクルード箇所も削除しているはずで、どのみちlib.h
をインクルードしていたファイルは再コンパイルの対象となるのだが。
-include *.d
は上記の工程でできた*.d
ファイルをMakefileにインクルードする。先頭に-
がついているので*.d
ファイルがひとつも存在しなかった場合エラーは起こさず無視する。
初回のmake
では当然*.d
ファイルは存在しない。2回目以降のmake
では前回のmake
で生成された*.d
ファイルが参照される。最新のソースコードから生成した*.d
ファイルではなく前回のものを参照して大丈夫なのか疑問に思うかもしれないが、これは問題ない。前回の*.d
ファイルさえあれば、前回からあるヘッダファイルに変更があった場合に依存するファイルの再コンパイルが走るし、ヘッダファイルの依存関係が増えたり減ったりするのはインクルードする側の変化なので*.d
とか関係なく再コンパイルが走る。*.d
ファイルだけを削除してmake
するとかはやってはいけない。
-
これはEmpty RecipesかRules without Recipes or Prerequisitesの扱いだと思うが正直よくわからない。 ↩︎
Discussion