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

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

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

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

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

いずれも else と endif で閉じます。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

環境変数 CI が true のとき(多くの 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変数チートシート もどうぞ。