Makefileでのヘッダファイルの依存関係の追加

問題設定

まず以下の4つのファイルを作る

/* main.c */
#include "foo.h"

int main(){
	foo(10);
	return 0;
}

/* foo.h */
void foo(int a);

/* foo.c */
#include "foo.h"
#include <stdio.h>

void foo(int a){
	printf("%d\n", a);
}
#Makefile
PROG = myapp
SRCS = main.c foo.c
OBJS = $(SRCS:%.c=%.o)

CC = gcc

all: $(PROG)

$(PROG): $(OBJS)
	$(CC) -o $@ $^

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

これでmakeする

$ make
gcc -c main.c
gcc -c foo.c
gcc -o myapp main.o foo.o
$ ./myapp
10

このように問題なく実行できる.ここでfoo.hに変更を加えた場合を想定する.

$ touch foo.h

touchコマンドは空ファイルを作るだけでなく,ファイルの中身を変更の有無にかかわらずファイルの更新時間を現在時刻に変えられる.
この状態でもう一度makeすると,main.cとfoo.cはfoo.hをインクルードしているので,それらのファイルに対し,もう一度コンパイルをしてほしい.しかし,もうmakeし直すと,

$ make
make: Nothing to be done for 'all'.

と出力され,何も起こらない.この原因はMakefile内にヘッダファイルの依存関係が書かれていないことにある.

解決案

この問題を解決ための,愚直な方法はMakefileの最終行にでも

main.o: main.c foo.h
foo.o: foo.c foo.h

と書き込む方法だがファイル数が多くなると面倒.なので別の方法を考える.

MMDオプション

MMDオプションをつけると,各ファイルのコンパイル時に,#include文でダブルコーテーションで囲まれたヘッダファイルを.dファイルに出力してくれる.
(例)先ほどのMakefileを編集する.

#Makefile
PROG = myapp
SRCS = main.c foo.c
OBJS = $(SRCS:%.c=%.o)

CC = gcc

all: $(PROG)

$(PROG): $(OBJS)
	$(CC) -o $@ $^

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

これでmakeしてみる.(その前に,先ほど作られたオブジェクトファイルなどを消去する.)

$ rm *.o myapp
$ ls
foo.c  foo.h  main.c  Makefile
$ make
gcc -c -MMD main.c
gcc -c -MMD foo.c
gcc -o myapp main.o foo.o
$ ls
foo.c  foo.d  foo.h  foo.o  main.c  main.d  main.o  Makefile  myapp

.dのファイルが作られてるのが分かる.中身を見てみると

$ cat main.d
main.o: main.c foo.h
$ cat foo.d
foo.o: foo.c foo.h

のように,依存ファイルが得られる.

includeオプション

つぎは,得られた依存ファイルの情報をMakefileに追加する.それにはincludeオプションを使う.

#Makefile
PROG = myapp
SRCS = main.c foo.c
OBJS = $(SRCS:%.c=%.o)
DEPS := $(SRCS:%.c=%.d)

CC = gcc

all: $(PROG)

$(PROG): $(OBJS)
	$(CC) -o $@ $^

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

-include $(DEPS)

.dファイルがない場合(1回目でのmake)では,.dファイルがないというエラーが出てmakeが止まってしまうのでincludeの先頭にハイフンをつける(ハイフンをつけるとファイルがなくてもエラーが出なくなる)

補足1

includeオプションは,今のmakefileの処理を止めて,指定した別のmakefileの処理を行うというものらしい.上の例では,

-include $(DEPS)

の部分は

main.o: main.c foo.h
foo.o: foo.c foo.h

と解釈される.なので,.dファイルもmakefileなのだろう(makefileの文法で書かれてる).また一番下にincludeを書いても真ん中の処理をする前にincludeの処理をするみたい.

補足2

includeオプションは,allよりも上に書いてはいけない.makeというコマンドは一番上のターゲットに対する処理を行うため,ターゲットが変ってしまう.今までの例で

-include $(DEPS)
all: $(PROG)

としてしまうと,

main.o: main.c foo.h
foo.o: foo.c foo.h
all: $(PROG)

となり,main.oを作ることが目的となってしまう.