Makefile変数チートシート:自動変数 $@ $< $^ と := = の違い

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 がまだ未定義だからです。=DEFERREDdemo を実行する時に 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 に書かなくても使えます。

優先順位は以下です(下にいくほど強い)。

  1. 環境変数
  2. Makefile 内の定義
  3. コマンドライン指定(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

環境変数の値で処理を分けたいときは、この originifdef を条件分岐と組み合わせます。書き方は Makefileの条件分岐 ifeq/ifdef 完全ガイド にまとめました。

おまけ: 変数を書くときのつまずき

  • レシピ(コマンド行)の行頭はタブでなければなりません。スペースだと missing separator エラーになります。
  • $ そのものを書きたいときは $$ とエスケープします(シェル変数を使うレシピでよくハマります)。
  • 変数展開は $(VAR) でも ${VAR} でも同じです。1文字変数は $X と書けますが、可読性のため $(X) を推奨します。

さいごに

=:= の違い、$@ $< $^ の3つを押さえれば、たいていの Makefile は読めます。仕様全体は GNU Make 4.4 日本語マニュアル にまとまっているので、逆引きで足りないところはそちらへどうぞ。