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変数チートシート もどうぞ。