4. Makefile模式规则与静态模式:%通配符的使用,静态模式规则的写法,常见应用场景。
好,咱们接着聊Makefile。前面几章我们讲了变量、自动变量、隐式规则这些基础。说实话,这些已经能应付不少场景了。但如果你写过稍微复杂一点的项目,比如一个目录下有几十个.c文件要编译,你肯定不想一个一个去写规则。
这时候,%通配符就该登场了。我个人觉得,这是Makefile里最优雅的设计之一。
4.1 %通配符:模式规则的灵魂
先看一个最简单的例子。假设你有三个.c文件:foo.c、bar.c、baz.c。你想把它们都编译成.o文件。
传统写法是这样的:
foo.o: foo.c
gcc -c foo.c -o foo.o
bar.o: bar.c
gcc -c bar.c -o bar.o
baz.o: baz.c
gcc -c baz.c -o baz.o
嗯,三个文件还好。那三十个呢?三百个呢?你想想看,这得写到什么时候去。
用模式规则,一行就搞定:
%.o: %.c
gcc -c $< -o $@
%在这里是个通配符,它匹配文件名中除去后缀的部分。比如foo.o匹配%.o时,%就代表foo。然后它会把foo代入到依赖中,变成foo.c。
说白了,这就是个「模式匹配」的机制。Make会拿着目标文件名,去和模式规则做匹配,匹配上了就按规则执行。
核心要点:%可以出现在目标和依赖中。同一个规则里,多个%匹配到的内容是相同的。
我在项目中遇到过一种情况:有人把%和*搞混了。*是shell的通配符,在Makefile里用$(wildcard *.c)才能展开。而%是Make自己的模式匹配,两者完全不是一回事。
4.2 静态模式规则:更精准的控制
模式规则虽然好用,但它有个问题:它会匹配所有符合模式的目标。有时候你只想对特定的文件列表应用规则,而不是所有文件。
举个例子。假设你的项目里有main.c、utils.c、test.c,但test.c是测试文件,你不想用同样的编译选项。这时候静态模式规则就派上用场了。
写法是这样的:
OBJS = main.o utils.o
$(OBJS): %.o: %.c
gcc -Wall -O2 -c $< -o $@
看清楚语法:目标列表 : 目标模式 : 依赖模式。Make会拿OBJS里的每个文件,去匹配%.o这个模式,然后生成对应的依赖。
这样test.o就不会被这条规则影响。你可以单独为它写另一条规则。
小技巧:静态模式规则比普通模式规则更安全。它不会意外匹配到你不想处理的文件。我个人的习惯是,只要文件列表是明确的,就用静态模式规则。
4.3 常见应用场景
光说不练假把式。咱们来看几个实际项目中会用到的场景。
场景一:编译所有.c文件到.o文件
这是最基础也是最常见的用法:
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
$(OBJS): %.o: %.c
gcc -Wall -O2 -c $< -o $@
这里用$(wildcard *.c)自动获取所有.c文件,然后用替换引用的语法$(SRCS:.c=.o)把后缀换成.o。最后用静态模式规则编译。
我曾经见过有人手动列出所有文件名,结果加了个新文件忘了更新Makefile,编译出来缺东西。用wildcard就省心多了。
场景二:生成不同目录下的目标文件
项目大了,源文件通常会分目录存放。比如src/目录放源码,obj/目录放编译产物。
SRC_DIR = src
OBJ_DIR = obj
SRCS = $(wildcard $(SRC_DIR)/*.c)
OBJS = $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRCS))
$(OBJS): $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
gcc -Wall -O2 -c $< -o $@
这里用了$(patsubst ...)来做路径替换。模式规则里的%匹配的是文件名(不含路径),所以src/foo.c匹配$(SRC_DIR)/%.c时,%就是foo。
注意:如果目标目录不存在,编译会失败。记得在规则里加上mkdir -p $(OBJ_DIR)。我曾经因为这个bug排查了半天,最后发现是目录没创建。
场景三:生成多个可执行文件
有时候一个项目会生成多个可执行文件,每个对应一个源文件。比如tool1.c生成tool1,tool2.c生成tool2。
TOOLS = tool1 tool2 tool3
$(TOOLS): %: %.c
gcc -Wall -O2 $< -o $@
这个规则的意思是:对于tool1,它依赖tool1.c,编译命令用$<和$@。简洁明了。
4.4 模式规则与隐式规则的优先级
嗯,这里有个坑。Make内置了很多隐式规则,比如%.o: %.c就是内置的。如果你自己写了同样的模式规则,你自己的会覆盖内置的。
但如果你写了静态模式规则,它的优先级比普通模式规则高。Make会先尝试匹配静态模式规则,匹配不上再去找普通模式规则。
我建议你养成一个习惯:只要是自己定义的规则,都用静态模式规则。这样行为更可控,不会莫名其妙被内置规则干扰。
4.5 知识体系梳理
说了这么多,咱们用一张图来总结一下模式规则和静态模式的核心逻辑。
这张图把两种模式规则的关系理清楚了。左边是普通模式规则,右边是静态模式规则。它们的核心都是%通配符,但应用范围和控制粒度不同。
4.6 避坑指南
最后,分享几个我踩过的坑:
- % 不能匹配路径中的斜杠。比如
%.o: %.c匹配不了src/foo.o,因为%不会跨目录匹配。需要写成src/%.o: src/%.c。 - 静态模式规则中的目标列表不能为空。如果
OBJS是空的,Make会报错。建议用$(if $(OBJS), ...)做保护。 - 同一个文件不能匹配多个模式规则。如果有两条规则都能匹配同一个目标,Make会选择第一条。这可能导致意想不到的行为。
嗯,模式规则和静态模式就讲到这里。说白了,它们就是让你少写重复代码的利器。用好了,Makefile能精简不少。下次你写编译规则的时候,不妨试试用%来偷个懒。
可可精品资料库