Makefile で第2引数を使う方法

Makefile で任意の第2引数を扱えるようにする小技を紹介します。
簡単な表記で2つの引数を扱える上に複数個の対象をデフォルト指定したり変更することもできます。
この方法を使えば `--jobs` オプションで `make` タスクの並列化もできます。 
本記事で紹介するスクリプトは gist で公開しています。
https://gist.github.com/algas/377ca9d3f11916af39951ccd599a3b88

## 解決した課題

1. make で2つの引数(1つ固定＋1つ任意)を扱えるようにした
2. 第2引数を複数個デフォルト設定かつ変更可能にした
3. 上記の2つに加えて jobs オプションでの make タスク並列化が効く形式を保った

## 対象読者
- Makefile を書く必要があるけどイマイチ書き方がよくわからない人
- シェルスクリプトの方が自由度が高いと思いつつ Makefile を書いている人

## Makefileの不自由なところ

Makefile について挙げられる課題の1つに引数の自由度の低さがあります。
Makefile では通常はターゲットと呼ばれる1階層だけ書くことができます。
つまり `make` コマンドの引数(サブコマンドとも呼ぶ)は1階層だけ使えます。
たとえば次のように書きます。
`make foo`
複数の引数を `make` に使った場合にはコマンドを連続実行するものと解釈されます。
`make foo bar`
これは次のコマンドと同じように解釈されます。
`make foo && make bar`
コマンドの用途によっては第2引数まで使いたい場合があります。
たとえば `FILEPATH` を git の管理下に置きたい場合には次のコマンドを使います。
`git add FILEPATH`
それでは `make` で上記と同等のコマンド体系を実現するにはどうすればいいでしょうか？

## 一般的な手法

一般的に第2引数を使われる手法を紹介します。
これらで十分な場合もあります。

### ハードコーディングする

対応する第2引数が少ない場合にはハードコーディングする方法が
使えます。
第2引数の種類が多かったり任意の個数がある場合には使えません。

```makefile
command-foo:
	@echo foo
command-bar:
	@echo bar
```

```bash
$ make command-foo
foo

$ make command-bar
bar

```

### make変数や環境変数を使う

make変数や環境変数を Makefile 内で呼び出して使います。
ここでは ARG という名前の変数を使います。

```makefile
command:
	@echo "command $(ARG)"
```

```bash
$ make command ARG=baz
command baz
```

## この記事で提案する手法

`make TARGET-SUBTARGET` というコマンド体系が使えます。
TARGET は固定、SUBTARGET は任意または有限個から選択する場合に使います。

### Makefile 本体

```makefile
NAMES = foo bar baz

hello: $(addprefix hello-, $(NAMES))

hello-%:
	@echo "hello ${@:hello-%=%}"

world: $(addprefix world-, $(NAMES))

world-%:
	@echo "${@:hello-%=%} world"

.PHONY: hello hello-% world world-%
```

### コマンドの実行結果

```bash
$ make hello-foo
hello foo

$ make hello-hoge
hello hoge

$ make hello
hello foo
hello bar
hello baz

$ make --jobs 2 hello
hello bar
hello foo
hello baz

$ make hello NAMES=hoge fuga
hello hoge
hello fuga

$ make world-bar
bar world

$ make world-123
123 world

$ make world
foo world
bar world
baz world
```

### Makefile の解説

まずはこの部分から。

```makefile
hello-%:
	@echo "hello ${@:hello-%=%}"
```

Makefile で `%` は wildcard つまり任意の文字列を渡せます。
`hello-%:` は `make hello-*` でコマンドを実行できることを表しています。
`@echo` は Makefile で使う特殊な `echo` コマンドの表現で echo の実行コマンド群を表示しないで echo の対象のみを表示します。
`$@` はターゲットを表す変数です。つまりここでは `hello-%` を意味します。
`${@:hello-%=%}` は `$@` から先頭の `hello-` を除去した文字列になります。
たとえば `make hello-foo` は `@echo "hello foo"` と解釈されるので `hello foo` がコンソールに表示されます。

```makefile
NAMES = foo bar baz
hello: $(addprefix hello-, $(NAMES))
```

`NAMES` という変数には `foo`, `bar`, `baz` の3つの文字列がデフォルトで入っています。
`$(addprefix PREFIX, $(STRINGS))` は `STRINGS` に含まれる各文字列の先頭に `PREFIX` を加える関数です。つまりここでは `NAMES` がデフォルトの場合 `hello-foo`, `hello-bar`, `hello-baz` となります。
`hello:` の行を展開すると次のように書けます。

```makefile
hello: hello-foo hello-bar hello-baz
```

`make hello` を実行すると `make hello-foo`, `make hello-bar`, `make hello-baz` がそれぞれ実行されます。

`NAMES` 引数は実行時に置き換えることができます。
`make hello NAMES="hoge fuga"` を実行すれば `make hello-hoge`, `make hello-fuga` がそれぞれ実行されます。

また make のオプションでコマンドを並列実行することもできます。
`make --jobs 2 hello` とすると `make hello-foo`, `make hello-bar`, `make hello-baz` のうち2つが同時に実行されることが期待できます。

### 問題点

この手法も万能ではありません。
特殊な状況で問題が発生する場合があります。

- wildcard target に `/` (スラッシュ) を含めることができない  
Makefile の仕様で wildcard target に `/` を含めることができません。  
https://stackoverflow.com/questions/21182990/makefile-is-it-possible-to-have-stem-with-slash  
たとえば次のようなエラーが発生します。

```bash
$ make hello-foo/bar
make: *** No rule to make target 'hello-foo/bar'.  Stop.
```

第2引数に特殊な文字列を使う場合にはmake変数を使う方が良さそうです。

## 補足

- コマンドの区切り文字は `-` (ハイフン) じゃなくても構いません

もっと簡単な表現方法や便利な使い方があればぜひコメントください。
Happy make life!