最近在看makefile相关内容,写这篇文章记录总结最近的学习内容。
首先创建测试目录makefileTest以及相关文件:
makefile中比较重要的内容就是依赖目标、生成规则,指明了要生成的目标以及如何生成,指定好以后makefile便会根据指定的规则去做。
编写第一个版本的makefile,写出每个生成目标及其依赖规则。由于刚安装centos7,所以显示g++用不了,搜索进行安装,发现刚安装好的系统处于安全性考虑是没有联网的,导致获取安装包失败,那首先联网吧。网上搜索,发现需要修改系统的配置文件,进入以下目录:
cd /etc/sysconfig/network-scripts/
找到配置文件名,此处是ifcfg-ens33,打开此文件将ONBOOT=no修改为yes,重新启动系统,发现已经可以联网下载安装包。
使用命令 yum install gcc-c++ 下载G++安装包,这一步操作需要使用su - 命令切换到root用户获取权限进行操作。
写出了第一个版本的makefile:
1 testobj:main.o student.o dog.o family.o 2 g++ -o testobj main.o student.o dog.o family.o 3 main.o:main.cpp student.h dog.h family.h 4 g++ -c main.cpp 5 student.o:student.cpp student.h 6 g++ -c student.cpp 7 dog.o:dog.cpp dog.h 8 g++ -c dog.cpp 9 family.o:family.cpp family.h 10 g++ -c family.cpp 11 12 clean: 13 rm -rf testobj *.o
生成的最终可执行目标文件是testobj,依赖于所有的.o文件,在写最终目标生成规则的时候,需要指定最终可执行文件名以及依赖的文件名,在生成.o文件的时候,只需要指出依赖的文件名即可,makefile会自动生成与源文件同名的.o文件。
在终端执行make,输出如下:
可以看到make将执行的命令回显在终端,在当前目录下生成了源文件同名的.o文件以及终极目标,执行此目标文件可以看到测试输出:
虽然这个简单版本的makefile能够正确的编译,但是有许多地方出现了同名的文件列表重复,例如第一个依赖规则中的.o文件,如果添加一个源文件,就需要修改很多地方的文件列表,很麻烦。在makefile中可以使用变量,因此可以将重复的部份用变量表示,在使用的地方引用这个变量即可。修改如下:
1 OBJECTS:=main.o student.o dog.o family.o 2 3 testobj:$(OBJECTS) 4 g++ -o testobj $(OBJECTS) 5 main.o:main.cpp student.h dog.h family.h 6 g++ -c main.cpp 7 student.o:student.cpp student.h 8 g++ -c student.cpp 9 dog.o:dog.cpp dog.h 10 g++ -c dog.cpp 11 family.o:family.cpp family.h 12 g++ -c family.cpp 13 14 clean: 15 rm -rf testobj *.o
可以看到将所有的.o文件用变量OBJECTS来表示,使用的时候用$(OBJECTS)来引用即可。
接下来考虑makefile中每添加一个源文件就需要写出其生成规则,也是很麻烦的,那么可以考虑使用make的隐含生成规则,只写出目标.o文件依赖的.h文件,让make自己去推导生成规则找对应的源文件,修改如下:
OBJECTS:=main.o student.o dog.o family.o 2 3 testobj:$(OBJECTS) 4 g++ -o testobj $(OBJECTS) 5 main.o:student.h dog.h family.h 6 student.o:student.h 7 dog.o:dog.h 8 family.o:family.h 9 10 clean: 11 rm -rf testobj *.o
至此,看着makefile变得简单多了,那么每添加一个源文件,只需要在OBJECTS变量中添加对应的.o文件,写出对应的.o文件的依赖规则即可。
到这里,还是觉得一个文件的添加就需要改东西很麻烦,这些依赖规则能不能交给强大的make来自己实现呢?当然可以,于是接下来就要使用makefile的自动生成依赖规则了,到这里好喜欢makefile了。在开始之前需要学习几个系统函数,因为接下来会用到这些函数。
(1)wildcard
用法:$(wildcard PATTREN...) 输出与给定模式匹配的所有文件列表
那么我们可以用此函数获取当前目录下的所有源文件:$(wildcard *.cpp)
此函数会列出当前目录下所有的.cpp源文件。
(2) patsubst
用法:$(patsubst PATTERN,REPLACEMENT,TEXT) 模式替换函数
用此函数我们可以根据.cpp文件得到.o文件:$(patsubts %.cpp,%.o,$(wildcard *.cpp))
Tips:在这里*.cpp使用了通配符,表示后缀为.cpp的所有文件
那么,用上系统函数的makefile修改如下:
1 SRCFILE:=$(wildcard *.cpp) 2 OBJECTS:=$(patsubst %.cpp,%.o,$(SRCFILE)) 3 4 testobj:$(OBJECTS) 5 g++ -o testobj $(OBJECTS) 6 main.o:student.h dog.h family.h 7 student.o:student.h 8 dog.o:dog.h 9 family.o:family.h 10 11 clean: 12 rm -rf testobj *.o
接下来考虑自动生成依赖规则。首先可以使用g++编译器的-M选项来列出文件的依赖关系,使用-M选项会输入所有的文件依赖关系,包括标准库文件,因此这里使用-MM选项只输出我们自己需要的依赖关系。
要让makefile自动生成依赖规则,需要使用上述的命令,在makefile中为每个源文件.cpp,为其生成一个同名.d文件,用来描述此源文件的依赖关系,例如对于main.cpp,为其生成main.d文件,描述了main.o文件的依赖关系。还需要学习一些自动化变量:
(1)$< 规则的第一个依赖文件名
(2)$@ 规则的目标文件名
(3)$^ 规则的所有依赖文件
修改为自动生成依赖关系后的makefile如下:
1 SRCFILE:=$(wildcard *.cpp) 2 OBJECTS:=$(patsubst %.cpp,%.o,$(SRCFILE)) 3 G++:=g++ 4 5 testobj:$(OBJECTS) 6 g++ -o testobj $(OBJECTS) 7 8 %.d:%.cpp 9 $(G++) -MM $< > $@.$$$$; 10 sed 's,($*).o[ :]*,1.o $@ :,g' < $@.$$$$ > $@; 11 rm -rf $@.$$$$ 12 13 sinclude $(SRCFILE:.cpp=.d) 14 15 clean: 16 rm -rf testobj *.o
首先使用g++ -MM 将每一个.cpp文件的依赖关系输出重定向到一个文件中,这个文件为依赖目标文件名.进程号,特殊变量$$$$表示当前进程号,举个具体的例子展开就是:g++ -MM main.cpp > main.d.123456。接下来使用sed做字符替换,将第一行命令生成的文件进行处理,加入.d文件,比如将main.o:main.cpp student.h dog.h family.h处理为 main.o mina.d:main.cpp student.h dog.h family.h,接下来最后一步删除产生的临时文件。在使用时,需要将这些.d文件包含进当前的makefile中,sinclude $(SRCFILE:.cpp=.d)命令,表示将当前的.cpp源文件替换问.d文件包含进来,这样就可以使用生成的.d依赖文件了。
执行make后的结果如下:
可以看到对于每个.o文件有同名.d文件生成,打开任意一个.d文件查看其内容:
可以看到main.d中是main.o的依赖关系。
最后介绍伪目标,伪目标只是一个标签,没有依赖任何文件,可以将clean写成伪目标,声明一个伪目标的方法是将其作为特殊目标.PHONY的依赖,当输入make clean时,一些定义的指令会被执行,现修改makefile如下:
1 SRCFILE:=$(wildcard *.cpp) 2 OBJECTS:=$(patsubst %.cpp,%.o,$(SRCFILE)) 3 G++:=g++ 4 5 testobj:$(OBJECTS) 6 g++ -o testobj $(OBJECTS) 7 8 %.d:%.cpp 9 $(G++) -MM $< > $@.$$$$; 10 sed 's,($*).o[ :]*,1.o $@ :,g' < $@.$$$$ > $@; 11 rm -rf $@.$$$$ 12 13 sinclude $(SRCFILE:.cpp=.d) 14 15 .PHONY:clean 16 clean: 17 rm -rf testobj *.o
可以看到定义了clean伪目标,当输入make clean时,clean定义的指令会被执行,删除终极目标以及所有的.o文件。有时候在执行伪目标的时候不想关注是否执行成功,也不关注出错的提示消息,这时候就可以在命令前面加-来告诉make你只管执行,结果我不关注,比如:
15 .PHONY:clean 16 clean: 17 -rm -rf testobj *.o
以上就是近期对于makefile的学习笔记,没有涉及到不同目录下文件的编译以及高级用法,算是初级学习吧。
参考资料:
(1)GUN make中文手册:https://pan.baidu.com/s/1BwlD910FXWA9mOrL9uAlSA
(2)makefile教程(中文版):https://pan.baidu.com/s/1EITITRS6P0Dk41YwlMD5_g