大規模なC言語プログラムでは、ソースやヘッダファイルが複雑な依存関係を持つため、それらを自動解決してくれるMakefileが欲しくなる。 欲望を具体化すると
- ソース、ヘッダ、オブジェクトファイルは異なるディレクトリに入れたい
- 依存関係を認識し、更新すべきオブジェクトファイルを自動検出
- 新しいソース、ヘッダファイルを作成した際、Makefileを書き換ずに済む
- 他のプロジェクトにも使いまわせる汎用性
- Makefile自体が短く、保守しやすい
だいぶ贅沢だが、頑張ってMakefileをこしらえたので、得られた知見を忘れないうちにまとめておく。 以前書いたMakefile文法ミニマムも参考にしてくれ。
ディレクトリ構成
root +----src/ | +---- main.c # mA.h を読み込む | +---- A.c # mA.h と A.h を読み込む | +----inc/ | +---- mA.h # 依存なし | +---- A.h # B.h を読み込む | +---- B.h # 依存なし | +----obj/ | +---- main.o # makeコマンドで作成される | +---- A.o # makeコマンドで作成される | +---- Makefile # *.o を作成 | +---- exec.out # makeコマンドで作成される +---- Makefile # exec.out を作成
*.c *.h *.o はそれぞれ別ディレクトリで管理する。 もちろん依存関係を考慮して
- A.c が更新されれば、A.o を更新
- mA.h が更新されれば、main.o と A.o を更新
- B.h が更新されれば、A.o を更新
といった具合でオブジェクトファイルを作り直し、exec.out に再リンクしたいのだ。
Makefile
この状況に対処するには、まず root/Makefile を作る
# root/Makefile CC = gcc ALL_C = $(wildcard src/*.c) # src/main.c src/A.c ALL_O = $(patsubst src/%.c,obj/%.o,$(ALL_C)) # obj/main.o obj/A.o ALL_CH = $(wildcard src/*.c inc/*.h) # src/*.c inc/*.h exec.out: $(ALL_CH) cd obj && $(MAKE) "CC=$(CC)" # obj/Makefile を実行する (ALL_Oが作成される) $(CC) $(ALL_O) -o $@ .PHONY: clean clean: @rm -rf *.out obj/*.o obj/*.d
このファイルの処理内容は Makefile文法ミニマム を見れば理解できるだろう。
要するにソースやヘッダファイルに更新があれば exec.out
をリビルドするのだが、
更新すべきオブジェクトファイルの検出と再コンパイルは root/obj/Makefileに丸投げしている。
この root/obj/Makefile は
# root/obj/Makefile ALL_C = $(wildcard ../src/*.c) # ../src/main.c ../src/A.c ALL_O = $(patsubst ../src/%.c,%.o,$(ALL_C)) # main.o A.o ALL_D = $(patsubst ../src/%.c,%.d,$(ALL_C)) # main.d A.d ALL_CH = $(wildcard ../src/*.c ../inc/*.h) # ../src/*.c ../inc/*.h .PHONY: dummy # dummy というファイルは作成されないので PHONY 指定 dummy: $(ALL_O) # 実行するには ALL_O が必要 --> 下の %.o:... で作成 %.o: ../src/%.c %.d # A.o が必要な際は A.o: ../src/A.c A.d として実行 $(CC) -c $< -o $@ %.d: ../src/%.c $(ALL_CH) # 下の -include 命令から呼ばれる cpp -MM $< -MF $@ # A.c の依存関係をMakefile形式で書いた A.d を生成 -include $(ALL_D) # main.d と A.d を読み込む --> 上の %.d:... で作成
ファイルの冒頭の ALL_C
と ALL_O
は意味がわかると思うが、ALL_D
については説明が必要だろう。
実は gcc にはプリプロセッサの cpp が含まれており、
$ cpp -MM hoge.c -MF hoge.d
とすることで hoge.c が参照している他のソースやヘッダファイルの情報を Makefile 形式で取得できる。
こうして得られた *.d をファイル末尾の -include $(ALL_D)
により読み込んでいるわけだ。
*.d を読み込む Makefile は *.d と同じディレクトリに設置する必要があるため、
root/Makefile から root/obj/Makefile を分離した。