在学习该篇文章之前,首先你需要有一定的makefile理论基础,如果不懂可以参考:Makefile教程(绝对经典,所有问题看这一篇足够了)建议看2遍以上。其中关于include可以参考Makefile 中的 include (依赖文件) 的执行过程。
在Linux C语言开发的时候我们需要自己手动编译新添加的文件。如果一个工程非常庞大这样将会是程序员的噩梦。使用 -MM
选项获取文件的依赖,并且自动推导文件依赖关系。
Demo1:以.c和.h在同一个目录来讲解
首先使用 cc -MM
分析当前目录下面所有的源文件的依赖关系,并且把结果生成到对应的.d文件中
运行结果:可以看到对应的.d文件,以及.d文件的内容就是对应cpp的依赖项。
此时可以看见.d的文件内容就是我们需要的文件依赖关系,并且可以直接通过cc编译。所以如果我们需要自动编译生成主程序main,我们首先分需要析一下依赖关系:
- main 需要依赖 *.o
- *.o可以通过include *.d文件生成
- *.d通过
cc -MM
生成
所以推导的makefile文件内容如下:
这样我们就可以每次执行make,自动更新依赖并且编译文件了。
所以最终的makefile如下:
Demo2:.c 在src目录,.h在include目录,src、include、makefile同级
Demo1和Demo2的区别就是是否需要指定 -I选项,指明头文件的位置。
注意事项:$<,$@,$^
使用这两个变量的时候获取到的目标是带有相对路径的。
所以和demo1相比我们需要处理.d的生成方式。变化主要两点:
-
.d
的内容需要指定 -I
参数(通过sed
命令修改 -MM
生成的.d
内容) -
.d
位置不会和.c
同目录,将会和makefile同目录(为什么不将.d
和.c
同目录是因为在make目录执行的时候 -I 参数为 ./include
,但是在src目录 -I参数为 ../include
,为了保持参数一致,所以将.d和make保持同级)
$(@F),$(<F)
:目标文件的文件名称
$(@D),$(<D)
:目标文件的相对目录位置
$*
:目标模式中"%"及其之前的部分,eg:test%.d 匹配的是 testmain.d那么$*
表示testmain
sed -e 's,^.*:,$*.o:,g'
将‘,’替换成’/'就可以看出是一个s/parten1/paretn2/g的替换模式
sed -e 's/^.*:/$*.o:/g'
所以上述语义 以main.d为例,则替换之后的正则表达
式为
sed -e 's/^.*:/main.o:/g'
eg:$@为./src/main.o
则$(@F)为main.o
,$(@D)为./src/
至于sed
的用法请百度,sed用法很灵活。所以最终的makefile内容为:
编写Makefile注意事项:
对于动态依赖的文件(*.o)不要随便更改位置,否则容易导致看似依赖文件存在却找不到的错误
demo:将生成的.o文件放在bin目录,bin和src,include同级
运行结果:第一次没有找到.o文件,导致编译失败。此时在执行一次,因为.o文件实际已经存在在bin目录,所以第二次成功了(此时大家肯定疑问,我明明指定了vpath的啊,为什么会失败呢)
为了弄清楚原因,我们把执行日志打印出来,看看两次有什么区别
此时运行一下make,查看$(o_obj)
的输出(注意我们上文提到$<,$@,$^是相对路径的位置输出
),所以为什么make失败显而易见,因为$(o_obj)
是当前目录下的*.o
此时我们再次运行一次make,看看$(o_obj)
的输出,此时为什么make成功,显而易见,因为$(o_obj)
是./bin/
目录下的*.o
,这个目标文件我们在第一次make的时候已经生成了。
此时我们可能会有疑问为什么第一次没有在指定的vpath里面寻找*.o
呢?事实上是查找了的,只是没找到而已,在make的过程中我们需要知道:一旦文件位置被定位了,在整个make的执行过程中都不会改变
。下面我们来分析一下:make关于.o查找的执行过程。
第一次 make:
—>到$(o_obj)= add.o sub.o main.o
指定的位置查找即当前目录查找,没找到
—>到vpath指定的目录查找,没找到
—>最终没找到,所以$(o_obj)
,最终被定位的位置为当前目录
所以虽然后面动态生成了.o但是.o文件位置并不在当前目录,所以make失败
第二次 make:
—>到$(o_obj)= add.o sub.o main.o
指定的位置查找即当前目录查找,没找到
—>到vpath指定的目录查找,找到OK
—>所以$(o_obj)
,最终被定位的位置为./bin/
所以此时第二次make是成功的
为了验证上述make关于.o查找的执行过程是否正确,我们把./bin目录下的.o文件拷贝到make同级目录,此时.o文件将会存在两份,位置分别位于当前目录和./bin。此时我们make -n查看一下*.o的定位位置。
结果符合预期 $(o_obj)
指定位置优先,之后才是vpath位置。
所以需要解决上述看似存在却找不到的bug很简单,只需要将上述第15行代码
修改为:
不要随便更改目标依赖文件的位置
///
MakeFile的递归调用
假设目录结构如下:
add目录makefile内容如下:
main 目录下的makefile如下:
此时我们可以递归调用make clean,执行过程如下:
此时不能使用include,因为include会展开makefile,此时会出现两个clean伪目标,会报错。include执行环境是以main目录下的makefile环境为主。