初見殺しなMakefileが生まれる過程を7ステップに分けて解説する

# はじめに
今のご時世でもMakefileをなんらかの形で触れる機会は多いと思います。
いちいち長ったらしいコンパイルコマンドを打ったりすることなく、

```
$ make
```

で必要な実行環境が整うとなかなかカッコ良いですね。

ただ、Makefileの書き方のルールがたくさんありややこしいなーと思って理解を後回しにしていました。

私もなんとなくノリで**便利コマンドをまとめたもの**ぐらいの使い方以上に使っていなかったのですが、「これじゃいかん！」ということで、いつもより凝った使い方をしようとしました。
そのときに予想通り「意味わからん」などしっかりハマりはしたものの、調べていく中で **Makefileの気持ちがわかってきた**ので記録として残しておきます。

## 結論：この記事を読んで書ける初見殺しなMakefile

ベストではないと思いますが、こんな感じになります。

```makefile:Makefile
dots = $(wildcard output/*.dot)
pngs = $(dots:%.dot=%.png)

image: $(pngs)

%.png: %.dot
	dot -Tpng $< > $@
```

# やろうとしたこと

今回は、**複数のdotファイルに対し、ファイルごとにコマンドを実行し、出力をpngで出力**しようとしました。

入力したいファイルとしては、

- `output/graph_00.dot`
- `output/graph_01.dot`
- `...`

のような各`dot`ファイルから次のような画像ファイルを作りたいです（すでに別プログラムでdotが出力されているので `output/` にあります）。

- `output/graph_00.png`
- `output/graph_01.png`
- `...`

dotファイルからpngを出力するのに用いるコマンドは次のようなものです。

```
$ dot -Tpng output/graph_00.dot > output/graph_00.png
```

これを全てのdotファイルに対して行いたいです。
↓理想は以下のコマンドで **各dotファイルがpngに変換されること**です。

```
$ make image
```

ちなみにdotファイルの中身は次のようなものです。

```dot:graph_00.dot
digraph {
    a -> b[weight="0.2"];
    a -> c[weight="0.4"];
    c -> b[weight="0.6"];
}
```

次のコマンドを実行すると、有向グラフを好きなファイル形式で出力してくれます（ここでは `-Tpng` でpngイメージとして出力しています）。

