GNU Make のふたつの変数の使い分け

# はじめに

最近はさまざまなビルドツールがあり、古典的な make を使う機会は減っているかもしれませんね。でも古典だけあって、make を使う機会が全くない、という人も少ないのではないでしょうか。
ここでは make でわかりにくいことのひとつ、2種類の変数と評価のタイミングについて整理してみます。

なお  make にはいろいろ方言がありますが、ここでは  GNU Make を対象にしています。

# ふたつの変数

**単純展開変数** と **再帰展開変数** の 2種類があります。違いは **いつ評価されるか** です。

## 単純展開変数

    CC := gcc

のように `:=` を使って定義します。文字通り単純で、特に難しいことはありません。
`make` がこの行を読んだタイミングで、変数 `CC` の値は `gcc` である、と決まります。

例えばこの Makefile 

```makefile
FOO := foo
BAR := $(FOO)
FOO := baz

$(info BAR: $(BAR))

all:
```

で make するとこうなります。
(ちなみに `$(info ...)` はログを出すための関数です)

```sh
$ make
BAR: foo
make: Nothing to be done for 'all'.
```

後から代入した `baz` ではなく `foo` が出力されました。当たり前ですね。

## 再帰展開変数

こちらは

```makefile
CC = gcc
```

のように `=` を使って定義します。単純展開変数と異なり、**評価されたときに値が決ま** ります。
例を見たほうがわかりやすいでしょう。

```makefile

FOO := foo
BAR = $(FOO)
FOO := baz

$(info BAR: $(BAR))

all:
```
さっきの例との違いは `BAR` への代入に `:=` を使うか、 `=` を使うか、だけです。実行してみましょう。

```sh

$ make
BAR: baz
make: Nothing to be done for 'all'.
```

`$(info BAR: $(BAR))` が読まれたタイミングで `BAR` の値が評価されます。`BAR` の値は `$(FOO)` です。 `FOO` の値はこのタイミングでは `baz` になっていますから、 `baz` が出力されました。

また、再帰展開変数は評価されるたびに展開されます。例えばこうです。

```makefile
BAR = $(shell date)

all: foo bar

foo:
        @echo $(BAR)
        @sleep 3

bar:
        @echo $(BAR)
```
```sh
$ make
Wed Feb 21 14:37:48 JST 2018
Wed Feb 21 14:37:51 JST 2018
```

同じように `echo $(BAR)` しているのに、異なる時間が表示されました。これはターゲット `foo` とターゲット `bar` に対応したコマンドの実行時に `BAR` の値がそれぞれ展開されているためです。

再帰展開変数のこの性質がトラブルとなることがあります。

- いつの間にか値が変わってしまう
- パフォーマンス劣化の原因になる
    - 時間がかかるような処理 (例えば `FILES = $(shell find . -type f -print)` とか) をしてしまうと何度もその処理が実行されてしまう

 ***特別な意図がないときは  `:=` を使って単純展開変数にしておく*** のがよいでしょう。

逆に再帰展開変数を使いたいのは、「Makefile のその行を読んでいるときにはまだ決まらない値を与えたいとき」です。ビルドの途中経過で値が決まるケースなど。

# コマンド中での変数参照で値が決まるタイミング

ところが、単純展開変数なのに後から値が決まるように見えることがあります。こんなケースです。

```makefile
FOO := foo

all:
        @echo $(FOO)

FOO := bar
```
```sh
$ make
bar
```

単純展開変数なのに、後から代入したはずの `bar` が出力されました !
実は、コマンド中での変数の展開は、そのターゲットを作ろうとするタイミングで評価されます。つまり、 Makefile を **いったん全部読み終わって、さあ `all` を作ろう、というときに** 評価されるのです。

この挙動はなかなかわかりにくいのでハマりやすいです。 Makefile の中でひとつの変数に何度も代入するときには気をつけましょう。

# 環境変数とコマンドラインオプションでの変数設定

「2種類の変数」という話題からちょっと離れますが、変数がらみで環境変数とコマンドラインオプションでの変数指定についても書いておきます。

A. `$ CFLAG=-O2 make`
B. `$ make CFLAGS=-O2`

このふたつの違いはわかるでしょうか ? 前者は `make` を実行するときの **環境変数**、後者は `make` に渡す  **コマンドラインオプション** です。

## 環境変数

こちらは別に `make`  に限ったやり方ではないですね。シェルから、環境変数を渡しつつコマンドを起動しているだけです。 Makefile 中では、この値も make 変数として参照できます。 (例: `$(CFLAGS)`)

## コマンドラインオプションによる変数設定

こちらは実は make のコマンドラインオプションです。このように値を与えた場合には、 Makefile 内で他の値が代入されていてもコマンドラインでの指定が優先されます。

```makefile
CFLAGS := -O0 -g
```

と書いてあったとしてもコマンドラインで `make CFLAGS=-O2` と指定したなら  `CFLAGS` の値は `-O2` となります。
ちなみにこの場合も単純展開変数と再帰展開変数を使い分けることができます。 `:=` なら単純展開、 `=` なら再帰展開です。

## 変数の値を make 起動時に柔軟に変更できるようにする

上記の性質を使うと、Makefile 中に例えば

```makefile
CFLAGS := -O0 -g $(CFLAGS)
```

などと定義しておけば、

-  `CFLAGS=-Wall make`  と起動すれば `CFLAGS` の値は `-O0 -g -Wall` になる (追加できる)
- `make CFLAGS:=-O2` と起動すれば `CFLAGS` の値は `-O2` になる (上書きされる)

みたいなこともできます。ちょっとトリッキーですが…。

# まとめ

- ふたつの make 変数、 **単純展開変数** と **再帰展開変数** について説明しました。
- ついでにコマンドラインオプションでの変数の上書きについても紹介しました。

# おまけ

オライリーの GNU Make の本の pdf が https://www.oreilly.co.jp/library/4873112699/ からダウンロードできます。 自分である程度の規模の Makefile を書く人は一度目を通しておくことをお勧めします。