LoginSignup
53
32

More than 5 years have passed since last update.

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

Posted at

はじめに

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

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

ふたつの変数

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

単純展開変数

CC := gcc

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

例えばこの Makefile

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

$(info BAR: $(BAR))

all:

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

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

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

再帰展開変数

こちらは

CC = gcc

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


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

$(info BAR: $(BAR))

all:

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


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

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

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

BAR = $(shell date)

all: foo bar

foo:
        @echo $(BAR)
        @sleep 3

bar:
        @echo $(BAR)
$ 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 のその行を読んでいるときにはまだ決まらない値を与えたいとき」です。ビルドの途中経過で値が決まるケースなど。

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

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

FOO := foo

all:
        @echo $(FOO)

FOO := bar
$ make
bar

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

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

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

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

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

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

環境変数

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

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

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

CFLAGS := -O0 -g

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

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

上記の性質を使うと、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 を書く人は一度目を通しておくことをお勧めします。

53
32
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
32