Makefile の冗長さを減らす define, call, eval, foreach (マクロ)

# 前記事
https://qiita.com/skkzsh/items/08ec6d2fd151ebc6d079

:::note
前記事の続きという位置付けですが, 単独で読めると思います.
:::

# はじめに
複数ファイルで使うアルゴリズムやデータ構造は, 共通のソースファイルとヘッダファイルに切り出すと良い.

例えば `union-find.(cpp|hpp)` が `code_11_%.cpp` と `solution_11_%.cpp` から使われるときの `Makefile` は以下のように書ける.
しかし,  `code_11_%` と `solution_11_%` で同じようなルールを定義している部分が冗長である.

```makefile
CXX := g++
CXX_BASE_FLAGS := -Wall -Wextra -std=c++20
CXXFLAGS := $(CXX_BASE_FLAGS) -lgtest_main -lgtest -lpthread

UNION_FIND_SRC := union-find.cpp
UNION_FIND_OBJ := $(UNION_FIND_SRC:.cpp=.o)
UNION_FIND_HDR := $(UNION_FIND_SRC:.cpp=.hpp)

TARGETS := $(basename $(wildcard *_[1-9]*_[1-9]*.cpp)) # ワイルドカードは適宜変更

.PHONY: clean

all: $(TARGETS)

# ↓ union-findを使うためのルールここから ↓
code_11_%: code_11_%.o $(UNION_FIND_OBJ)
	$(CXX) $(CXXFLAGS) $^ -o $@
solution_11_%: solution_11_%.o $(UNION_FIND_OBJ)
	$(CXX) $(CXXFLAGS) $^ -o $@

code_11_%.o: code_11_%.cpp $(UNION_FIND_HDR)
solution_11_%.o: solution_11_%.cpp $(UNION_FIND_HDR)
# ↑ union-findを使うためのルールここまで ↑

$(UNION_FIND_OBJ): $(UNION_FIND_SRC) $(UNION_FIND_HDR)

.cpp.o:
	$(CXX) $(CXX_BASE_FLAGS) -c $<

clean:
	$(RM) $(TARGETS) $(UNION_FIND_OBJ)
```

# 冗長さを減らす
そこで, 以下のように`define`, [`call`](https://www.gnu.org/software/make/manual/html_node/Call-Function.html), [`eval`](https://www.gnu.org/software/make/manual/html_node/Eval-Function.html), [`foreach`](https://www.gnu.org/software/make/manual/html_node/Foreach-Function.html) を使いマクロで冗長さを減らせる. [^1] [^2]

[^1]: けど読みづらい…ので他に良い書き方があれば募集してます. 
[^2]: `$$` で `$` をエスケープできる

```makefile
CXX := g++
CXX_BASE_FLAGS := -Wall -Wextra -std=c++20
CXXFLAGS := $(CXX_BASE_FLAGS) -lgtest_main -lgtest -lpthread

UNION_FIND_SRC := union-find.cpp
UNION_FIND_OBJ := $(UNION_FIND_SRC:.cpp=.o)
UNION_FIND_HDR := $(UNION_FIND_SRC:.cpp=.hpp)

TARGETS := $(basename $(wildcard *_[1-9]*_[1-9]*.cpp)) # ワイルドカードは適宜変更
TARGETS_WITH_UNION_FIND := code_11_% solution_11_%  # ← ★追加

.PHONY: clean

all: $(TARGETS)

# ↓★書き換えココから↓
define CXX_WITH_UNION_FIND
$(1): $(1).o $(UNION_FIND_OBJ)
	$$(CXX) $$(CXXFLAGS) $$^ -o $$@

$(1).o: $(1).cpp $(UNION_FIND_HDR)
endef

$(foreach T, $(TARGETS_WITH_UNION_FIND), $(eval $(call CXX_WITH_UNION_FIND, $(T))))
# ↑★書き換えココまで↑

$(UNION_FIND_OBJ): $(UNION_FIND_SRC) $(UNION_FIND_HDR)

.cpp.o:
	$(CXX) $(CXX_BASE_FLAGS) -c $<

clean:
	$(RM) $(TARGETS) $(UNION_FIND_OBJ)
```

# 確認
`make -np` すると, 以下のように期待どおりにルールが展開されている.

```console:抜粋
$ make -np
.
.
.
code_11_%: code_11_%.o union-find.o
#  commands to execute (from `Makefile', line 22):
    $(CXX) $(CXXFLAGS) $^ -o $@
    

code_11_%.o: code_11_%.cpp union-find.hpp

solution_11_%: solution_11_%.o union-find.o
#  commands to execute (from `Makefile', line 22):
    $(CXX) $(CXXFLAGS) $^ -o $@
    

solution_11_%.o: solution_11_%.cpp union-find.hpp
.
.
.
```

`make` すると, 以下のように期待どおりにコンパイルされる.

```console:出力例
$ make
g++ -Wall -Wextra -std=c++20 -c union-find.cpp
g++ -Wall -Wextra -std=c++20 -c code_11_03.cpp
g++ -Wall -Wextra -std=c++20 -lgtest_main -lgtest -lpthread code_11_03.o union-find.o -o code_11_03
g++ -Wall -Wextra -std=c++20 -c solution_11_01.cpp
g++ -Wall -Wextra -std=c++20 -lgtest_main -lgtest -lpthread solution_11_01.o union-find.o -o solution_11_01
rm code_11_03.o solution_11_01.o
```