3. Makefile变量:让构建脚本真正活起来
说实话,我刚学Makefile那会儿,最烦的就是重复写同样的路径和命令。比如一个项目里,gcc后面跟着一长串源文件,改一个就要改好几处。后来我才明白——变量就是用来解决这个痛点的。
变量这东西,说白了就是给一段文本起个名字。你后面想用这段文本,直接喊名字就行。Makefile里的变量,跟C语言的宏有点像,但又不太一样。咱们慢慢聊。
3.1 变量的定义与引用
定义变量很简单:
CC = gcc
CFLAGS = -Wall -O2
TARGET = myapp
SRCS = main.c utils.c
引用变量用 $(变量名) 或者 ${变量名}。我个人习惯用圆括号,看着顺眼:
$(TARGET): $(SRCS)
$(CC) $(CFLAGS) -o $(TARGET) $(SRCS)
嗯,这里要注意:变量名大小写敏感。CC 和 cc 是两个不同的变量。我见过有人因为大小写写错,排查了半天。
3.2 自动变量:Makefile的魔法符号
自动变量是我最喜欢的特性之一。你想想看,每次写规则都要重复目标名和依赖名,多烦啊。自动变量就是来解决这个问题的。
常用的三个自动变量:
| 变量 | 含义 | 示例值 |
|---|---|---|
$@ |
目标文件名 | myapp |
$^ |
所有依赖文件(去重) | main.c utils.c |
$< |
第一个依赖文件 | main.c |
看个例子就明白了:
myapp: main.o utils.o
gcc -o $@ $^
main.o: main.c
gcc -c -o $@ $<
utils.o: utils.c
gcc -c -o $@ $<
我在项目中遇到过这样的场景:一个项目有几十个源文件,每个都要写类似的编译规则。用自动变量后,配合模式规则,几行就搞定了。
$@,那会出问题。
3.3 变量的赋值方式
这部分容易让人迷糊。四种赋值方式,各有各的脾气。
3.3.1 = 递归展开赋值
这是最基础的赋值方式。但它有个特点:变量在使用时才展开。什么意思呢?
A = $(B)
B = hello
all:
echo $(A) # 输出 hello
你看,A定义的时候B还没定义,但最后用A的时候,B已经有了值。这就是递归展开。我曾经被这个特性坑过——如果B的值里又引用了A,就会陷入无限递归。Make会报错,但排查起来挺费劲的。
3.3.2 := 简单赋值
这个就直白多了:定义时就展开。我个人的项目里,90%的情况都用这个:
A := $(B) # 此时B还没定义,A就是空
B := hello
all:
echo $(A) # 输出空
简单赋值的好处是:行为可预测,不会出现递归问题。你想想看,写代码最怕什么?最怕不确定性。
= 和 :=,结果变量展开顺序乱成一锅粥。后来我定了个规矩:能用 := 的地方绝不用 =。
3.3.3 ?= 条件赋值
这个很实用:只有变量未定义时才赋值。如果已经定义过,就跳过:
CC ?= gcc
CC := clang # 这一行会覆盖上面的吗?不会,因为 ?= 只在未定义时生效
# 但如果反过来:
CC := clang
CC ?= gcc # 这一行不会生效,因为CC已经定义了
这个在写通用Makefile时特别好用。用户可以在命令行传参覆盖默认值:
# 命令行:make CC=arm-linux-gcc
# Makefile里:
CC ?= gcc # 如果用户没指定,就用gcc
3.3.4 += 追加赋值
这个最简单,就是在原有值后面追加内容:
CFLAGS := -Wall
CFLAGS += -O2
CFLAGS += -I./include
# 结果:CFLAGS = -Wall -O2 -I./include
追加时自动加空格,不用你操心。我经常用这个来分层构建编译选项:基础选项放一块,调试选项追加一次,发布选项再追加一次。
3.4 知识体系总览
说了这么多,咱们用一张图把变量这块的知识串起来:
3.5 实战建议
说了这么多理论,来点实在的。我写Makefile时,变量这块一般这么组织:
- 工具链变量放最前面:
CC、LD、AR这些,用?=赋值,方便用户覆盖。 - 编译选项用
:=:CFLAGS、LDFLAGS,确定下来就别变了。 - 源文件列表用
:=:SRCS、OBJS,用函数生成也行。 - 自动变量用在规则命令里:别在依赖列表里用,那是找坑。
变量这块,说白了就是让Makefile变得灵活、可维护。你想想看,没有变量的Makefile,改一个路径要翻好几处,那跟手写shell脚本有什么区别?
嗯,今天就聊到这儿。变量这东西,用多了自然就熟了。下次咱们聊聊Makefile里的函数,那又是另一番天地。
可可精品资料库