学习目标:
1、熟悉程序的编译和链接流程
2、熟悉Makefile的工作方式
3、对Makefile的语法规则、常用函数、常用参数有一定了解
4、结合对Makefile的理论理解阅读公司项目Makefile,总结Makefile的编写框架
学习内容:
一、程序的编译和链接流程
1、首先是编译,编译器将源文件编译成中间代码文件(Object文件),即".c"源文件编译成".o"目标文件,编译器在编译过程中只关心函数、变量在头文件中的声明和源文件语法是否正确,通常在编译时使用"CFLAGS"环境变量包含源文件有关的头文件;
2、编译完成后得到大量的Object文件,这时就需要链接器将大量的Object文件链接成为可执行文件,在链接过程中只关心函数的实现即函数在Object File中的实现位置,可是如果大量的源文件就会生成大量的Object文件,函数实现的找寻就会变得极不方便,这时我们就要给Object文件打个包成为".a"文件,即我们所说的静态库;
二、熟悉Makefile的工作方式
GNU 的 make 工作时的执行步骤入下:(想来其它的 make 也是类似)
1、读入所有的 Makefile。
2、读入被 include 的其它 Makefile。
3、初始化文件中的变量(相当于编译的展开宏)。
4、推导隐晦规则,并分析所有规则(自动推导)。
5、为所有的目标文件创建依赖关系链。
6、根据依赖关系,决定哪些目标要重新生成。
7、执行生成命令。
三、熟悉Makefile的语法规则、常用函数、常用参数
1、正所谓无规不成方圆,Makefile自然也有自己的规则,首先是最终的目标文件,一般是我们要执行的可执行文件,那我们怎么生成这个最终的目标文件呢,Makefile很巧妙的引出依赖关系,即我要生成这个目标文件需要依赖文件,那如果这个依赖文件也不存在呢?没关系,我们可以通过命令生成这个依赖文件,这个命令就是我们上述说到的编译和链接了,这就很简单的连成一条线了,即相当于main.o:main.c 由编译器将.c文件编译成为.o文件,main:main.o,链接器将.o文件链接成为目标文件;
2、依赖关系的实质上就是目标文件是由哪些文件更新的;
3、Makefile中使用变量$(object)使用这个变量
4、.PHONY:clean 表示clean是一个“伪目标”,如果直接clean: 放在第一位 就会变成make的默认目标,通常是all放在第一位,作为make默认目标;
5、显式规则:目标文件:依赖文件
6、隐晦规则:由于make自动推导功能,可以简约地书写Makefile,如已知目标文件".o",则可以省略写依赖文件.c,因为make可以自动推导Main.o依赖Main.c;
7、文件指示符号:类似C语言的include头文件、还有根据某些情况指定有效情况类似#if #else等、还有就是定义多行的命令如XXX|XXX|XXXX|;
9、Makefile命令中,必须要以【Tab】键开始,以便Makefile识别哪一个是命令
10、最好使用"Makefile"文件名、若要指定特定的Makefile、可以使用"-f"参数 如:make -f Make.Linux
11、-include $(DEP) make会包含DEP所在的目录,即包含依赖文件.d,并且-表示即使无法读取这个目录,不会报错继续执行;
12、objects := $(wildcard *.o) object的值是所有.o文件名的集合即 Main.o mq.o Cjson.o等
13、特殊变量"VPATH"类似Linux的环境变量,若在当前目录没有找到依赖文件或者目标文件,则会去VPATH定义的路径去寻找 如:VPATH = src:…/headers
14、关键字vapth 更灵活,可以搜寻目录下特定的文件 如:vpath %.c ajb_module_uart
15、若要生成多个可执行文件,可以使用伪目标特性:总是被执行的,即.PHONY:all all:prog1 prog2 prog3 all总是会被执行,即三个依赖文件也总是被重新生成
16、可以通过.PHONY: cleanall cleanobj cleandiff 达到清除不同文件类型的目的
17、静态模式:$(object):%.o:%.c 指明我们的目标从object变量中获取,目标模式%.o表示只取.o结尾的目标表示foo.o bar.o是目标,依赖模式%.c表示依赖目标是foo.c bar.c
18、$<表示依赖目标集 $@表示生成目标集
19、$(filter %.o,$(files)) 表示调用filter函数过滤,只留下%.o内容 因此可以结合静态模式实现不同类型的目标文件由.c---->.o
20、-MM 是编译器支持的一个选项,自动找寻源文件的包含的头文件,并且生成依赖关系 如cc -MM main.c 输出:main.o:main.c defs.h
而-M会把标准库的头文件也包含进来生成依赖关系
21、$(sources:.c=.d)中的.c=.d意思是将sources里面的所有[.c]子串都替换成【.d】;
22、显示命令时@字符在命令行前,可以隐藏命令、而带入参数“-n”可以显示命令,不执行命令
23、分号后命令才执行生效,即下条命令要在当前命令结果时生效,要加分号;
24、变量值得替换:“ ( v a r : . d = . o ) ” 将 v a r 中 所 有 以 . d 结 尾 的 子 串 替 换 为 . o 结 尾 类 似 的 静 态 模 式 也 是 “ (var:.d=.o)” 将var中所有以.d结尾的子串替换为.o结尾 类似的静态模式 也是“ (var:.d=.o)”将var中所有以.d结尾的子串替换为.o结尾类似的静态模式也是“(var:%.d=%.o)”
25、$(subst ( s p a c e ) , (space), (space),(comma),$(foo)) 将foo中的空格替换为逗号
26、$(patsubst %.c,%.o,x.c.c bar.c) x.c.c bar.c -------->x.c.o bar.o;
27、去空格函数strip 去掉子串的开头和结尾的空格;
28、$(foreach ,,
29、调用隐含规则:已知【.o】的依赖文件是【.c】,那么将使用C的编译命令"cc -c [.o] $(CFLAGS)" 来生成[.o]目标,而不必用户明写;
30、模式规则:一旦依赖目标中的%确定后,make会被要求去匹配当前目录下所有的文件名,一旦找到则执行对应规则生成目标文件
31、$< 依赖目标中的第一个目标名
32、使用-MM选项生成文件的依赖关系 保存在.d文件中,生成的.d依赖文件包含.o目标文件的依赖关系,如:main.o:main.c cJson.h等,一旦增加.h文件或者减少.h文件都不用在Makefile中修改make会将旧的.d删掉重新生成新的依赖关系写入.d文件,编译得到新的.o文件,不用自己手动修改
33、-MT指定依赖规则中的目标;
四、结合阅读公司项目Makefile,总结Makefile的编写框架
1、#第一步是写好真正的Makefile->config.mk 指定好整个工程项目的源文件路径(即每个模块的路径 如 MODULE_NET=$(PWD)/src/ajb_module_net)、库文件(包括第三方库、动态库、静态库)、需要编译模块的变量、库文件变量、头文件变量、CFLAGS编译器参数、LDFLAGS链接器参数、编译器的名字、最终目标文件的路径、打印的字体格式等
2、#第二步是将外部的共享文件COMMON中的源文件,如mq.c等源文件 编译为.o、.d文件存放在OBJ文件中;主要是调用patsubst、foreach函数和Makefile的编译链接规则根据依赖关系生成.o和.d文件,并且包含依赖文件.d到Makefile中;
注意:
既然生成了依赖关系那么就需要存放在一个地方,这时就会通过-MF -MM生成依赖文件列表,而这个列表又能以-include 或者include 的形式添加到Makefile中
编译选项:
-MM 代表编译器会找寻源文件的头文件,并且生成依赖关系
-MF 指定存放的文件名
-MT 指定依赖关系的目标
如:
DEP := $(patsubst %.o,%.d, $(OBJ))
-include $(DEP)
$(OBJ_DIR)/%.d: %.c
$(PRINTF) “DEP
@
"
;
@";
@";(CC)
(
C
F
L
A
G
S
)
−
M
M
−
M
F
"
(CFLAGS) -MM -MF"
(CFLAGS)−MM−MF"@” -MT"$(@:.d=.o)" $<
例如生成的依赖关系:main.d main.o = main.c cJson.h
①:MM生成依赖关系;
②:"(OBJ_DIR)/%.d"则是依赖关系存放的文件名;
③:"(OBJ_DIR)/%.o"则是依赖关系的目标;
④:$(CFLAGS)编译器的选项 指定头文件的路径
⑤:-include $(DEP)则是将这个依赖文件列表存放在Makefile中,以便下次更新;
3、#第三步是处理Src内部的模块,与上述同理,编译获得.o、.d文件放在OBJ文件中
4、最终是将OBJ目录下的所有.o文件和库文件(包括动态库、静态库、线程动态库和一些优化参数) 通过链接成为TARGET
整篇文档的编写参考了陈皓老师的跟我一起写Makefile(仅阅读了一遍,相信以后也要回头再看几遍才能深入领悟一些东西)和公司项目Makefile,并结合了自己对Makefile的理解,以及自己的学习历程。因为自己本身对Makefile并没有深入的了解,平时也只会敲命令make 、make clean、 make distclean 并不了解其中的运行原理,是直到有一个新项目需要移植语音模块、所以通过两天阅读和结合项目的Makefile,才初步对Makefile有一个更进一步的理解,那么一定会有很多地方存在表达问题,语言歧义或是错误。因些,我迫切地得等待各位给我指证和建议,以及任何的反馈、并且也希望大家能动手去实践,即使初始不能自己写出一个完好的Makefile也能完成一些小任务去修改Makefile里面的一些东西,通过打印去体验脚本的乐趣;