第1章:Makefile条件判断 — ifeq/ifneq/ifdef/ifndef 的使用与常见陷阱
条件判断,说白了就是让 Makefile 学会「看人下菜碟」。
同一个 Makefile,在不同平台、不同配置下,能编译出不同的东西。这就是条件判断的价值。我刚开始用 Makefile 时,觉得这东西不就是个 if-else 嘛,有啥好学的?结果被坑了几次之后,才老老实实回来补课。
1.1 四种条件判断指令
Makefile 里一共就四种条件判断:ifeq、ifneq、ifdef、ifndef。名字很直白,但用法上有讲究。
1.1.1 ifeq — 判断相等
格式很简单:
ifeq ($(VAR), value)
# 相等时执行
else
# 不相等时执行
endif
注意括号和逗号的位置。我见过有人写成 ifeq ($(VAR),value) 少了个空格,结果死活不生效。嗯,Make 对空格很敏感。
1.1.2 ifneq — 判断不等
用法和 ifeq 一模一样,就是逻辑反过来:
ifneq ($(CC), gcc)
$(warning 你不是在用 GCC 编译)
endif
1.1.3 ifdef — 判断变量是否定义
这个我特别喜欢用。它只关心变量「有没有被定义」,不关心值是什么:
ifdef DEBUG
CFLAGS += -g -O0
endif
注意:如果 DEBUG 被定义为空字符串,ifdef 仍然认为它是「已定义」的。这一点很多人会搞错。
1.1.4 ifndef — 判断变量是否未定义
就是 ifdef 的反面:
ifndef CROSS_COMPILE
CROSS_COMPILE = arm-linux-
endif
1.2 条件判断的常见陷阱
这部分是我最想讲的。我在项目中踩过的坑,一个一个说给你听。
Make 对空格的处理非常「死板」。比如:
# 错误写法
ifeq ($(VAR), value ) # value 后面多了一个空格
# 正确写法
ifeq ($(VAR), value)
我曾经因为一个尾随空格,排查了整整两个小时。最后用 $(strip) 函数才定位到问题。
看这个例子:
FOO =
ifdef FOO
$(info FOO 已定义)
else
$(info FOO 未定义)
endif
输出结果是「FOO 已定义」。因为 FOO 确实被定义了,只是值为空。如果你想要判断「变量是否被定义且非空」,得用 ifneq ($(FOO),)。
条件判断指令本身不能缩进!但里面的命令可以:
# 错误
ifeq ($(OS),Linux)
CC = gcc
endif
# 正确
ifeq ($(OS),Linux)
CC = gcc
endif
Make 会把缩进的 ifeq 当成命令来执行,然后报错。这个坑我见过不下五次。
1.3 实战:一个跨平台编译的条件判断示例
来,看一个我实际项目里用过的例子:
# 检测操作系统
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S), Linux)
CC = gcc
CFLAGS = -Wall -O2 -DLINUX
LDFLAGS = -lm
else ifeq ($(UNAME_S), Darwin)
CC = clang
CFLAGS = -Wall -O2 -DMACOS
LDFLAGS = -framework CoreFoundation
else
$(error 不支持的操作系统: $(UNAME_S))
endif
# 调试模式开关
ifdef DEBUG
CFLAGS += -g -DDEBUG
$(info 调试模式已开启)
endif
# 交叉编译检测
ifdef CROSS_COMPILE
CC = $(CROSS_COMPILE)gcc
AR = $(CROSS_COMPILE)ar
$(info 使用交叉编译工具链: $(CROSS_COMPILE))
endif
这个例子覆盖了三种条件判断的典型用法。我个人习惯把平台检测放在 Makefile 开头,这样后面的编译规则就不用操心了。
1.4 条件判断的执行时机
这一点很重要:条件判断在 Makefile 解析阶段就执行了,不是在编译阶段。
什么意思呢?就是说:
- 条件判断里的变量,必须在判断之前就已经定义好
- 不能用条件判断来控制「是否执行某条命令」——那是 shell 的事
举个例子,下面这种写法是错的:
target:
@echo "开始编译"
ifeq ($(shell test -f config.h && echo yes), yes)
@echo "config.h 存在"
endif
因为 ifeq 在解析时就被处理了,而 test -f config.h 是在运行时才执行的。正确的做法是用 shell 的 if:
target:
@echo "开始编译"
@if [ -f config.h ]; then echo "config.h 存在"; fi
1.5 条件判断的嵌套
条件判断可以嵌套,但要注意格式:
ifeq ($(ARCH), arm)
ifdef FLOAT_ABI
ifeq ($(FLOAT_ABI), hard)
CFLAGS += -mfloat-abi=hard
else
CFLAGS += -mfloat-abi=softfp
endif
endif
endif
嵌套时,每个 ifeq 都要有对应的 endif。我建议最多嵌套三层,再深就不好读了。
1.6 条件判断与函数结合
条件判断里可以调用 Make 函数,这招很实用:
ifeq ($(strip $(CUSTOM_DEFINES)),)
$(info 没有自定义宏定义)
endif
ifneq ($(findstring arm, $(ARCH)),)
$(info 目标架构包含 arm)
endif
strip 去掉空格,findstring 查找子串。这些函数配合条件判断,能写出很灵活的配置逻辑。
调试条件判断时,多用 $(info ...) 打印变量值。比如:
$(info DEBUG = $(DEBUG))
$(info ARCH = $(ARCH))
这样能快速定位问题。我曾经靠这一招,十分钟就找到了同事 Makefile 里的 bug。
1.7 本章小结
条件判断是 Makefile 的「大脑」,但也是个容易出幺蛾子的地方。记住三点:
- 空格要小心,尤其是值的前后
- ifdef 和「变量是否非空」是两码事
- 条件判断在解析阶段执行,别把它当 shell 用
把这些搞清楚了,你的 Makefile 就能适应各种环境。下次遇到「换个平台就编译不过」的问题,先检查条件判断——八成是这里出了岔子。