目录
makefile简介
一个工程中的源文件不计其数,其按类型,功能,模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个shell脚本一样,也可以执行操作系统的命令。(摘抄百度百科)
程序的编译与链接
程序的编译与链接:
可执行程序的生成经过两个过程:编译过程和链接过程
以生成helloworld.exe可执行程序为列:
程序文件的分类:
程序文件分为两大类:BIN文件和ELF文件
BIN文件的全称是Binary file,该文件为可执行的二进制文件
ELF文件的全称Executable and Linkable Format,该文件为可执行与可链接文件
ELF文件又分为:
1.可执行文件
2.可重定位文件(目标文件)
3.共享库文件
使用指令 readelf -h “elf-文件名” 查看elf文件的表头
使用指令 file 文件名 查看文件的类型
静态库和动态库 :
静态库是事先被加载到可执行文件中的;
当可执行文件运行时,才会动态地去加载动态库,这个过程有点像函数调用。动态库的好处之一可以节约内存空间。
makefile基本语法
makefile文件的主要内容:
规则:
-----构成makefile的基本单元,构成依赖关系的核心部件
-----其他内容可以看作为规则服务
变量:
-----类似于c语言中的宏,引用:$(VAR)或者${VAR}
-----可以让makefile更加灵活
条件执行:
-----根据某一变量的值来控制make执行或者忽略makefile的某一部分
函数:
-----文本处理函数:字符串替换,查找,过滤,排序,统计等
-----文本名处理函数:取目录/文件名,前后缀,加前缀/后缀,单词连接等函数
-----其它常用函数:if函数,shell函数,foreach函数(for函数)
文件包含:
-----类似于c语言的#include,使用include命令
注释:
-----使用#开头,表示注释
依赖关系树:
makefile的目的:
------构建依赖关系树
如何表示依赖关系树:
------可以自己画图表示依赖关系树
依赖关系树的生命周期:
------解析阶段载入内存
------运行阶段根据其进行编译,根据时间戳生成文件
------有新文件添加,减少会动态改变依赖关系树
makefile基本单元:规则
规则基本构成:
目标: 目标依赖
命令
注意事项:
-------命令必须使用tab键开头,一般是shell命令
-------一个规则中可以无目标依赖,仅仅实现某种操作
-------一个规则中可以没有命令,仅仅描述依赖关系
-------一个规则中必须有一个目标
目标:
默认目标:
-------一个makefile里面可以有多个目标
-------一般会选择第一个作为默认目标(使用make指令编译的就是默认目标)
多目标:
-------一个规则中可以有多个目标
-------多个目标具有相同的生成命令和依赖文件
多规则目标:
-------多个规则可以是同一个目标
-------make在解析时,会将多个规则的依赖文件合并
伪目标:
-------并不是一个真正的文件名,可以看做是一个标签
-------无依赖,相比一般文件不会去重新生成,执行
-------伪目标,可以无条件执行
目标依赖:
文件时间戳:
-------根据时间戳来判断目标依赖文件是否更新
-------所有文件编译过,则对所有文件编译, 生成可执行程序
-------在上次make之后修改过的c文件,会被重新编译
-------在上次make之后修改过的头文件,依赖此头文件的会被重新编译
自动产生依赖:
gcc -M 命令生成该文件要依赖的文件
gcc -MM命令生成自己写的文件要依赖的文件
隐式申明:
依赖文件为.c文件,隐式申明会直接生成目标文件.o文件
生成命令:
命令的组成:
-------shell命令组成,tab键开头
命令的执行:
-------每条命令,make会开一个进程
-------每条命令执行完,make会检测每个命令的返回码
-------若命令返回成功,make继续执行下个命令
-------若命令执行出错,make会终止执行当前规则,退出
并发执行命令:
make -j4 表示有4个进程并发执行命令
命令同一进程执行
makefile中的变量
变量基础:
变量定义:
------ CC = gcc
变量赋值:
------ 追加赋值:+=
------ 条件赋值: ?=
变量引用:
------ $(CC) ${CC}
变量分类:
立即展开变量:
------使用:=操作符赋值
------在解析阶段直接赋值常量字符串
延迟展开变量:
------使用=操作符赋值
------在运行阶段,实际使用变量时再进行求值
注意事项:
------一般在目标,目标依赖中使用立即展开变量
------在命令中一般使用延迟展开变量
目标变量:
一般变量:
------默认为全局变量
目标变量:
------该目标所依赖的规则中都可以使用
使用目标变量用途:
------做到文件级的编译选项
模式变量:
目标变量:
------变量可以定义在某个目标上
模式变量:
------变量可以定义在符合某种模式的目标上,例如: %.o : N = 3
自动变量:
------自动变量是局部变量
目标:
------$@
所有目标依赖:
------$^
第一个依赖:
------$<
使用举例:
------gcc -o $@ $^
系统环境变量:
作用范围:
------变量在make开始运行时被载入到makefile文件中
------对所有的makefile都有效
------若makefile中定义同名变量,系统环境变量将被覆盖
------命令行中传递同名变量,系统环境变量将被覆盖
常见的系统环境变量:
------CFLAGS
------SHELL
------MAKE
变量的传递:
makefile在多目录下递归执行
$(MAKE) -C subdir
cd subdir && $(MAKE)
通过export传递变量
通过命令行传递变量
makefile中的条件判断
条件执行:
关键字:ifeq ,else ,endif
使用:条件语句从ifeq开始,括号与关键字用空格隔开
例如:有时候可以根据条件判断到底生成debug版本还是release版本
makefile中的函数
一些函数的使用:
foreach函数的使用:
$(foreach var text command)
var:局部变量
text:文件列表,空格隔开,每一次取一个值赋值为变量var
command:对var变量进行操作,每次操作结果都会以空格隔开
shell函数的使用:
makefile的执行过程
执行过程:
进入编译目录,执行make命令
依赖关系解析阶段
命令运行阶段
依赖关系解析阶段:
1.解析makefile,建立依赖关系图
2.控制解析过程:引入makefile,变量展开,条件执行
3.生成依赖关系树
命令执行阶段:
1.把解析生成的依赖关系树加载到内存
2.按照依赖关系,按顺序生成这些文件
3.再次编译make会检查文件时间戳,判断是否过期
----若无过期,不再编译
----若文件有更新,则依赖该文件的所有依赖关系上的目标重新更新,编译生成
makefile隐含规则
隐含规则:
make默认将.c文件编译成对应的.o目标文件
对没有命令行的规则:寻找一个隐含的规则来执行
取消隐含规则:使用 -r或-R参数取(在命令行输入 make -r)
隐含变量:
命令变量:
CC: 编译程序,默认是cc
AS:汇编程序,默认是as
CXX:C++编译程序,默认g++
AR:函数库打包程序,默认是ar
命令参数变量:
CFLAGS:执行CC编译器的命令行参数
CXXFLAGS:执行g++编译器命令行参数
ASFLAGS:执行汇编器AS命令行参数
ARFLAGS:执行AR命令行参数
更多参数:LDFLAGS,CPPFLAGS
模式规则:
使用模式规则来定义一个隐式规则:
1.模式规则,至少在规则的目标定义中包含%
2.%任意长度的非空字符串,表示对文件名的匹配
3.在目标文件名中,%匹配的部分称为“茎”
目标和目标依赖同时含有%:
1.依赖目标的茎会传给目标
多目标模式规则:
1.同一个模式规则可以存在多个目标
2.普通多目标规则:每个目标作为一个独立规则处理;多个目标对应多个独立规则
3.多目标模式规则:所有规则目标共同拥有依赖文件和规则的命令行,当文件符合多个目标模式中的任何一个时,规则定义的命令执行
使用makefile构建MP3项目工程
自动添加目标对头文件的依赖
从关系依赖树中可以看出makefile生成的可执行文件并不依赖头文件,所以当我们修改头文件,可执行文件不会重新生成。
将usb.o:usb.c mp3.h规则加入到makefile中,makefile对有相同目标文件的规则会将依赖文件合并起来处理。
如下:
usb.o:usb.c
gcc -o usb.o -c usb.c
usb.o:usb.c mp3.h
使用make编译时发生了下面的错误:
使用gcc编译的时候,如果加了-c参数时后面不能跟多文件。因为编译是按文件编译的。
因此使用filter函数将.c文件过滤出来
使用gcc -MM usb.c可以直接将依赖关系打印出来,并重定向到usb.d文件
在makefile中使用include usb.d将文件包含进来
也可以将gcc -MM命令加入到makfile中
使用目录管理源文件
使用指令mkdir创建源目录,使不同的源文件存放在不同的目录下,最后使用命令tree查看,如下图:
将makefile拷贝至源文件模块目录下,同时清除BIN变量,并在伪目标all中添加变量OBJS
对于player.c文件目录下的makefile需要将BIN变量等于mp3最终目标文件
执行该makefile会显示 错误,找不到该头文件。
出现该错误的原因是该头文件已经不再当前目录了,头文件被放在模块文件目录下。在头文件中加入该头文件的相对目录,如下图:
再次使用make编译出现如下错误,提示找到不到模块函数:
打开makefile检查发现,在当前目录下只有player.c文件,只能生成player.o。手动添加目标文件,如下图:
模块文件的makefile生成对应的目标文件,和头文件依赖文件。
而程序应用的makefile生成对应的目标文件,和头文件依赖文件,以及最终的应用程序文件。
因此可以写一个包含他们相同内容的common.mk。
模块文件的makefile只需要包含该改commom.mk即可
使用tree查看目录分级
同时,如果去每个模块文件下编译比较麻烦。所以可以再主目录下编译一个makefile自动编译。
使用目录管理目标文件
由于目标文件放在不同的目录下,所有在生成app程序时,需要手动将目标文件添加到规则中。不是很方便。在app目录下新建一个link_obj目录,将生成的所有目标文件都放在该目录下。
修改目标变量OBJS,同时修改生成该目标的规则。
使用make执行,出现如下错误:
找不到对应的函数,链接的时候模块目标文件没有被链接进去。因为目标文件都被放在了/home/xyz/makefile/mp3/app/link_obj/目录下。通过定义一个变量将目标文件加入到规则当中去。
使用make再次编译发现,player.o文件未生成,同时没有该文件也无法链接。
这是因为主makefile执行顺序是
make -C lcd
make -C usb
make -C media
make -C app
执行完前三个模块文件的makefile后会生成三个目标文件,lcd.o usb.o media.o。进入app
目录执行makefile的时候,在解析阶段会把如下规则
$(BIN):$(LINK_OBJ)
gcc -o $@ $^
中的$(LINK_OBJ)展开成lcd.o usb.o meida.o。在makefile执行的时候因为没有player.o目标依赖,最终的生成的程序没有main函数报错。 解决办法将player.o添加到目标依赖中。
同时可以将mp3程序直接生成在主目录,生成在app目录下,每次都需要进入执行该程序,不是很方便。
查看.d依赖文件可以发现,.d文件中的依赖关系与comom.mk里面的依赖关系已经不一致了。使用正则表达式匹配生成相应的.d依赖文件
使用目录管理依赖文件
1.在app目录下新建一个dep文件夹,用于存放.d依赖文件
2.定义一个变量DEP_DIR存放dep文件夹路径
3.将变量DEP_DIR添加到DEPS变量中
4.修改生成.d依赖文件的规则,将变量DEP_DIR添加到目标当中
在usb.h中添加一个头文件usb20.h,如果make之后修改usb20.h的内容,依赖文件.d并不会重新生成,所以需要增加依赖关系。
例如:
/home/xyz/makefile/mp3/app/link_obj/usb.o /home/xyz/makefile/mp3/app/dep/usb.d:usb.c usb.h
同时gcc -MM $^命令中只需要c文件,因此需要使用filter函数过滤
结论:依赖文件跟位置无关,目标文件跟位置无关,依赖关系不变!
使用export定义一个BUILD_ROOT变量来存放根目录路径
修改commom.mk将BUILD_ROOT写入进去
修改app目录下的makefile
使用目录管理头文件
1.在主目录下新建一个inc目录用来存放头文件
2.将各个模块目录下的头文件移动到inc目录中,移动之后的软件层次架构如下图:
修改主makefile文件,这样gcc就能找到头文件。修改如下图:
同时commom.mk也要修改,修改如下:
将player.c中头文件的相对路径删除
正则表达式不匹配,修改如下:
支持静态库的生成和使用
1.在app目录下新建一个lib_obj文件夹,在主目录下新建一个lib文件夹
2.创建变量用来存放lib_obj文件夹的路径,创建变量用来存放lib文件夹的路径
1. 使用条件编译,默认生成模块目标文件,当要生成库文件时就生成库目标文件
2.定义一个生成库的规则
make之后出现如下错误,找到到对应的meida_init函数。这因为目标文件被生了库文件,而没有生成对应的目标文件链接到主程序中。
静态库的使用,例如:
gcc -o hello -L 库的路径 -l库的名字
修改common.mk文件,让其使用静态库生成目标程序
模式替换函数patsubst的使用
取文件名函数notdir
$(notdir $(var))
取文件名前缀部分函数basename
支持动态库的生成和使用
1.新建一个数学模块math,创建math目录,math.c文件,math.h文件,已经该模块文件中的makefile。
2.修改common.mk文件,已经修改主makefile,添加命令make -C math
动态库的使用
将math_dll_init函数添加到player.c文件当中,同时将math.h移动到目录inc下面。
修改生成BIN文件的规则如下:
支持使用第三方的静态库,动态库
1.在lib中新建ext_lib文件夹用于存放第三方的库文件。
2.在mp3目录下新建temp目录,在里面创建jpg.c jpg.h rmvb.c rmvb.h文件,将他们分别编译成静态库和动态库,当作第三方库使用。
3.将libjpg.a 和librmvb.so 拷贝到 ext_lib目录下
4. 将头文件rmvb.h和jpg.h拷贝至inc目录下
5.修改common.mk文件
6.在player.c文件中调用rmvb_init函数,jpg_init函数,并且添加头文件。
支持软件的安装与卸载
安装就是将可执行程序放在系统环境变量里面,使用echo $PATH查看环境变量。如果使用了动态库,动态库也需要拷贝到系统环境变量当中。
重构我们的makefile
在主目录下面新建一个文件夹config.mk
将一部分内容移动到config.mk文件当中,同时在makefile中添加shell脚本for循环语句
将config.mk包含到makefile中
有时为了程序的可读性,我们还会添加一些注释
在模块目录下的makefile里面写入注释
在common.mk中添加注释