目录
我们继续Makefile编写的内容学习~第一节可参考:↓传送门↓
如何编写Makefile(1)-CSDN博客
https://blog.csdn.net/L_peanut/article/details/144134136?spm=1001.2014.3001.5501 本节内容主要介绍Makefile的编写规则。
一、规则示例
a.o:a.c a.h
cc -c -g a.c
这个示例中,a.o是我们的目标,a.c和a.h是目标a.o所依赖的源文件,只有一个指令cc -c -g a.c(以Tab键开头)。规则是根本意义是:
(1)文件的依赖关系。a.o依赖于a.c和a.h,如果a.c和a.h的文件日期比a.o文件日期要新,或者a.o不存在,那么依赖关系发生;
(2)生成方法。cc命令就是生成或更新a.o的命令,其说明了如何生成a.o这个文件。
二、规则的语法
targets:prerequisites
command
...
或
targets:prerequistes;command
command
...
targets是文件名,以空格分开,可以使用通配符。一般情况下,目标基本上是一个文件,但也可能是多个文件。
command是命令行,如果其不与“target:prerequisites”一行,则必须以Tab键开头,如果和prerequisites在一行,可以用分号隔开。
prerequisites是目标所依赖的文件,如果其中的某个文件比目标文件要新,目标就被认为是“过时的”,被认为是需要重新生成的。
如果命令过长,可以使用反斜杠(\)作为换行符。make对一行中有多少个字符没有限制。一般来说,make会以UNIX的标准shell,也就是/bin/sh来执行命令。
三、通配符使用
如果我们需要定义一系列比较类似的文件,则可以使用通配符的形式来高效实现。
make支持三个通配符:*,?,~。
波浪号(~)字符在文件名中也有比较特殊的用途,如果是~/test,这就表示当前用户的$HOME目录下的test目录,而~user/test则表示用户user的宿主目录下的test目录,在Windows系统中用户没有宿主目录,波浪号所指的目录根据环境变量“HOME”来决定。
通配符代表一系列文件,如*.c表示所有后缀为c的文件。如果文件名中有通配符,如:*,可以使用转义字符\,如\*来表示真实的*字符。
clean:
rm -f *.o
也可以在clean后面加上其它指令,如希望在编译完成之后看看main.c的代码,可以:
clean:
cat main.c
rm -f *.o
通配符也可以在我们规则当中,目标print依赖于所有的.c文件,其中的$?是一个自动化变量。
print:*.c
lpr -p $?
touch print
通配符可以用在变量中,下面的例子并不代表*.o会展开,objects的值就是*.o。Makefile中的变量类似于C/C++中的宏,如果想要让通配符在变量中展开,也就是让objects的值是所有.o的文件名的集合,可以这样:
objects:=$(wildcard *.o)
下面是变量使用通配符的一些例子:
1、objects:=$(wildcard *.c) #列出确定文件夹中的所有.c文件
2、$(patsubst %.c,%.o,$(wildcard *.c)) #列出1中所有文件对应的.o文件
3、objects:=$(patsubst %.c,%.o,$(wildcard *.c)) #由1、2两步,可以编译并链接所有.c和.o文件
a:$(objects)
cc -o a $(objects)
四、文件搜索
如果工程文件很大,会有大量的源文件,我们通常是把这些源文件进行分类,并存放在不同的目录中。所以当make需要去寻找文件的依赖关系时,可以在文件前加上路径,但更好的方法是告诉make一个路径,让其去自动寻找。
Makefile文件中的特殊变量VPATH就是完成这个功能的,如果没有指明这个变量,make只会在当前目录中去寻找依赖文件和目标文件。如果定义了这个变量,那make就会在当前目录找不到的情况下到指定的目录中寻找文件。
VPATH=src:../headers
make会按照上面定义指定的两个目录“src”和“../headers”的顺序进行搜索,目录用“冒号”隔开。
另一个设置文件搜索路径的方法是使用make的“vpath”关键字(全小写),这不是变量,而是一个make关键字,这个VPATH很类似,但是更加灵活。它可以指定不同的文件在不同的搜索目录中。其使用方法有:
vpath <pattern> <directories>
#为符合模式<pattern>的文件指定搜索目录<directories>
vpath <pattern>
#清除符合模式<pattern>的文件的搜索目录
vaptah
#清除所有已被设置好的文件搜索目录
vpath使用方法中的<pattern>需要包含%字符。%的意思是匹配零或若干字符,(如果要使用%,通过\来转义)。例如,%.h表示所有以.h结尾的文件。<pattern>指定了要搜索的文件集,而<directories>则指定了<pattern>的文件集的搜索的命令。例如:
vpath %.h ../headers
上面的命令要求make在"../headers"目录下搜索所有以.h结尾的文件。
我们可以连续使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的<pattern>,或者是被重复了的<pattern>,那make会按照vpath语句的先后顺序来执行搜索。如:
vpath %.c a
vpath % b
vpath %.c d
上面命令表示.c结尾的文件,先在“a”目录,然后是“b”目录,最后是“d”目录。
vpath %.c a:d
vpath % b
上面的语句表示.c结尾的文件,先在“a”目录,然后是“d”目录,最后是“b”目录。
五、伪目标
第一节中我们就提到过一个“clean”目标,这是一个伪目标。
clean:
rm *.o temp
我们并不生成“clean”这个文件,“伪目标”不是一个文件,只是一个label(标签)。由于伪目标不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有显式地指明这个“目标”才能让其生效。当然,伪目标的取名不能和文件名重名,不然就失去伪目标的意义了。所以,为了避免和文件名重名,我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是伪目标,向make说明,不管是否有这个文件,这个目标就是伪目标。
.PHONY:clean
只要有这个声明,不管是否有“clean”这个文件,要运行“clean”这个目标,只有“make clean”的方式。于是可以写成这样:
.PHONY:clean
clean:
rm *.o temp
伪目标一般没有依赖文件,但是也可以给伪目标指定依赖文件。伪目标也可以作为“默认目标”,只要将其放在第一个。如果你想要Makefile一次性生成多个可执行文件,通过一个make命令即可实现,所有的目标文件都写在一个Makefile中,则可以利用“伪目标”的特性:
all:test1 test2 test3
.PHONY:all
test1:test1.o other.o
cc -o test1 test1.o other.o
test2:test2.o
cc -o test2 test2.o
test3:test3.o other.o
cc -o test3 test3.o other.o
上面示例中声明了一个“all”的伪目标,其依赖其它三个目标,同时“all”将被作为make的默认目标。由于默认目标的特性是总会被执行,但“伪目标”只是一个标签不会生成文件,所以不会产生“all”的可执行文件。.PHONY:all声明了“all”这个目标为“伪目标”。这个例子也说明,目标也可以成为依赖,所以伪目标也可以成为依赖,如下所示。
.PHONY:cleanall cleanobj cleandiff
cleanall:cleanobj cleandiff
rm program
cleanobj:
rm *.o
cleandiff:
rm *.diff
"make cleanall"清除所有文件,“cleanobj”和“cleandiff”这两个伪目标有点类似于子程序,我们可以在命令行输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来实现清理不同类型的文件。
六、多目标
Makefile支持多目标,且很可能多个目标同时依赖一个文件,并且生成的命令大体类似,我们可以将其合并。多个目标的生成规则的执行命令也可以不是同一个,我们可以使用自动化变量$@来实现,这个变量表示目前规则中所有的目标的集合。如下例子所示。
fastwrite slowwrite:test
generate test -$(subst write,,$@) > $@
上面语句等价于
fastwrite:test
generate test -fast > fastwrite
slowwrite:test
generate test -little > littlewrite
其中,-$(subst write,,$@)中的$表示执行一个Makefile的函数,函数名为subst,后面的为参数,作用是替换字符串,$@表示目标的集合,就像一个数组,$@依次取出目标,并执行命令。
七、静态模式
Makefile的静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加灵活。语法如下:
<targets ...>:<target-pattern>:<prereq-patterns ...>
<commands>
...
targets定义了一系列的目标文件,可以有通配符,是目标的一个集合。
target-pattern指明了targets的模式,也就是目标集模式。
prereq-patterns是目标的依赖模式,它对target-pattern形成的模式再进行一次依赖目标的定义。
比如说,<target-pattern>定义为%.o,意思是<target>;集合中都是以.o结尾的,如果<prereq-patterns>定义为%.c,意思是对<target-pattern>所形成的目标集进行二次定义,计算方法是:取<target-pattern>模式中的%(去掉.o),为其加上.c作为结尾,形成新的集合。
objects=a.o b.o
all:$(objects)
$(objects):%.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
上面的例子指明了我们的目标从$objects中获取,%.o表明要所有以.o结尾的目标,而依赖模式%.c则取%.o的%,也就是a b,并为其加上.c的后缀,即我们的依赖目标是a.c b.c。命令中的$< 和$@是自动化变量,$<表示第一个依赖文件,$@表示目标集(即a.o b.o)。于是将这些规则展开后等价于:
a.o:a.c
$(CC) -c $(CFLAGS) a.c -o a.o
b.o:b.c
$(CC) -c $(CFLAGS) b.c -o b.o
如果我们有几百个%.o文件,只要使用这种“静态模式规则”就可以写完一堆规则,极大提升效率。
files=a.elc b.o c.o
$(filter %.o,$(files)):%.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)):%.elc: %.el
emacs -f batch-byte-compile $<
$(filter %.o,$(files))表示调用Makefile的filter函数,过滤“$files”集,只要其中模式为%.o的内容。
八、自动生成依赖性
Makefile中,依赖关系可能需要包含许多头文件,例如main.c中有一句#include "circle.h",那我们的依赖关系为
main.o:main.c circle.h
对于大型工程,需要了解哪些C文件包含哪些头文件,并且在加入或删除头文件时,都需要修改Makefile。为了避免这种繁杂的事务,我们可以使用C/C++编译的一个功能,即编译器的“-M”选项,添加该参数后可以自动寻找源文件中包含的头文件,并生成一个依赖关系。例如:
cc -M main.c
该语句输出为:
main.o:main.c circle.h
我们可以写出.c文件和.d文件的依赖关系,并且让make自动更新或生成.d文件,并将其包含在我们的主Makefile文件中,这样就可以自动化生成每个文件的依赖关系了。
%.d:%.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
这个规则的意思是,所有.d文件依赖于.c文件,rm -f $@表示删除所有的目标,也就是.d文件;第二行的意思是为每个依赖文件$<,也就是.c文件生成依赖文件,$@表示模式%.d文件,如果有一个C文件是name.c,那么%就是name,$$$$是一个随机编号,生成的文件可能是"name.d.12345";第三行使用sed命令做了一个替换;第四行是删除临时文件。
总之,这个模式要做的事就是在编译器生成的依赖关系中加入.d文件的依赖,把依赖关系:
main.o:main.c circle.h
转换为
main.o main.d:main.c circle.h
这样.d文件也会自动更新,并自动生成。我们还可以在.d文件中加入不只依赖关系,包括生成的命令也可以一并加入,让每个.d文件都包含一个完整的规则。我们可以使用Makefile的“include”命令,来引入其它Makefile文件:
sources=a.c b.c
include $(sources:.c=.d)
上面语句中的$(sources:.c=.d)中的.c=.d的意思是做一个替换,把变量$(sources)所有.c的字符串替换成.d。需要注意的是,因为include是按照次序载入文件的,最先载入的.d文件中的目标会成为默认目标。
-书写规则&spm=1001.2101.3001.5002&articleId=144189831&d=1&t=3&u=93c6cc3f42394be58b27348134392f5c)
1054

被折叠的 条评论
为什么被折叠?



