4、Makefile条件判断:ifeq、ifneq、ifdef、ifndef、条件判断实战
条件判断这东西,说白了就是让Makefile学会「看人下菜碟」。
我刚开始写Makefile时,总觉得条件判断可有可无。后来维护一个跨平台项目,Windows和Linux的编译选项完全不同,同一个文件要生成debug和release两个版本……嗯,没有条件判断的话,你得维护两套Makefile,那才叫一个酸爽。
4.1 条件判断的基本语法
Makefile的条件判断,和C语言的预处理器有点像。它是在make解析阶段执行的,不是在编译阶段。这一点很重要,我见过不少新手在这里栽跟头。
基本格式长这样:
ifeq (条件1, 条件2)
# 条件成立时执行的语句
else
# 条件不成立时执行的语句
endif
注意这里的缩进,我习惯用Tab,但说实话用空格也行。不过Makefile里其他规则必须用Tab,这里倒是宽松一些。
4.2 ifeq 和 ifneq:比较两个值
ifeq 是最常用的条件判断。它比较两个值是否相等。我曾经在项目中用它来区分不同的编译器:
CC = gcc
ifeq ($(CC), gcc)
CFLAGS = -Wall -O2
else ifeq ($(CC), clang)
CFLAGS = -Weverything -O2
else
CFLAGS = -O2
endif
ifneq 正好相反,两个值不相等时才执行。说白了就是 ifeq 的反面:
ifneq ($(OS), Windows_NT)
# 非Windows系统
RM = rm -f
else
# Windows系统
RM = del /q
endif
ifeq ($(VAR), value) 和 ifeq ($(VAR),value) 是不同的。我建议统一在逗号后加一个空格,养成好习惯。
4.3 ifdef 和 ifndef:检查变量是否定义
这两个判断的是「变量有没有被定义」,而不是「变量的值是不是空」。这个区别很微妙,我解释一下:
# 情况1:变量已定义且有值
VAR1 = hello
ifdef VAR1
$(info VAR1 已定义) # 会执行
endif
# 情况2:变量已定义但值为空
VAR2 =
ifdef VAR2
$(info VAR2 已定义) # 也会执行!因为变量确实被定义了
endif
# 情况3:变量未定义
ifdef VAR3
$(info VAR3 已定义) # 不会执行
endif
看到了吗?ifdef 只关心「有没有这个变量名」,不关心它的值。我曾经在项目里用这个特性来检测用户是否传入了自定义参数:
ifdef CUSTOM_PATH
PATH := $(CUSTOM_PATH)
else
PATH := /usr/local/bin
endif
ifndef 就是 ifdef 的反面,变量未定义时才执行。我经常用它来设置默认值:
ifndef BUILD_TYPE
BUILD_TYPE = debug
endif
ifdef 检查一个变量,明明打印出来是空的,条件却成立了。查了半天才发现,这个变量在Makefile的某个地方被 := 赋值为空了。记住:ifdef 检查的是「是否被赋值过」,不是「值是否为空」。
4.4 条件判断实战:多平台编译
来,我们写一个完整的例子。这是我当年做嵌入式开发时用过的模板,稍微简化了一下:
# 检测操作系统
UNAME_S := $(shell uname -s)
# 根据平台设置编译器和参数
ifeq ($(UNAME_S), Linux)
CC = gcc
CFLAGS = -DLINUX -Wall
LDFLAGS = -lm
TARGET = app_linux
else ifeq ($(UNAME_S), Darwin)
CC = clang
CFLAGS = -DMACOS -Wall -Wno-deprecated
LDFLAGS = -framework CoreFoundation
TARGET = app_macos
else
$(error 不支持的操作系统: $(UNAME_S))
endif
# 检查是否启用调试模式
ifdef DEBUG
CFLAGS += -g -O0 -DDEBUG
else
CFLAGS += -O2
endif
# 检查是否启用静态链接
ifdef STATIC
LDFLAGS += -static
endif
all: $(TARGET)
$(TARGET): main.o utils.o
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -f *.o $(TARGET)
这个例子用到了 ifeq、ifdef 和 else if。你想想看,如果没有条件判断,你得写三个不同的Makefile,每个平台一个。现在一个文件搞定,多清爽。
4.5 条件判断的嵌套与注意事项
条件判断可以嵌套,但我不建议嵌套超过两层。为什么呢?因为Makefile的条件判断不像C语言那样有花括号,全靠缩进来区分层次,嵌套多了容易晕:
ifeq ($(A), 1)
ifeq ($(B), 2)
$(info A=1, B=2)
else
$(info A=1, B!=2)
endif
else
$(info A!=1)
endif
这里有几个坑,我踩过,你注意一下:
- 条件判断不能跨规则:一个条件判断必须在一个规则内部完成,不能从规则A开始,到规则B结束
- 条件判断在变量展开时执行:这意味着条件判断里引用的变量,必须是已经定义好的
- make的版本差异:GNU Make和BSD Make的条件判断语法略有不同,跨平台时要注意
4.6 本章知识体系
下面这张图,是我梳理的条件判断知识结构。你看一眼,心里就有数了:
这张图把四个条件判断的关系理清楚了。你写Makefile的时候,对照着看就行。
好了,条件判断就这些内容。说白了就是四个关键字,但用好了能让你的Makefile灵活十倍。下一章我们聊聊函数,那又是另一个世界了。
ifeq 试试水。等熟悉了再玩 ifdef 和嵌套。别一上来就搞复杂的,容易把自己绕进去。