Makefile で同じようなコマンドのターゲット群をまとめる方法

# tl;dr
- [Multiple Targets](https://www.gnu.org/software/make/manual/make.html#Multiple-Targets) 使うと、複数ターゲットを1つのターゲットで表現できて便利
- 複数ターゲットがほぼ同じコマンドで構成されるときに使えて便利
- Makefileは奥が深い
# はじめに

ほぼ同じコマンドで、パスだけ異なるターゲットを作成することがあり、そのときに知ったMakefileの機能を紹介します。
コードは以下においています。

https://github.com/haru-256/blog-makefile-multiple-targets-20240501

# Makefileで複数ターゲットを1ターゲットで表現する方法

Golangでは、`cmd/` ディレクトリ配下に実行エンドポイントを複数置くことがあります。例えば、Web APIサーバーを開発する場合、APIサーバーの実行エンドポイント(`cmd/api/main.go`)と、APIサーバーが参照するDBのマイグレーションツール (`cmd/tools/main.go`) を置くことがあります。

```sh
.
├── Makefile
├── cmd/
│   ├── api/  # API サーバー実行エンドポイント
│   └── tools/  # CLI用の実行エンドポイント
└── go.mod
```

このとき、それぞれのコマンドのビルドコマンドを素直にMakefileに書くと、以下のようになります。見ての通り、ターゲット `api`と`tools`それぞれはほぼ同じコマンドであり、パス名のみ異なります。今後、コマンドの種類が増えると同じようなコマンドを持つターゲットを繰り返すことになり大変です。

```Makefile
ifndef GOARCH
	GOARCH=$(shell go env GOARCH)
endif

ifndef GOOS
	GOOS := $(shell go env GOOS)
endif

.PHONY: build-api
build-api:
	CGO_ENABLED=0 GO111MODULE=on GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o ./bin/api cmd/api/main.go

.PHONY: build-tools
build-tools:
	CGO_ENABLED=0 GO111MODULE=on GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o ./bin/tools cmd/tools/main.go

```

引数を受け取る`_build`ターゲットを作り、そこに環境変数や引数でパス名を渡すのもよいのですが、このような場合に使えるMakefileの機能があります。それが複数ターゲット機能です。
-  [Multiple Targets](https://www.gnu.org/software/make/manual/make.html#Multiple-Targets)

こちらの機能を使い、汎用的なターゲットを作ると以下のようなMakefileとなります。こちらのMakefileにより`cmd`配下のコマンドが増えても1つのターゲットで対応できます。

```Makefile
ifndef GOARCH
	GOARCH=$(shell go env GOARCH)
endif

ifndef GOOS
	GOOS := $(shell go env GOOS)
endif

COMMAND_DIRS := $(wildcard cmd/*)
BUILD_TARGETS := $(addprefix build-,$(notdir $(COMMAND_DIRS)))

.PHONY: $(BUILD_TARGETS)
$(BUILD_TARGETS): build-%:
	CGO_ENABLED=0 GO111MODULE=on GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o ./bin/$* -mod=vendor cmd/$*/main.go

```

上記Makefileで使用している機能も補足説明しておきます。

| 項目                           | 説明内容                                                                                                                                                           | 公式ページ                                                                                                                         |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `wildcard`                   | (wildcard pattern)` という書き方で、パターンを展開したリストを返します。例えば、上記では `$(wildcard cmd/*)` = `cmd/api cmd/tools`となります                                                          | [Wildcard Function (GNU make)](https://www.gnu.org/software/make/manual/html_node/Wildcard-Function.html)                     |
| `notdir`                     | `$(notdir paths…)` という書き方でpathの親ディレクトリ部分は取り除いた結果を返します。例えば、`$(notdir /hoge/foo hoge.c)` は `foo hoge.c` を返します。                                                   | [File Name Functions (GNU make)](https://www.gnu.org/software/make/manual/html_node/File-Name-Functions.html)                 |
| `addprefix`                  | `$(addprefix prefix,names...)` という書き方で、namesには空白区切りのリストが与えられます。与えられたリストの各要素に、`prefix`という文字列が連結されます。 例えば、`$(addprefix src/,foo bar)` は `src/foo src/bar` となります。 | [File Name Functions (GNU make)](https://www.gnu.org/software/make/manual/html_node/File-Name-Functions.html#index-addprefix) |
| `$(BUILD_TARGETS): build-%:` | これは静的ルールと呼ばれるもので、`$(BUILD_TARGETS)` というターゲットリストにのみ、 `build-%` というルールを適用します。つまり、`$(BUILD_TARGETS)` にないような `build-hoge` などはターゲットになりません。                          | [Static Pattern Rules](https://www.gnu.org/software/make/manual/make.html#Static-Pattern)                                     |
| `%`                          | pattern ruleと呼ばれるもので、任意の一文字以上の文字にマッチするもの(stem)を表しています。例えば、上記では `build-api`や`build-tools`がマッチします。                                                               | [Pattern Intro (GNU make)](https://www.gnu.org/software/make/manual/html_node/Pattern-Intro.html)                             |
| `$*`                         | pattern ruleでマッチした部分を表す文字列(stem)に置換されます。例えば、`build-api`であれば、`$*` = `api` となります。                                                                                | [Automatic Variables (GNU make)](https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html)                 |

# 小話
上記は、動的にターゲットが決まるため、どういうターゲットが存在するのかがパット見では分かりづらいのが悪い点です。
ただ、fish shell や CodeWhisperer(旧 fig) ではターゲットを解析してくれてちゃんと補完してくれるようです。

![fig1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200359/22cd69b7-6e94-7cc6-73f6-554ea0eca8c7.png)


- [CodeWhisperer for command line - CodeWhisperer](https://docs.aws.amazon.com/codewhisperer/latest/userguide/command-line.html)

# まとめ
ほぼ同じコマンドで、パスだけ異なるターゲットを作成するときに使える機能を紹介しました。
他にもMakefileが内蔵している機能は多くあるようなので、一度Makefileの公式ドキュメントを読んでみるのも面白いかもしれません。
# 参考
- [- 自動化のためのGNU Make入門講座 - Makefileの基本：ルール](https://objectclub.jp/community/memorial/homepage3.nifty.com/masarl/article/gnu-make/rule.html)
- [Make覚書 #Makefile - Qiita](https://qiita.com/Ro3jin/items/27170827707e5136ee89)