规则没有先后顺序
一般来说规则的顺序是没有先后的,除了默认target的规则。make会将第一个makefile里面的第一条规则的第一个target作为默认的target。所以,默认target的规则应该放在最前面,一般使用all作为默认target的名称。
规则的声明
第一种格式,将recipe从新行开始写,如下:
targets : prerequisites
recipe
...
第二种格式,从prerequisites列表后面开始写recipes,用[ ; ]隔开,如下:
targets : prerequisites ; recipe
recipe
...
target可以有多个,使用空格分开,通常来说一个规则里面只有一个target,也不排除有其它特殊的情况(比如后面要讲的使用了通配符,或者静态模板规则)。
规则里面可以$
来引用变量,如果只是想单纯的使用$
符号,必须使用$$
,因为targets和prerequisites都会在read-in阶段被展开,一个$
的话会被直接当作变量引用进行展开。如果使用.SECONDEXPANSION
开启了prerequisites的二次展开,而你又想在prerequisites部分使用$
符号,则应该使用$$$$
(第一次展开去掉第一个,第三个$
,第二次展开去掉第二个$
,最后剩下一个单纯的$
符号)。
在make的第二阶段,make会对target的prerequisites列表里面的项逐个进行检查,看是否需要更新target。如果某个target不存在,或者某些prerequisites有更新的话,则该target会被生成或更新,这样的presiquisites叫做常规presiquisites,它的更新会导致依赖它的target也会被更新。
但是有时候,我们仅仅是希望某些prerequisites存在即可,不希望它们影响到target的更新。比如说像目录这样的prerequisites,我们可能希望运行target的recipe之前存在某个目录以方便存放一些临时文件,但是这个目录是否更新我们并不关心,我们只是希望它存在即可。这样的prerequisites叫做Order-only prerequisites,检查target的时候,其只参与存在与否的检测,不参与是否比target更新的检测。
在prerequisites列表中,使用[ | ]来分割常规prerequisites和Order-only prerequisites。[ | ]左边的为常规类型,右边的为Order-only类型:
targets : normal-prerequisites | order-only-prerequisites
使用通配符
我们可以在规则里面使用通配符,使用通配符得到的结果是经过排序的。make里面的通配符是[ * ],[ ? ]和[ … ]。[ ~ ]也有特殊含义,表示用户的家目录。
targets和prerequisites部分的通配符展开是由make来负责的,recipe部分的通配符展开由shell负责。其它地方应通过wildcard函数来使用通配符,不然通配符会被当作一个普通的字符处理。如果想在Rule里面单纯使用[ * ]符号,则应使用转义[ \ ]进行转义,比如foo\*bar
。
使用通配符要注意的地方
当通配符没有配到到任何结果时,则返回它本身,比如*.o
,如果查找目录下没有.o文件,则*.o
的结果就是"*.o",这通常并不是我们想要的结果。我们可以通过一些更成熟的方法来避免这样的问题,比如说使用wildcard
函数和字符串替换,这些方法后面再进行学习。
使用wildcard函数
前面讲了Rule里面的通配符可以直接由make或shell展开,但是其它部分的通配符(比如赋值部分出现的通配符,或者函数参数里面的通配符)并不能自动展开,需要借助wildcard
函数,其使用形式如下:
$(wildcard pattern...)
匹配到的条目排序后以空格分开,如果没有匹配到任何条目,则函数直接被忽略,不会返回任何东西。
VPATH变量,指定Prerequisites的查找路径
make的查找路径默认为Makefile所在的目录,通过使用VPATH变量,可以设置其它查找目录(目录使用冒号或空格分开),查找顺序是当前目录 > VPATH指定的路径,找到prerequisite后则停止查找。这样,我们可以将prerequisites集中放在几个目录里面,通过VPATH变量来指定这些目录的路径,就不需要在Rule里带上具体的路径了。
实际上,make也会通过VPATH去查找target。
使用vpath指令设置匹配文件的查找路径
vpath directive比VPATH变量更进一步,它可以以文件名匹配的方式设置查找路径,其使用方式如下:
vpath pattern directories
pattern是要匹配的文件名,使用[ % ]来匹配零个或多个任意字符。directories是设置的查找目录,用冒号或空格隔开。查找顺序为当前目录 > 设置的目录,比如有如下的makefile片段:
vpath %.c foo:bar
vpath % blish
对于当前目录下不存在的.c文件,会依次去foo bar blish目录下查找。对于当前目录下不存在的其它文件,则会再去blish目录下查找。
vpath directive还有其它两种使用方式如下:
vpath pattern
表示清除掉使用vpath为pattern设置的查找路径。
vpath
表示清除掉所有通过vpath设置的查找路径。
make是如何使用搜索目录的?
前面两部分说了可以通过VPATH变量或vpath directive来指定target和prerequisites的额外查找路径。对于target文件来说,还存在一个问题就是是否确实要使用查找到的路径。当我们在额外路径下面找到了target文件时,如果发现其是最新的,不需要重构,那么会直接将它作为其它target文件的prerequisite。如果它需要进行重构,那么make会抛弃找到它的路径,优先使用Rule里面指定的路径(也就是会在Rule里指定的路径下生成新的target)。
可以通过GPATH变量来为指定目录定制这种行为,通过GPATH变量指定的目录,作为被查找到的路径,会被保留使用(新的target会生成在查找到的目录里面)。
当我们在makefile里面使用了目录搜索的功能时,需要小心编写Rule里面的Recipe,以便shell能够通过正确的路径找到文件。这可以通过使用自动变量来实现,自动变量会包含文件所在的具体路径。比如,有如下的makefile片段:
VPATH = src:../headers
foo.o : foo.c defs.h hack.h
cc -c $(CFLAGS) $< -o $@
如果在src
目录下找到了foo.c,则$<
为src/foo.c
;如果在../headers
目录下找到了foo.c,则$<
为../headers/foo.c
。相对路径都是以makefile所在目录为起点。
隐式规则里面的目录查找
目录查找也适用于隐式规则,比如对于foo.o目标的隐式规则,会使用foo.c来生成foo.o。当前目录下没有foo.c的时候,就会去vpath和VPATH指定的目录下去查找foo.c。隐式规则的Recipe里面使用的通常都是自动变量,所以能正确引用foo.c的路径。
链接库的目录查找
当prerequisites列表里面使用-lname的形式指定了依赖库的时候,也会对其进行目录查找,优先使用动态库libname.so,然后是静态库libname.a。查找顺序是当前目录 > vpath指定的目录 > VPATH指定的目录 > 系统库目录(/lib, /usr/lib, and /usr/local/lib)。
伪目标(Phony Targets)
伪目标并不对应一个真正的文件,其只是用于明确告诉make去执行一个规则,而不是创建伪目标这个文件。比如有如下的伪目标定义:
clean:
rm *.o temp
一般来说,目录下不会存在与伪目标同名的clean文件,所以每次去make伪目标的时候,对应的Recipies都会被运行。但是当目录下确实存在一个同名文件的时候,这个规则就不能得到预期的效果,因为规则里没有presiquisites,所以target总是最新的,因此recipe总是得不到运行。为了避免这样的问题,我们可以明确指定clean为一个Phony Target,方法是将clean作为.PHONY
这个特殊目标的依赖,如下:
.PHONY: clean
clean:
rm *.o temp
这样,每次make clean的时候,不管是否存在clean这个文件,规则里面的recpies都会运行。
.PHONY目标的依赖都是直接被当作文件名,不进行通配展开。
make不会对伪目标进行隐式规则匹配。
伪目标不应该作为其它真实目标的prerequisite,不然目标总是会被更新。
伪目标可以包含prerequisites列表,比如我们常用的all:
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
这样,我们可以使用一个makefile编译出3个程序的可执行文件。
没有Prerequisites或Recipes的规则
如果这种规则的target不是一个存在的文件,则make将Rule的运行等同于target的更新。这样的话,任何将这个target当作prerequisite的target的Recipies总是会被运行。比如,有如下的makefile片段:
clean: FORCE
rm $(objects)
FORCE:
对于目标FORCE的Rule,没有prerequisites和recipes,并且FORCE也并不是一个文件(可以近似看作一个伪目标,只是没有用.PHONY声明)。当运行make clean
的时候,去检查其依赖FORCE。因为FORCE不存在,并且规则里面也没有对应的Recipes去生成它,所以make直接将其视为更新状态。这样,clean也会被视为需要进行更新,其Recipes会被执行。通常用这种方法对某个目标进行强制更新;或者强制运行某个Rule里面的Recipes(这种情况跟使用.PHONY
的方法效果相同,对于一些不支持.PHONY
的make程序,就使用FORCE来实现相同的效果)。
使用空目标文件来记录事件
有时候一个规则不是为了生成一个具体的target文件,而是为了执行一组命令,比如打印改变了的文件。这样,我们需要记录上次打印的时间,以免重复打印,可以专门将target作为记录打印事件的用途。假设有如下的makefile片段:
print: foo.c bar.c
lpr -p $?
touch print
这条规则用于在foo.c或bar.c被更新的时候打印对应的文件。print并不是一个规则需要生成的target文件,其只是在打印后被更新,用于表示更新的文件已经被打印过了,下次再运行make print便不会重复打印。
一条规则里面出现多个target
一条规则里面出现多个target的时候,make以两种不同的方式对待这些target:把它们当作相互独立的target或将它们当作成组的target。
当作独立的target是指相当于将每个target都拆分成相同的规则,有同样的prerequisites列表和recipes,自动变量$@
用于表示当前的target。
当作成组的target是指这些target都是在规则的recipes里同时生成的,此时自动变量$@
表示的是触发Recipies被执行的那个target。成组的target总是会同时更新,哪怕只有一个target过期,其它的target也会被一起更新。
targets : prerequisites
表示targets是相互独立的。targets &: prerequisites
表示targets是成组的。
[ &:: ]用于在多个组里面包含同一个target文件,这种情况就不讨论了。
多条规则对应同一个target文件
一个文件可以作为多条规则的目标文件,这个目标对应的所有规则里面的所有prerequisites文件会合并成一个prerequisites列表来作为这个target的prerequisites,但是只能运行一个recipe来更新这个target。当有一个以上的规则里面都有recipe的时候,make会使用最后一条规则的recipe并打印一个错误消息(对于使用[ . ]开头的target,不会打印这个错误)。如果这些规则里面都没有recipe,则尝试使用合适的隐式规则来更新target。
通常来说,将多条规则对应同一个target文件是为了方便分多次为target添加prerequisites文件,recipe可以写在任意一条规则里面,比如下面的例子:
objects = foo.o bar.o
foo.o : defs.h
bar.o : defs.h test.h
$(objects) : config.h
config.h同时作为foo.o和bar.o的依赖,使用一条规则同时把它添加到foo.o和bar.o的prerequisites列表里面。
静态模板规则(Static Pattern Rules)
前面讲的规则都是普通规则(包括显式规则和隐式规则),在一条普通规则里面,可以有多个target文件,这些target文件共享规则的prerequisites列表。因为这个缘故,当某些targets有部分共享的prerequisites时,可以通过这种方式将这些targets写在一个规则里面。但是对于其它不是共享的prerequisites,则需要为target再单独增加规则进行指定,这就出现一个target会有多条规则的情况。
出现这个情况的根本原因在于将多个target写在一个规则里面时,它们会共享规则的prerequisites列表(哪怕这些target其实并不依赖于某些prerequisites)。静态模板规则就是为了解决这个问题而提出来的,我们可以将多个target文件写在一条静态模板规则里面,但是prerequisites列表不是写死的,而是使用静态模板匹配的方式分别生成这些targets的prerequisites列表。静态模板规则的书写格式如下:
targets ...: target-pattern: prereq-patterns ...
recipe
...
targets列表中的每个target文件都会与target-pattern模板进行匹配(对于没有进行匹配的target,会给出一个警告信息),匹配符是[ % ],target文件名中被[ % ]匹配到的部分叫做主干(stem)。通常来说,pattern中只有一个匹配符。主干替换掉prereq-patterns模板里面的[ % ]就得到这个target文件的prerequisites列表。
从静态模板的匹配方式可以看出,虽然可以在一个静态模板规则里面为不同的targets生成不同的prerequisites列表,但是这些targets文件的名称必须相似,否则无法使用target模板来做主干提取;这些prerequisites文件的名称也必须相似,否则无法使用prereq模板来做主干替换。
在prereq-patterns里面直接写死prerequisite文件名也是合法的,这个文件会成为所有target的prerequisite。
假设有如下的makefile片段:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
对于foo.o,[ % ]匹配到的主干是"foo",因此其prerequisites列表是foo.c;对于bar.o,[ % ]匹配到的主干是"bar",因此其prerequisites列表是bar.c;
假设有如下的makefile片段:
bigoutput littleoutput : %output : text.g
generate text.g -$* > $@
这里text.g会同时作为bigoutput和littleoutputd的prerequisite文件,recipe中的自动变量$*
表示匹配到的主干,对于bigoutput目标,$*
= “big”;对于littleoutput目标,$*
= “little”。
隐式规则和静态模板规则的比较
这两种规则有点类似,都是通过使用模板匹配的方式,通过target文件名来生成对应的prerequisites文件列表的,它们的区别在于make怎么决定什么时候应用规则。
当没有为target指定recipe,并且target的prerequisites文件可以找到时,才会应用隐式规则。如果有多条匹配的隐式规则可用时,只会按顺序选择一条规则来应用。
而静态模板规则会直接应用到指定的targets,如果有两条规则冲突,并且都指定了recipe,会报错。文章来源:https://www.toymoban.com/news/detail-775596.html
一般来说,静态模板规则更优于隐式规则,因为我们可以在规则里面指定匹配方式并且可以直接给出recipe。而隐式规则对我们来说其实并不具体(特别是我们不太清楚目录下有些什么东西时),我们不知道它具体匹配到了什么prerequisites,使用了什么recipe,这些不确定性因素的引入可能会埋下一些潜在的隐患。文章来源地址https://www.toymoban.com/news/detail-775596.html
到了这里,关于二、写规则(Rules)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!