📄

コンパイラでヘッダファイルの依存関係を出力してMakeで必要なファイルだけをコンパイルする

2023/01/02に公開

はじめに

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

それぞれのファイルの中身は以下の通り。

a.c
#include "lib.h"

int main(void) {
    return ANSWER;
}
lib.h
#define ANSWER 42
Makefile
CC=gcc

program: a.o
	$(CC) -o $@ $^ $(LDFLAGS)

.PHONY: clean

clean:
	rm -f program *.o

このディレクトリでmakeするとa.oprogramができる。

$ make
gcc    -c -o a.o a.c
gcc -o program a.o 

このような状況で、lib.hの内容が変わったとき、makea.cが再度コンパイルされるようにしたい。それだけであればa.o: lib.hのようなルールを書けば実現できるのだが、この方法では将来ソースファイルやヘッダファイルがもっとたくさんになったときに、それらの依存関係が常に正しく反映されるようにMakefileを更新し続けなければならない。もっと楽に、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するとかはやってはいけない。

脚注
  1. これはEmpty RecipesRules without Recipes or Prerequisitesの扱いだと思うが正直よくわからない。 ↩︎

Discussion