Makefile容易踩坑的地方
前言
Makefile很灵活,写Makefile的人,如果对其把握不到位,就容易写出各种bug。
如果大家常常因为编译大型工程而感到头疼,因为总是在莫名其妙的地方会报错,这大概率就是Makefile写的比较烂。
这里博主就在工作过程中,积累的一些Makefile容易踩坑的地方,记录在博客中。
隐式规则 vs 显式规则
隐式规则包含:
- 内建规则(buildin rule,可能是一个模式规则)
- 模式规则(包括buildin rules中包含的模式规则,和自己定义的模式规则)
显式规则包含:
- 普通规则
- 静态规则
静态规则不同于模式规则的最重要的一点是,它的target-pattern匹配的文件只会从targets中筛选,这样作用范围更局限。
依赖合并
只有显式规则才会进行依赖合并到显式规则或隐式规则,隐式规则的依赖不会被合并到显式规则或其他隐式规则
规则跳过
由上面的依赖合并问题,带来了下面的模式规则优先级问题。
如果某个目标 t 需要被构建,且满足这个目标构建的任意一条显式规则的依赖无法满足,Makefile会抛出错误;然而,对于满足这个目标构建的模式规则,当依赖 d 不满足时,该规则会被跳过,而不产生报错;因为模式规则的依赖 d 不会在别的模式规则中发生合并,make会认为依赖 d 对于构建目标 t 并不一定是必须的
规则检索优先级
博主自己总结的
- 显式规则>隐式规则(包括buildin规则,和显式定义的模式规则)
- 隐式规则中:显式定义的模式规则>buildin规则
一个目标匹配多个模式规则的target部分时的优先级:
- 首先排除那些依赖无法生成的模式规则(即使它们是被你书写的)。[example 1]
- 在所有可以使用的模式规则(包括显式定义的模式规则和隐式规则)中:
- 显式模式规则优先于隐式模式规则
- 匹配越具体(%替代部分(称为词干,stem)越少)优先级越高;
- 相同stem长度,依赖无需推断的(包括被明确表明了生成方式/依赖已经存在的),优先级高于需要隐式推断依赖生成的模式规则;[example 2][example 3]
- 在上述优先级依然相同的情况下,模式规则定义更靠前的生效[example 2][example 4]
例外情况是,如果两个模式规则的target和依赖相同(优先级相同),而只有命令不同,那么后边定义的模式规则会覆盖前面的模式规则。[example 3]
官方文档中文和英文版
中文描述如下:
当一个目标文件同时符合多个目标模式时,
- make将会把第一个目标匹配的模式规则作为重建它的规则。
- Makefile中明确指定的模式规则会覆盖隐含模式规则。就是说如果在Makefile中出现了一个对目标文件合适可用的模式规则,那么make就不会再为这个目标文件寻找其它隐含规则,而直接使用在Makefile中出现的这个规则。在使用时,明确规则永远优先于隐含规则。
- 另外,依赖文件存在或者被提及的规则,优先于那些需要使用隐含规则来创建其依赖文件的规则
原文其余描述如下:
A pattern rule can be used to build a given file only if there is a target pattern that
matches the file name, and all prerequisites in that rule either exist or can be built. The
rules you write take precedence over those that are built in. Note however, that a rule
which can be satisfied without chaining other implicit rules (for example, one which has no
prerequisites or its prerequisites already exist or are mentioned) always takes priority over
a rule with prerequisites that must be made by chaining other implicit rules.It is possible that more than one pattern rule will meet these criteria. In that case,
make will choose the rule with the shortest stem (that is, the pattern that matches most
specifically). If more than one pattern rule has the shortest stem, make will choose the first
one found in the makefile.
example 1
这个例子我们定义了三条同样优先级的模式规则,但是第一条模式规则的依赖得不到满足。
# 当前目录下不存在helloworld文件和目录
%.aaa:helloworld
@echo zero rule
%.aaa:
@echo first rule
%.aaa:
@echo second rule
执行make hello.aaa,结果为:
second rule
注意模式匹配的依赖文件不会合并,不满足依赖的规则即使target匹配也会被跳过,除非找不到合适的规则,从而抛出错误。
example 2
这个例子中,所有的依赖都被明确定义,且依赖存在,无需推断,三个优先级完全相同
# 当前目录下存在my_fun.c,不存在my_fun.o
%.aaa:my_fun.c
@echo zero rule
%.aaa:
@echo first rule
%.aaa:
@echo second rule
执行结果为zero rule
example 3
这个例子中,第一个依赖不存在,需隐式推断且可以推断出来
# 当前目录下存在my_fun.c,不存在my_fun.o
%.aaa:my_fun.o
@echo zero rule
%.aaa:
@echo first rule
%.aaa:
@echo second rule
执行结果为:second rule
stem一致,第一个规则依赖需推断,其优先级低于第2,第3规则;第2,第3规则target和依赖完全一致,后定义覆盖了前定义的规则。
example 4
这个例子展示了,一个目标匹配多个target优先级和依赖相同,但依赖本身不同的模式规则时,先定义的更优先。
# 当前目录下,my_fun.c,test.c,test.d都存在
%.aaa:my_fun.c
@echo zero rule
%.aaa:test.c
@echo first rule
%.aaa:test.d
@echo second rule
执行结果为:zero rule
实际上,在有可用的模式规则或显式规则时,Makefile会跳过那些不满足依赖的模式规则而不产生错误。这在冥冥之中,会带来一些隐患。比如接下来这个例子,这也是博主公司有人写的bug代码,不是博主这么仔细的人,一般人在1w行日志中可能根本发现不了这个漏洞。
example 5
# 当前目录下不存在.h文件
%.o:%.c *.h
@echo "CC $@"
$(CC) -O2 $^ -o $@
这条规则在匹配时,make会发现没有后缀名为 .h 的文件,也不存在 *.h 文件,而名为 *.h 的文件也没有隐含规则可以被创建,因此,这条规则的依赖无法得到满足,转而使用buildin规则,由.c生成.o文件并且可能不产生任何错误,但实际上完全没有执行你希望的指令!

浙公网安备 33010602011771号