普段、自分でMakefileを書くことはほとんど無い。あったとしても、ちょっとしたプログラムでソースファイルとして数個のCソースがあるようなもの。なので、Makefileの知識は、ターゲットと依存関係を : で区切って書くとか、変数が使えて、その参照は$(~)でやるとか、そういうぐらいしかなかった。
ところが、ある程度の規模があるプログラムを作成することになり、ちょっと自分流Makefileというものを、ちゃんと作っておこうと思い立つ。一回作っておけば、使いまわしできるからね。
こんなことができれば・・・・という自分の要求は、以下のとおり。
- 中間ファイル(オブジェクトファイル)は、ソースとは別のディレクトリに吐き出させたい。ただし、ここで、元となるソースは複数のディレクトリ内に散在している。
- ソースからインクルードしているヘッダなどの依存関係も、ちゃんとなんとかしてほしい
- ビルドは、最速である必要はないが、モッサリしているのは嫌
- 新しいソースを追加したとき、いろんなところを書き換えるのは嫌
適当にググレば、そんなMakefileのサンプルがわんさか出てきて、コピペで終了と思ったが、甘かった。1.と2.を同時に実現する方法がなかなか見つからない・・・。
ということで、Makefileの仕様を勉強しながら、自分流Makefileをつくる。
例題
例として次のようなディレクトリ・ファイル構成を考える。
/ | Makefile / main.c / ValDef.h / Funcs.h |- SubDir1 | | func.c | |- SubSub | func.c |- SubDir2 | func.c |- Build 中間ファイルはここに吐き出させたい
作業スペースのルートに Makefile と Cコード(main.c)とヘッダファイル。そしてサブのディレクトリに、複数のCコード(func.c)が存在している状態。複数のディレクトリに同じファイル名のCファイルがあるのが曲者だ。既存のソースの流用などをしたいとき、同じファイル名のcファイルが複数存在することはありうる。
こういうMakefileを作った
SRCS = \ main.c \ SubDir1/func.c \ SubDir1/SubSub/func.c \ SubDir2/func.c INC_DIR = . BUILD_DIR = Build OBJS=$(addprefix $(BUILD_DIR)/,$(patsubst %.c,%.o,$(SRCS))) DEPS=$(patsubst %.o,%.d, $(OBJS)) CC = gcc TARGET = TestProg.exe CFLAGS += $(addprefix -I,$(INC_DIR)) LDFLAGS += all: $(BUILD_DIR) $(TARGET) $(BUILD_DIR): mkdir -p $(BUILD_DIR) $(TARGET) : $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(BUILD_DIR)/%.o : %.c mkdir -p $(dir $@); \ $(CC) -c $(CFLAGS) -o $@ $< $(BUILD_DIR)/%.d : %.c mkdir -p $(dir $@); \ $(CC) -MM $(CFLAGS) $< \ | sed 's/$(notdir $*).o/$(subst /,\/,$(patsubst %.d,%.o,$@) $@)/' > $@ ; \ [ -s $@ ] || rm -f $@ clean: rm -rf $(BUILD_DIR) $(TARGET) .PHONY: all clean -include $(DEPS)
上記のMakefileを、Buildディレクトリがまだできていない状態から実行したときに何が行われるのか、ちゃんと理解しようとすると、以下のとおり。
- makeは41行目のinclude $(DEPS)にしたがって、Build/main.d や、Build/SubDir1/func.d などのファイルをincludeしようとするが、これらファイルが 存在していないことに気づく。ゆえに、まずは ~.d ファイルがターゲットとなっているルールを見つける。
- ~.dを生成するルールは31~35行目にある。
例えば、Build/SubDir1/func.d は、$(BUILD_DIR)/%.d にマッチする。このとき、%の部分(stemと言うらしい)は、SubDir1/func なので、make は、Build/SubDir1/func.d が依存するファイルが、 SubDir1/func.c であると認識する。 - fund.dを吐き出すための Build/SubDir1 ディレクトリを作成する。(32行目)
- gcc -MM オプションを使って func.c がincludeしているヘッダの一覧を標準出力に出力させる。ここで、gccの出力は、以下のフォーマットになる。
func.o: SubDir1/func.c Funcs.h
このままでは、次の理由でNGである。
- .oファイルの相対パスが書かれていない。これでは、Func.h を書き換えても、Build/SubDir1/func.o を新たに生成する必要があると、makeは思わない。
- .h ファイルに新しいincludeを追加したときに、.dファイルが新しく生成されない。
そこでsedを使って、gccの出力を以下のようになるように書き換える。
Build/SubDir1/func.o Build/SubDir1/func.d: SubDir1/func.c Funcs.h
さらに、gcc -MMが失敗して、空の .d ファイルができてしまった場合は、その .d ファイルを削除するようにした。
- .dが出来上がるとincludeが成功するので、次にmakeは$(TARGET)の依存ファイルである各種.oファイルを生成しようとする
- そのルールは27-30行目に書いてあるので、それに従い、.oを作成
- .oができると24行目のルールで最終生成物 TARGET が出来上がる
・・・なんとか、できました。
最近のコメント