Makefileを書いていると、$@ や $< みたいな記号や、= と := の使い分けで手が止まりがちです。この記事は変数まわりだけを逆引きでまとめたチートシートです。挙動はすべて手元の GNU Make で実行して確認しています。
もっと踏み込んだ仕様は GNU Make 4.4 日本語マニュアル 第6章「変数の使い方」 を参照してください。この記事はその逆引き版です。
= と := の違い(ここだけは押さえる)
いちばん引っかかるのがこれです。
=… 遅延展開。右辺は使うときに評価される。:=… 即時展開。右辺はその行を読んだ時点で評価される。
次の Makefile で違いが出ます。
DEFERRED = $(LATER)
IMMEDIATE := $(LATER)
LATER = value
demo:
@echo "deferred = [$(DEFERRED)]"
@echo "immediate = [$(IMMEDIATE)]"
deferred = [value]
immediate = []
IMMEDIATE が空なのは、:= の行を読んだ時点で LATER がまだ未定義だからです。= の DEFERRED は demo を実行する時に LATER を見に行くので value になります。
迷ったら基本は := を使うのが安全です。参照順に依存せず、シェル呼び出し(後述の $(shell ...))を毎回実行してしまう事故も防げます。= は「あとで決まる値を遅延させたい」ときだけに絞ると読みやすくなります。
::=は:=と同じ意味です(POSIX で標準化された書き方)。どちらを使っても挙動は変わりません。
?= と +=
FLAGS := -Wall
FLAGS += -O2
NAME ?= default
cfg:
@echo "FLAGS = $(FLAGS)"
@echo "NAME = $(NAME)"
+=… 既存の値に追記する。上の例ではFLAGSが-Wall -O2になります。?=… その変数がまだ未定義のときだけ代入する。すでに値があれば何もしません。
$ make cfg
FLAGS = -Wall -O2
NAME = default
コマンドラインから上書きするとこうなります。
$ make cfg NAME=custom
FLAGS = -Wall -O2
NAME = custom
?= は「デフォルトを用意しつつ、外から差し替えられるようにする」用途にちょうど合います。
自動変数 $@ $< $^ など
ルールのレシピの中でだけ使える、ターゲットや前提条件を指す特別な変数です。よく使うのは次の3つです。
| 変数 | 意味 |
|---|---|
$@ |
ターゲット名 |
$< |
最初の前提条件(依存ファイル) |
$^ |
すべての前提条件(重複を除く) |
out.o: a.c b.c
@echo "target = $@"
@echo "first prereq = $<"
@echo "all prereqs = $^"
target = out.o
first prereq = a.c
all prereqs = a.c b.c
コンパイル定義なら、こう書けばターゲットごとにファイル名を書き直さずに済みます。
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
他にもある自動変数:
| 変数 | 意味 |
|---|---|
$? |
前提条件のうち、ターゲットより新しいものだけ |
$* |
パターンルールの % にマッチした部分(stem) |
$(@D) / $(@F) |
$@ のディレクトリ部分 / ファイル名部分 |
自動変数は暗黙のルールと一緒に使うことが多いので、詳しくは GNU Make 4.4 日本語マニュアル 第10章「暗黙のルールの使い方」 が参考になります。
環境変数との関係
make は起動時の環境変数を、そのまま make の変数として読み込みます。つまり $(PATH) や $(HOME) は Makefile に書かなくても使えます。
優先順位は以下です(下にいくほど強い)。
- 環境変数
- Makefile 内の定義
- コマンドライン指定(
make VAR=値)
つまり Makefile 内の FOO = ... は、同名の環境変数より優先されます(make -e を付けると環境変数が勝つように逆転します)。
変数がどこ由来かを調べたいときは $(origin) を使います。
src:
@echo "origin(PATH) = $(origin PATH)"
@echo "origin(DEBUG) = $(origin DEBUG)"
@echo "origin(UNDEF) = $(origin UNDEF)"
origin(PATH) = environment
origin(DEBUG) = file
origin(UNDEF) = undefined
環境変数の値で処理を分けたいときは、この origin や ifdef を条件分岐と組み合わせます。書き方は Makefileの条件分岐 ifeq/ifdef 完全ガイド にまとめました。
おまけ: 変数を書くときのつまずき
- レシピ(コマンド行)の行頭はタブでなければなりません。スペースだと
missing separatorエラーになります。 $そのものを書きたいときは$$とエスケープします(シェル変数を使うレシピでよくハマります)。- 変数展開は
$(VAR)でも${VAR}でも同じです。1文字変数は$Xと書けますが、可読性のため$(X)を推奨します。
さいごに
= と := の違い、$@ $< $^ の3つを押さえれば、たいていの Makefile は読めます。仕様全体は GNU Make 4.4 日本語マニュアル にまとまっているので、逆引きで足りないところはそちらへどうぞ。