Makefileの条件分岐 ifeq / ifdef 完全ガイド

Makefile で「デバッグビルドのときだけフラグを足したい」「OS ごとにコマンドを変えたい」といった分岐をしたいとき使うのが ifeqifdef です。書き方に少しクセがあるので、実例つきでまとめました。挙動はすべて手元の GNU Make で実行して確認しています。

仕様の詳細は GNU Make 4.4 日本語マニュアル 第7章「Makefileの条件分岐」 を参照してください。この記事はその実例版です。

4つの条件ディレクティブ

ディレクティブ 分岐の条件
ifeq 2つの値が等しい
ifneq 2つの値が等しくない
ifdef 変数が定義されている(空でない)
ifndef 変数が定義されていない(空)

いずれも elseendif で閉じます。else は省略できますが endif は必須です。

ifeq: 値を比較して分岐

DEBUG ?= 0

check:
ifeq ($(DEBUG),1)
	@echo "DEBUG mode ON"
else
	@echo "DEBUG mode OFF"
endif
make check          # DEBUG mode OFF
make check DEBUG=1  # DEBUG mode ON

書式は ifeq (値1,値2) です。カッコとカンマの形に注意してください。カンマの前後に余計なスペースを入れると、そのスペースも比較対象になって一致しなくなることがあります。

よくあるハマり: ifeq ($(DEBUG), 1) とカンマ後に空白を入れると、DEBUG=1 でも「1」と比較され一致しない、という事故が起きます。スペースは入れないのが無難です。

引用符を使った ifeq ("$(X)","yes") という書き方もできます。値に空白が含まれるときはこちらが安全です。

ifneq: 等しくないとき

「空でなければ」を表す定番のイディオムがこれです。

ifneq ($(EXTRA),)
	# EXTRA が空でないときだけ実行される
endif

ifneq ($(EXTRA),) は「EXTRA を空文字と比較して、等しくない=何か値がある」という意味になります。

ifdef / ifndef: 変数の有無で分岐

greet:
ifdef NAME
	@echo "Hello, $(NAME)"
else
	@echo "NAME is not set"
endif
make greet           # NAME is not set
make greet NAME=Tom  # Hello, Tom

ifdef変数名を直接書きます($(NAME) ではなく NAME)。ここが ifeq との違いです。

注意点として、ifdef は「定義されているか」ではなく「値が空でないか」で判定します。NAME =(空で定義)の場合は ifdef NAME は偽になります。空文字も含めて厳密に判定したいケースは少ないので、実用上は「値があるか」で覚えておけば十分です。

環境変数・コマンドライン引数で分岐する

make は環境変数をそのまま変数として読み込むので、条件分岐でそのまま使えます。

build:
ifeq ($(CI),true)
	@echo "CI 環境向けビルド"
else
	@echo "ローカルビルド"
endif

環境変数 CItrue のとき(多くの CI サービスが自動で設定します)に分岐します。

「その変数が環境変数由来か、Makefile 定義か」まで区別したいときは $(origin) を使います。

ifeq ($(origin CC),default)
	# CC がユーザー未指定(make の組み込みデフォルト)のときだけ差し替える
	CC := clang
endif

origin の戻り値は environment / file / command line / default / undefined などです。変数の由来まで踏まえた分岐ができます。変数の由来の調べ方は Makefile変数チートシート にもまとめています。

OS で分岐する実例

uname の結果で macOS と Linux を切り替える、実務でよくある例です。

UNAME := $(shell uname)

ifeq ($(UNAME),Darwin)
	OPEN := open        # macOS
else
	OPEN := xdg-open    # Linux
endif

show:
	$(OPEN) result.html

$(shell uname):= で受けるのがポイントです。= だと $(UNAME) を参照するたびに uname を実行してしまいます。

つまずきポイント

  • 条件ディレクティブはレシピではないので、行頭にタブを入れません(ifeq 等はタブなしで書く)。タブで始めるとレシピの一部と見なされてエラーになります。
  • 一方、ifeq の中に書くレシピ行は通常どおりタブで始めます。上の例のインデントはタブです。
  • else if を続けたいときは else ifeq (...) と書けます。
ifeq ($(TARGET),debug)
	FLAGS := -g -O0
else ifeq ($(TARGET),release)
	FLAGS := -O2
else
	FLAGS :=
endif

さいごに

ifeq は値の比較、ifdef は変数の有無、と役割を分けて覚えると迷いません。仕様の全体像は GNU Make 4.4 日本語マニュアル にまとまっています。変数まわりで詰まったら Makefile変数チートシート もどうぞ。