makefile增量编译(生成依赖关系)

1      Makefile基本用法

1.1      常用符号

1.1.1        编译器

CC   // C语言编译器,默认值为gcc 默认的变量,无需用户自定义,也可以改变其值

CXX  // C++语言编译器,默认值为g++ 默认的变量,无需用户自定义,也可以改变其值

CFLAGS   // C语言编译器的编译选项

LDFLAGS  // C语言编译器的链接选项

CXXFLAGS // C++语言编译器的编译选项

1.1.2        自动化变量

$@ 代表规则中的目标文件名。如果目标是一个文档(Linux中,一般称.a文件为文档),那么它代表这个文档的文件名。在多目标的模式规则中,它代表的是哪个触发规则被执行的目标文件名。

$% 规则的目标文件是一个静态库文件时,代表静态库的一个成员名。例如,规则的目标是“foo.a(bar.o)”,那么,“$%”的值就为“bar.o”,“$@”的值为“foo.a”。如果目标不是函数库文件,其值为空。

$< 规则的第一个依赖文件名。如果是隐含规则,则它代表通过目标指定的第一个依赖文件。

$? 所有比目标文件更新的依赖文件列表,空格分割。如果目标是静态库文件名,代表的是库成员(.o文件)的更新情况。

$^ 规则的所有依赖文件列表,使用空格分隔。如果目标是静态库文件名,它所代表的只能是所有库成员(.o文件)名。一个文件可重复的出现在目标的依赖中,变量“$^”只记录它的一次引用情况。就是说变量“$^”会去掉重复的依赖文件。

$+ 类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时,库的交叉引用场合。

$(@D) 代表目标文件的目录部分(去掉目录部分的最后一个斜杠)。如果“$@”是“dir/foo.o”,那么“$(@D)”的值为“dir”。如果“$@”不存在斜杠,其值就是“.”(当前目录)。注意它和函数“dir”的区别!

$(@F) 目标文件的完整文件名中除目录以外的部分(实际文件名)。如果“$@”为“dir/foo.o”,那么“$(@F)”只就是“foo.o”。“$(@F)”等价于函数“$(notdir $@)”。

$(%D),$(%F) 当以如“archive(member)”形式静态库为目标时,分别表示库文件成员“member”名中的目录部分和文件名部分。它仅对这种形式的规则目标有效。

$(<D),$(<F) 分别表示规则中第一个依赖文件的目录部分和文件名部分。

$(^D),$(^F) 分别表示所有依赖文件的目录部分和文件部分(不存在同一文件)。

$(+D),$(+F) 分别表示所有依赖文件的目录部分和文件部分(可存在重复文件)。

$(?D),$(?F) 分别表示被更新的依赖文件的目录部分和文件部分。

是通配符,%.c表示工程里的.c文件

命令行以'@'打头的含义: 在执行到的时候不回显相应的命令内容,只显示命令的输出。

“-” 命令行以'-'打头的含义: 在执行到的时候如果发生错误(退出返回非零状态)时,不中断make过程。

“+” 命令行以'+'打头的含义: makefile中以+开头的命令的执行不受到 make的-n,-t,-q三个参数的影响。我们知道,在make的时候,如果加上-n, -t, -q这样的参数,都是不执行相应命令的,而以'+'开头的命令,则无论make命令后面是否跟着三个参数,都会被执行。

1.2      变量

1.2.1        变量定义

OBJ = ../objs

make变量(Makefile中定义的或者是make的环境变量)的引用使用“$(VAR)”格式。

1.2.2        变量赋值

= 当它的右边赋值是变量时,这个变量的定义在本条语句之前或之后都可以,即可以递归展开。

:= 它右边赋得值如果是变量,只能使用在这条语句之前定义好的,而不能使用本条语句之后定义的变量,即不可以递归展开。

?= 该符号左边的变量,如果在本条语句之前没有定义过,则执行本语句,如果已经定义,那么本语句什么都不做。

+= 是添加等号后面的值

1.2.3        变量操作

$(VAR:A=B)  同${VAR:A=B}

${VAR:A=B} 替换变量“VAR”中所有“A”字符结尾的字为“B”结尾的字。“结尾”的含义是空格之前(变量值的多个字以空格分开)

