前面我们涉及到的makefile,都只是考虑到目标文件(.o)依赖于源文件(.c)。然而实际情况却并没有这么简单,我们的源文件一般都是会包含一些自己编写的头文件的,这样的话%.o : %.c这种模式规则的写法是不是就有问题了呢?当源文件不改变而头文件改变时,make解释器是无法根据文件的新旧关系来决定重新编译的,即改变头文件,make不会重新编译。
编写func.h func.c main.c三个文件的内容分别如下:
makefile程序以及以上三个文件的依赖关系如下:
执行make,可以得到正确的可执行文件,修改func.h中的宏定义,将HELLO宏改为"Hello Makefile",此时,再次执行make,提示文件是最新的,如下所示:
make解释器根据文件新旧关系判断出并不需要重新编译,但是,这并不是我们想要的结果,那么,有什么好的解决方案吗?想必大家首先想到的就是将func.h加入到依赖关系中去,这样头文件的改变,make就能聪明的感知到。
修改makefile如下:
上图中将func.h加入到了依赖关系中,同时将$^改为$<,因为gcc编译生成目标文件时-c选项只能对应一个文件,而且gcc在编译时是不考虑.h头文件的。
执行make,输出如下,可见得到了正确的可执行文件:
下面我们来考虑一下这种解决方案存在的问题:
1、头文件作为依赖条件出现在每个目标对应的规则中(即使这个目标文件与该头文件没有任何关系)。
2、当头文件改动,任何源文件都将被重新编译(编译低效)。
3、当项目中头文件数量巨大时,makefile将很难维护。
有没有什么更好的解决方案呢,我们的一个疯狂想法是:
1、通过命令自动生成对头文件的依赖。
2、将生成的依赖自动包含进makefile中。
3、当头文件改动后,自动确认需要重新编译的文件。
为了完成这个疯狂的想法,我们先来进行一些预备知识的学习。
Linux的sed命令,其具体表述如下:
sed是一个流编辑器,可以对输入输出流进行编辑,我们只用到了字符串替换这一项功能,替换方式为 sed 's:src:des:g',其中,s和g是固定格式,src为待替换的字符串,des为替换后的字符串。
演示结果如下:
sed还支持正则表达式,如下所示:
演示结果如下:
(.*).o[ :]*匹配以.o结尾的文件名,包括它的路径结构,后边的[ :]表示文件名后有任意空格或者冒号(:)也可以进行匹配。替换部分则表示在匹配到的文件名前加上前缀objs/。
Linux中的gcc关键编译选项,具体描述如下:
演示如下:
-M选项列出目标文件所有的依赖,包括系统提供的头文件。而-MM选项只列出一些简单依赖,包括我们自己编写的一些文件。使用gcc -MM -E func.c还可以提高效率。
下面我们将gcc的这个特殊选项和sed命令结合在一起,看一下输出结果:
下面介绍一个小技巧:拆分目标的依赖
makefile和执行结果如下所示:
可见,目标的完整依赖可以拆分为部分依赖,而不会改变最终的依赖关系(即拆分后test依旧是依赖a b c这三个伪目标),也不会影响最终行为。依赖中如果存在伪目标,那么伪目标对应的规则一定会被执行,如果依赖中存在伪目标但是该伪目标并没有对应的规则,则默认认为该伪目标已经存在,即依赖条件(伪目标a b c)默认满足。
将目标的完整依赖拆分为多个部分依赖,则可以将同一个目标的依赖写在多个地方,甚至是写在不同的文件中。
有了以上的预备知识,我们离上述提到的三个疯狂目标更近了一步,更深入的知识将在下一篇讲解。
参考如下:
狄泰软件教程及课件
gun make手册
专业嵌入式软件开发