Makefile 学習記録

某学校でC言語から勉強し始めて早1年、、今更ながら現在使用しているMakefileを紹介してみます。
2024.2.14 修正・追加しました。
```Makefile
NAME = test
CXX = c++
CXXFLAGS = -Wall -Wextra -Werror -std=c++98
SRCS =  main.cpp test1.cpp test2.cpp hoge2.cpp
DEPFILES = $(OBJS:%.o=%.d)
OBJS_DIR = objs
VPATH = ./src2
OBJS = ${addprefix $(OBJS_DIR)/,$(SRCS:.cpp=.o)}
RM = rm -f
.DEFAULT_GOAL :=$(NAME)
all: $(NAME)
$(NAME): $(OBJS)
	$(CXX) $(CXXFLAGS) -o $(NAME) $(OBJS)
$(OBJS_DIR)/%.o: %.cpp | $(OBJS_DIR)
	$(CXX) $(CXXFLAGS) -c -MMD -MP $< -I $(INCLUDE_DIR) -o $@
$(OBJS_DIR):
	mkdir $(OBJS_DIR)
clean:
	$(RM) $(OBJS)

fclean: clean
	$(RM) $(NAME)

re: fclean all

.PHONY: all clean fclean re

-include $(DEPFILES)
```

## 1. ソースファイルが複階層にあるとき
例えば下記のような場合
![スクリーンショット 2024-02-14 22.35.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840848/4c835b3c-d254-b63e-972a-6899fc540c66.png)

```VPATH = ./src2```は、カレントディレクトリにソースファイルが存在しない場合に、探すディレクトリのリストを表す。複数ディレクトリを指定する場合は、空白かコロンで区切る。
## 2. .oファイルをobjsディレクトリに展開する
下記のように、生成された.oファイルは専用のディレクトリに展開した方が見やすい。
![スクリーンショット 2024-02-14 22.31.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840848/0dc75d41-f4cd-be05-670c-ce482c2d215b.png)

```Makefile
OBJS = ${addprefix $(OBJS_DIR)/,$(SRCS:.cpp=.o)}
```
addprefixを使用し、ソースファイルの前にobjs/をつけることで、ソースファイルから生成されるオブジェクトファイルのリストを生成する。ex:) main.cpp -> objs/main.cpp
```Makefile
$(OBJS_DIR)/%.o: %.cpp | $(OBJS_DIR)
	$(CXX) $(CXXFLAGS) -c $< -o $@
```
```| $(OBJS_DIR)```で、ターゲットファイルである```$(OBJS_DIR)/%.o```が生成される前に存在しているか判定。存在しない場合は、先に```$(OBJS_DIR):
	mkdir $(OBJS_DIR)```を実行。
```$<```は最初の依存ファイル（.cppファイル）を表し、```$@```はターゲットファイル（.oファイル）を表す。```$<```でコンパイル対象のソースファイルを指定し、```-c```オプションでコンパイルのみを実行。```-o```オプションで生成されるオブジェクトファイル名（.oファイル）を指定する。
## 3. makeと打った時に、必ずmake allが実行されるようにする
Makefileは、最初に定義されるターゲットがデフォルトのターゲットとなる。例えば、下記の場合でmakeと打つと、cleanが実行されてしまう。
```Makefile
NAME = test
CXX = c++
CXXFLAGS = -Wall -Wextra -Werror -std=c++98
SRCS =  main.cpp test1.cpp test2.cpp hoge2.cpp
DEPFILES = $(OBJS:%.o=%.d)
OBJS_DIR = objs
VPATH = ./src2
OBJS = ${addprefix $(OBJS_DIR)/,$(SRCS:.cpp=.o)}
RM = rm -f
#allとcleanの順番を入れ替えた⇩
clean:
	$(RM) $(OBJS)
all: $(NAME)
$(NAME): $(OBJS)
	$(CXX) $(CXXFLAGS) -o $(NAME) $(OBJS)
$(OBJS_DIR)/%.o: %.cpp | $(OBJS_DIR)
	$(CXX) $(CXXFLAGS) -c $< -o $@
$(OBJS_DIR):
	mkdir $(OBJS_DIR)

fclean: clean
	$(RM) $(NAME)

re: fclean all

.PHONY: all clean fclean re
```
```.DEFAULT_GOAL :=$(NAME)```でデフォルトのターゲットを指定すると、```$(NAME)```に対応するルールが実行される。

## 4.ヘッダーファイルに変更があった場合も再コンパイルされる
```Makefile
DEPFILES = $(OBJS:%.o=%.d)
```
.oファイルは、コンパイル時にソースファイルから機械語やアセンブリ言語に変換されたコード等が格納されている。.dファイルはファイルの依存関係を含むファイルのこと。再コンパイルが必要なファイルを正確に特定するのに役立つ。
```Makefile
$(OBJS_DIR)/%.o: %.cpp | $(OBJS_DIR)
	$(CXX) $(CXXFLAGS) -c -MMD -MP $< -I $(INCLUDE_DIR) -o $@
```
コンパイラ・オプションの-MMD, -MPを使用することで、ファイルの依存関係を把握し、コンパイラがソースファイルから生成される依存関係を.dファイルに書き出す。オプション-Iは、コンパイラにヘッダファイル$(INCLUDE_DIR)の検索パスを指示するために使用される。
```Makefile
-include $(DEPFILES)
```
$(DEPFILES)がMakefileにインクルードされる。例えば、test1.hppに変更があった場合は、test1.oが再コンパイルされる。
参考
・[GNU Make 再コンパイル作業を制御するプログラム GNU make Version 3.77 May 1998](https://www.ecoop.net/coop/translated/GNUMake3.77/make_4.jp.html#SEC26)
・[4.3 Types of Prerequisites](https://www.gnu.org/software/make/manual/make.html#Prerequisite-Types)
・https://quruli.ivory.ne.jp/document/make_3.79.1/make-jp_0.html#Overview
・[GCCコマンド・オプション](https://www.asahi-net.or.jp/~wg5k-ickw/html/online/gcc-2.95.2/gcc_2.html#SEC11)