1、wildcard : 扩展通配符 src=$(wildcard *.c ./sub/*.c) 取当前路径和sub路径下的所有.c

                                   src = a.c b.c ./sub/c.c ./sub/d.c 

2、notdir : 去除路径    file=$(notdir $(src))

                                          file = a.c b.c c.c d.c

3、dir:获取路径         dir=$(dir $(src))

                                   dir = .  .  ./sub  ./sub

4、patsubst :替换通配符  obj=$(patsubst %.c,%.o,$(file) ) 可用于构造目标文件

                                    obj = a.o b.o c.o d.o

5、sed:替换指定字符串 sed 's/要替换的字符串/新的字符串/g'   (要替换的字符串可以用正则表达式),可以用 –i 直接替换文件内容

6、grep:(global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种文本搜索,使用正则表达式搜索文本,并把匹配的行打印出来。

grep "\$(COMPONENT_NAME)_SRC =" $MK_PATH/$1.mk >> makefiles/$1_files.mk

1.3      Makefile的编写

Makefile的作用就是实现自动化编译,它关系到了整个工程的编译规则。一个工程中可能包含很多的源文件,并且按功能,模块等分别存放在特定的目录中,makefile定义了一系列的跪着来指定哪些文件需要先编译,哪些文件需要后编译,哪些要重新编译,以及更复杂的操作。Makefile写好之后,只需要一个make命令,整个工程就可以自动编译,极大地提高了软件开发的效率。

1.3.1        依赖关系

Makefile其实就是为了指定文件依赖性的问题,下面的例子中,target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),作为一个伪目标存在。下面的command就是make要执行命令,可以是编译或者链接命令或者是任意的shell命令,但是命令前必须用tab缩进。Make会把include指令所指定的任何文件视为一个需要更新的工作目标(GNU+Make项目管理第三版P43)

target ... : prerequisites ...

command

...

1.3.2        工作方式

当我们输入make 命令时:

1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件。

2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。

3、如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。

4、如果edit所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)

5、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件edit了。

通常makefile的最后会有一个clean目标,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显式要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。

1.3.3        自动生成依赖关系

在19B的编译过程中,每次编译都需要执行clean命令清除所有的所有的目标文件,否则,新的代码修改就不会生效,极大地降低了编译效率,通常这种情况的原因是makefile的依赖关系不全或者依赖关系有误。

对19B的编译过程进行梳理发现其依赖关系中仅对.c文件进行了依赖,没有将头文件包含进去,这样当头文件更改时,不会触发任何编译。所以出现了必须clean重编译的效率问题。

完善所有目标文件的依赖关系工作量是巨大的,好在gcc为我们提供了自动生成依赖关系的选项。

定义make-depend程序自动生成依赖规则:

b1c6625872d69cc48103_481x104.png@900-0-90-f.pnguploading.4e448015.gif转存失败重新上传取消b1c6625872d69cc48103_481x104.png@900-0-90-f.pnguploading.4e448015.gif转存失败重新上传取消b1c6625872d69cc48103_481x104.png@900-0-90-f.pnguploading.4e448015.gif转存失败重新上传取消

-MM选项可以为我们生成依赖关系,并且相比-M它省略了标准的头文件,使得依赖关系尽量不那么混乱。

-MF选项用来指定依赖关系文件,通常把目标文件的后缀.o替换成.d。当然也可以使用-MD或者-MMD自动生成类似的文件名,但是它无法加入目标文件的相对路径,只能将.d放在当前目录。

-MP选项用来指示gcc为每一个依赖文件加入假想工作目标,这样当代码重构造成某个被依赖的头文件删除时,不会出现no rule to make target deleted.h的错误,而是认为其尚未被更新,所有依赖其的目标都会被重新编译,对应的依赖文件也会重新生成,进而使deleted.h自然的从makefile中消失。

-MT选项后的字符串做为依赖文件中的工作目标,从而使目标文件可以带上路径信息。

上面只是一个宏定义的函数,我们在基本依赖关系中必须调用这个函数来使其得以执行,并在最后通过include选项将其包含进来(“-”是为了在.d不存在时不发生错误)。

299ac25872d69d36fb62_553x58.jpg@900-0-90-f.jpguploading.4e448015.gif转存失败重新上传取消299ac25872d69d36fb62_553x58.jpg@900-0-90-f.jpguploading.4e448015.gif转存失败重新上传取消299ac25872d69d36fb62_553x58.jpg@900-0-90-f.jpguploading.4e448015.gif转存失败重新上传取消

5c01c25872d69d8a90b4_211x54.jpg@900-0-90-f.jpguploading.4e448015.gif转存失败重新上传取消5c01c25872d69d8a90b4_211x54.jpg@900-0-90-f.jpguploading.4e448015.gif转存失败重新上传取消5c01c25872d69d8a90b4_211x54.jpg@900-0-90-f.jpguploading.4e448015.gif转存失败重新上传取消

这样,每个.o依赖于.c并由include来的依赖关系对其依赖规则进行补充,在第一次编译不存在依赖文件时每个.o也不存在,编译就可以生成依赖文件,在后续增量编译过程中,源文件的改变可能会导致依赖关系的改变,并会触发.o重新编译,在.o更新时会调用make-depend重新更新.d,进而完成增量编译过程中,只更新需要更新的文件,并且更新了所有需要更新的文件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章