普段はVisual StudioなどのIDEを使ってるんですが、場合によってはgcc+makefileを使うこともあります。
特に組み込み向けプログラミングとか。
そんなとき、『あれっ!?Makefileってどう書くんだっけ???』となることがあるので、汎用的に使えるMakefileを作ってみました。
Makefileで実現したいこと
まず、Makefileで実現したいことをまとめておきます。
- 実行ファイル名はフォルダ名からとってきてくれる
- ソースディレクトリ下のファイルを自動的に列挙してくれる
- ファイルの依存関係を考慮しつつ、必要最小限のソースをコンパイルしてくれる
IDEなら全部自動でやってくれることですね。 ここまでやっておけば、どんなプロジェクトでもこのMakefileをそのまま使えるようになりそうです。
Makefileのサンプル
ということでMakefileのサンプルです。
CC = gcc CFLAGS = -g -MMD -MP LDFLAGS = LIBS = INCLUDE = -I ./include SRC_ROOT = ./src OBJ_ROOT = ./obj TARGET_DIR = ./bin TARGET_NAME = $(notdir $(shell pwd)).exe SRC_DIRS = $(shell find $(SRC_ROOT) -type d) OBJ_DIRS = $(addprefix $(OBJROOT)/, $(SRC_DIRS)) SRCS = $(foreach dir, $(SRC_DIRS), $(wildcard $(dir)/*.c)) OBJS = $(addprefix $(OBJ_ROOT)/, $(SRCS:.c=.o)) DEPENDS = $(patsubst %.o,%.d, $(OBJS)) TARGET = $(TARGET_DIR)/$(TARGET_NAME) all:$(TARGET) $(TARGET):$(OBJS) @mkdir -p $(dir $@) $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ $(LIBS) $(OBJ_ROOT)/%.o: %.c @mkdir -p $(dir $@) $(CC) $(CFLAGS) -c $< -o $@ clean: $(RM) -rf $(OBJ_ROOT) $(TARGET_DIR) .PHONY: all clean ifneq ($(MAKECMDGOALS),clean) -include $(DEPENDS) endif
解説
では解説します。
実行ファイル名は以下のコードで自動的に設定します。
TARGET_NAME = $(notdir $(shell pwd)).exe
makeを実行すると、srcディレクトリ下の.cをサブディレクトリも含めて全てコンパイル対象として列挙します。他の拡張子(.cppとか.asmとか)も対象に加えたい場合はSRCSに追加してください。
また、.exeはobjディレクトリに、.objはobj/src下に生成します。
ファイルの依存関係は、gccの -MMD -MPオプションで生成します。
CFLAGS = -g -MMD -MP
このオプションは、コンパイルと同時にファイルの依存関係を.dファイルとして生成してくれるとても便利なオプションです。.dファイルは.objと同じディレクトリに生成されます。
-MMD -MPオプションで生成した.dファイルは、
ifneq ($(MAKECMDGOALS),clean) -include $(DEPENDS) endif
で読み込みます。
ここのポイントは、MAKECMDGOALSと-(マイナス)です。
MAKECMDGOALSにはコマンドラインで与えて指定されたゴールのリストが入ります。例えば、make cleanを実行した場合は、MAKECMDGOALSにcleanが格納されます。
つまり、上記のコードはmake clean時には.dファイルを読み込まないという意味です。もしifneq~endifがなかったらどうなるかというと、clean実行前に.dファイルを作りに行ってしまいます。無駄ですね。
次に-(マイナス)ですが、これはエラーが発生してもmakeを中断しないという意味です。一番最初のビルド時など、.dファイルが無いのでincludeできずにエラーが発生してしまいます。これでmakeを中断されたら一生ビルドできませんからね。
改良ポイント
関連するライブラリが更新されたときにビルドが走るようにしたらもっと便利になりそうですね。