```
$ dot -Tpng output/graph_00.dot > output/graph_00.png
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260547/f288d528-a026-a7dc-37e2-2858dfb1c84e.png)

なんらかの計算をして、**大量のdotファイルを出力した後、各dotファイルから画像を出力し、まとめてgifアニメーションにする**、なんてことができたら面白そうです。

# Makefileを書いてみる

ここからは、ベタ書きから始めて、初見殺しなMakefileっぽい書き方に至るまでのプロセスを、いくつかのステップに分けながら解説をしていきます。

なぜこうしたかというと、**Makefileの機能は便利だけど、初見殺しが多すぎる**からです。
記事をいろいろ読んだのですが、Makefileの機能が充実しすぎて、結果だけ見せられても「なぜそうしているの？私の知ってるMakefileとちがーう」という気持ちになりました。

ですので、ステップバイステップで、一番馴染みのあるベタ書きなコマンドから、Makefileっぽい書き方のMakefile（変な表現）に至るまで見ていきます。

変換したいdotファイルは最終的にはgifにしたいというのもあり、数十〜数百個に及ぶことを想定しています。ベタ書きだと苦しくなります。

なお、環境をキレイにするために各実行前に

```makefile:Makefile
clean:
	rm output/*.png
```

```
$ make clean
```

を実行しています。以下のMakefileには `clean` は省略しています。


## 1. 超ベタ書き

まずは、一番何も考えなくてできるシンプルなバージョンで実行してみましょう。

### Makefile

```make:Makefile
image:
	dot -Tpng output/graph_00.dot > output/graph_00.png
	dot -Tpng output/graph_01.dot > output/graph_01.png
```

### 実行結果

```
$ make image
dot -Tpng output/graph_00.dot > output/graph_00.png
dot -Tpng output/graph_01.dot > output/graph_01.png
```

そんなにファイル数が多くない時はぶっちゃけこれでも動くので良いとは思うのですが、最終的には数百ファイルから数百枚の画像を出力したいので、こんな書き方ではやってられません。

## 2. 依存関係を考慮してみる
特にMakefileにも詳しいわけではない私ですが前知識として、**ターゲットと依存ファイルのようなものがあり、ソースファイルが更新されている場合のみコマンドの実行してくれるという機能がある**ということぐらいは知っていました。
確か、こんな感じ。

```make:Makefile
output/graph_00.png: output/graph_00.dot
    dot -Tpng output/graph_00.dot > output/graph_00.png
``` 

 `出力ファイル(ターゲット): 依存ファイル` のような書き方をすると、**依存ファイルの更新日時が出力ファイルのものより新しい場合のみ、コマンドを実行します**。

```
$ make output/graph_00.png
dot -Tpng output/graph_00.dot > output/graph_00.png
```

上のコマンドを実行したあと、依存ファイル( `output/graph_00.dot` )を更新しないでmakeコマンドを再実行すると、dotコマンドは `up to date` となって実行されません。

```
$ make output/graph_00.png
make: `output/graph_00.png' is up to date.
```

### Makefile

これを愚直に書いていくと、次のようになりました。

```makefile:Makefile
image: output/graph_00.png output/graph_01.png

output/graph_00.png: output/graph_00.dot
	dot -Tpng output/graph_00.dot > output/graph_00.png

output/graph_01.png: output/graph_01.dot
	dot -Tpng output/graph_01.dot > output/graph_01.png
```

### 実行結果

```
$ make image
dot -Tpng output/graph_00.dot > output/graph_00.png
dot -Tpng output/graph_01.dot > output/graph_01.png

$ make image
make: Nothing to be done for `image'.
```

makeの気持ちは次の通りです。

1. imageターゲットをmakeするために、まず `image` ファイルと、依存ファイルである `.png` の更新日時を比較する( `image` ファイルはないので必ず実行)
2. `.png` をターゲットとするコマンドが呼ばれる
3. 依存ファイルであるdotファイルが更新されていればdotコマンドが実行される
3. imageターゲットを作るためのコマンドは何もないので、結局imageターゲットは作られずに終わる

### いや、Makefileがさっきより長くなってるやんけ・・・

とツッコミを入れた方は鋭いですね。
ここからMakefileの便利な使い方を導入していきます。

## 3. ターゲットと依存ファイルを変数で置き換える

プログラマとしては、重複する記述はなるべく避けたいところですね。
Makefileのコマンド内で、ターゲットは `$@` 、依存ファイル（の先頭）は `$<` という変数名で表すことができます。

覚え方としては次のような感じでしょうか。

- ターゲットだからat(@)だから `$@`
- ファイルからの標準入力を受け取るときには `コマンド < ファイル` のようにする。依存ファイルは入力ファイルと考えられるから `$<` 


### Makefile

```makefile:Makefile
image: output/graph_00.png output/graph_01.png

output/graph_00.png: output/graph_00.dot
	dot -Tpng $< > $@

output/graph_01.png: output/graph_01.dot
	dot -Tpng $< > $@
```

### 実行結果

```
$ make image
dot -Tpng output/graph_00.dot > output/graph_00.png
dot -Tpng output/graph_01.dot > output/graph_01.png

$ make image
make: Nothing to be done for `image'.
```

`$@` や `$<` のような自動的に定義される変数は[Automatic Variables - GNU(英語)](https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html#Automatic-Variables)にまとめられていますので、気になる方は参照してください。

だんだんとMakefileっぽい書き方になってきましたが、まだまだ重複箇所があるので、それをまとめていきます。

**以下では、実行結果が全て同じになるので省略します。**

## 4. パターンマッチを使う

先ほどの例では、ターゲットと依存ファイルの関係が `.dot` が依存ファイルで、 `.png` に出力するというものになっていました。
makeでは、いちいちファイル名を指定しないでも、拡張子やファイル名のパターンがマッチしたときにコマンドを実行できるようにする **パターンマッチ**という機能があります。

`.dot` を依存ファイルとして、同名の `.png` をターゲットとするようなパターンマッチは `%.png: %.dot` のように書くことができます。
これまでのように、 `ターゲット: 依存ファイル` の関係になっていてわかりやすいですね。

今回の場合、二つのファイルに対してターゲットを書いていたのを次のようにまとめることができます。

```makefile:Makefile
%.png: %.dot
	dot -Tpng $< > $
```

#### （注）サフィックスルールは使わない
`.c.o` のような形で記述するサフィックスルールなどもありますが、公式の[Suffix-Rules - GNU](https://www.gnu.org/software/make/manual/html_node/Suffix-Rules.html)では **Old-Fashioned** だと言われているので使うのはやめておきましょう。

### Makefile

```makefile:Makefile
image: output/graph_00.png output/graph_01.png

%.png: %.dot
	dot -Tpng $< > $@
```

下のターゲットは具体的なファイル名が入っておらず、汎用的で再利用しやすい形です。

パターンマッチは、今回のような拡張子に対して行うものだけではなく、**特定のprefixを持つファイル**のパターンも次のように書くことができます。

```makefile:Makefile
graph_%.png: graph_%.dot
	dot -Tpng $< > $@
```

さて、残るは `image` ターゲットの依存するファイル群をどうにかするだけです。実はここが案外わかりにくいんですよね。。

## 5. まずは出力ファイル名を変数に置く

`image` ターゲットの依存ファイルをベタ書きではなく、ワイルドカードなどで取得できるようにしたいですね。
その前段階として、 `pngs` という変数をMakefileの最初で定義しておきます。


```makefile:Makefile
pngs = output/graph_00.png output/graph_01.png

image: $(pngs)

%.png: %.dot
	dot -Tpng $< > $@
```

すると、 `image` と `%.png` の二つのターゲットが具体的なファイル名に依存しない形にすることができました。

## 6. dotファイル名からpngファイル名を変換する

今回、`pngs` 変数は、dotファイルの `.dot` を `.png` にそのまま置き換えたものです。
Makefileの関数で、パターンで置換する `$(変数名:%.dot=%.png)` という記法がありますので、これを使います。

```makefile:Makefile
dots = output/graph_00.dot output/graph_01.dot
pngs = $(dots:%.dot=%.png)

image: $(pngs)

%.png: %.dot
	dot -Tpng $< > $@
```

この辺りの書き方は、[Makefile CheetSheet](https://devhints.io/makefile)を参考にしています。
公式でも、[Functions for String Substitution and Analysis (英語)](https://www.gnu.org/software/make/manual/html_node/Text-Functions.html)で解説がされています。

## 7. dotファイル名をワイルドカードで取得する

ここまでくれば、 `output/*.dot` というワイルドカードを使ってファイル名を取ってこれます。

```makefile:Makefile
dots = $(wildcard output/*.dot)
pngs = $(dots:%.dot=%.png)

image: $(pngs)

%.png: %.dot
	dot -Tpng $< > $@
```

[wildcardを利用したシンプルな汎用Makefile](https://qiita.com/szkny/items/07b4c93702b3f8090fa3)や先ほどのCheetSheetを参考にしています。

このようなMakefileの関数が何もないところから出てくると、ビビってしまうものだと思います（自分がそう）。
ですが、一個一個細かくみると、単に**ターゲットより新しいソースファイルがあればコマンドを実行する**というプログラムになっていることがわかります。


## 完成！
```makefile:Makefile
dots = $(wildcard output/*.dot)
pngs = $(dots:%.dot=%.png)

image: $(pngs)

%.png: %.dot
	dot -Tpng $< > $@
```

## あえて初見殺しなMakefileにしてみる

驚かすために大文字にし、ついでにdotファイルとpngファイルのディレクトリを異なるものにすると、あっという間にわかりにくくなります。
やっぱり大文字は読みにくいです。**できれば変数名を大文字で書くのをやめてほしい。**

```makefile:Makefile
SRCDIR = output
PNGDIR = output
DOTS = $(wildcard $(SRCDIR)/*.dot)
PNGS = $(DOTS:$(SRCDIR)/%.dot=$(PNGDIR)/%.png)

image: $(PNGS)

%.png: %.dot
	dot -Tpng $< > $@
```

# まとめ
「Makefileマジでわかりにくいよなぁ」とずっと思ってきた私でしたが、今回の画像出力の一件でかなり理解が進みました。
以前は、「依存関係？何それおいしいの？」状態で、ターゲットが本当はファイル名を表すものだということすらあまりわかっていませんでした。

こうやって記事を書くことで、あやふやな知識がだんだん固まっていくので、言語化は大事だなと痛感します。

この記事が、初見殺しなMakefileに悩まされている人の助けになれば幸いです。
ありがとうございました！

# 参考URL
- [Makefileの関数](https://qiita.com/chibi929/items/b8c5f36434d5d3fbfa4a)
- [Make覚書](https://qiita.com/Shigets/items/27170827707e5136ee89)
- [GNU make](https://www.gnu.org/software/make/manual/html_node/index.html#) やっぱり公式が一番いいっすわ。英語だけど。