目录
一、Makefile是什么
二、如何写Makefile
1. 文件命名
2. Makefile的规则
三、 Makefile的工作原理
四、Makefile中的变量、函数
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为Makefile就像一个Shell脚本一样,也可以执行操作系统的命令。
以上摘自百度百科,个人理解是:windows下的那些IDE,比如VS,我们只需要点击生成解决方案,就会对一个工程进行编译并生成最终的文件,其中编译并生成文件的操作IDE已经帮我们做好了;Makefile文件就是完成类似的功能,对一个工程内包含的所有文件进行编译生成可执行文件或者库文件等,只是Makefile文件的内容需要我们自己来写。
Makefile的好处是可以自动化编译,写好Makefile的内容后,只需要使用make命令,整个工程会完全自动编译,能大大提高软件开发的效率。
上面提到的make 是一个命令工具,用来解释Makefile文件中指令。
在写Makefile之前首先要知道Makefile的文件命名:makefile或者Makefile,不能是其它的名字。
因为make命令会寻找当前目录下的makefile或者Makefile文件,所以如果文件名不是makefile或者Makefile的话,make命令是无效的。
了解了文件的命名,就要开始编写Makefile了,需要了解编写Makefile的规则:
⚪ 规则1:一个Makefile文件可以有一个或者多个规则,但规则需要满足如下格式:
目标:依赖
命令(Shell命令)
例如:
app:main.c
gcc main.c -o app
目标:最终要生成的文件,可执行程序(伪目标除外);如示例的app就是最终要生成的文件;
依赖:生成目标所需要的文件或是目标;如示例的main.c就是生成可执行文件需要的依赖文件;
命令:通过执行命令对依赖操作生成目标,命令前必须是Tab缩进;如示例的gcc命令;
⚪ 规则2:Makefile中的其它规则一般都是为第一条规则服务的。
如果其它规则与第一条规则没有关系,那么这条规则不会被执行。
如下Makefile文件中,gcc -c d.c -o d.o不会被执行,因为第一条规则没有用到d.o文件。
app:a.o b.o main.o
gcc a.o b.o main.o -o app
a.o:a.c
gcc -c a.c -o a.o
b.o:b.c
gcc -c b.c -o b.o
main.o:main.c
gcc -c main.c -o main.o
d.o:d.c
gcc -c d.c -o d.o
示例,lesson7文件夹中有如下文件,其中main.c文件调用了add.c sub.c dicv.c mult.c中的函数,
那么可以编写Makefile,操作命令如下,首先进入Makefile文件的编辑
$ vim Makefile
编写Makefile文件,app是生成的目标文件,可执行文件,冒号后面的*.c文件是要生成app所依赖的文件,如下是最简单的一种Makefile写法。
app:add.c div.c mult.c sub.c main.c
gcc add.c div.c mult.c sub.c main.c -o app
然后保存,退出编辑,执行make命令进行编译,然后使用 ls 命令查看文件,会发现生成了app文件,执行 ./app 程序正常运行
$ make
注意:如果电脑中没有安装make命令,需要使用命令【sudo apt install make】安装后才能使用make命令,否则会出现如下提示:
⚪ 命令在执行之前,会先检查规则中的依赖是否存在
如果存在,执行命令;
如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令;
⚪ 检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间
如果依赖的时间比目标的时间晚,需要重新生成目标;
如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行;
如下是对上面的Makefile的一种升级,目标文件是通过.o文件生成的,.o文件又是通过.c文件编译得到的
app:add.o div.o mult.o sub.o main.o
gcc add.o div.o mult.o sub.o main.o -o app
add.o:add.c
gcc -c add.c -o add.o
div.o:div.c
gcc -c div.c -o div.o
mult.o:mult.c
gcc -c mult.c -o mult.o
sub.o:sub.c
gcc -c sub.c -o sub.o
main.o:main.c
gcc -c main.c -o main.o
如下图,对以上Makefile执行make命令后,从第二条规则开始执行,最后再执行第一条规则,这就是在执行之前会先检查依赖的文件是否存在
如果更新了其中的一个.c文件,比如,更新了main.c文件,在其中增加一个空行,再执行make命令,发现只执行了 gcc -c main.c -o main.o命令,因为会检测更新。
⚪ 自定义变量
格式: 变量名=变量值,比如 :var=hello,取出变量值可以使用 $(var)
⚪ 预定义变量
预定义变量只能在规则的命令中使用,常见的预定义变量比如:
预定义变量名 | 说明 | 默认值 |
AR | 归档维护程序的名称,例如生成静态库的命令 | ar |
CC | C编译器的名称 | cc/gcc |
CXX | C++编译器的名称 | g++ |
$@ | 获取目标的完整名称,包括后缀 | |
$< | 第一个依赖文件的名称 | |
$^ | 获取所有的依赖文件,中间用空格隔开 |
⚪ 获取变量值
使用$(变量名)获取变量值。
如下是对上面的Makefile的升级,定义了自定义变量src和target,在第一条规则中使用这两个变量代替值
src=add.o div.o mult.o sub.o main.o
target=app
$(target):$(src)
gcc $(src) -o $(target)
add.o:add.c
gcc -c add.c -o add.o
div.o:div.c
gcc -c div.c -o div.o
mult.o:mult.c
gcc -c mult.c -o mult.o
sub.o:sub.c
gcc -c sub.c -o sub.o
main.o:main.c
gcc -c main.c -o main.o
⚪ 模式匹配
%是通配符,匹配一个字符串,示例:%.o:%.c。一个规则中的两个%匹配的是同一个字符串。使用通配符可以对上面的Makefile文件进行进一步升级:
※ 第一条规则指定的依赖不存在,就会向下寻找,找到第二条规则发现%.o可以匹配需要的依赖(.o文件),会依次匹配需要的依赖文件;
※ 在同一条规则中%表示用一个字符串,所以%.o:%.c依次可以表示为add.o:add.c、div.o:div.c、mult.o:mult.c、sub.o:sub.c、main.o:main.c;
※ $<获取第一个依赖的文件的名称,那么它依次获取到add.c、div.c、mult.c、sub.c、main.c;
※ $@获取目标的完整名称;
src=add.o div.o mult.o sub.o main.o
target=app
$(target):$(src)
gcc $(src) -o $(target)
%.o:%.c
gcc -c $< -o $@
⚪ 函数
■ 函数1 获取指定目录下指定类型的文件列表
$(wildcard PATTERN...)
wildcard是函数名,PATTERN是参数;
该函数的功能是 获取指定目录下指定类型的文件列表;
参数PATTERN指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔;
返回得到若干个文件的文件列表,文件名之间使用空格间隔
示例,如下将返回当前目录下的所有.c文件和当前目录下的sub子目录下的所有.c文件,中间用空格隔开:
$(wildcard *.c ./sub/*.c)
■ 函数2 查找单词并替换
$(patsubst) , , )
patsubst是函数名,
, , 是函数参数; 功能是查找
中的单词是否符合 ,如果匹配,用 替换;
中的单词可以是空格、Tab或回车、换行分割;
可以包括通配符%,表示任意长度的字串,如果 中也包含%,那么 中的这个%将是 中的那个%所代表的字串。 返回值:函数返回被替换过后的字符串。
示例,如下语句返回x.o bar.o,%.c可以匹配 x.c bar.c,所以用%.o替换;%.c匹配x.c,时被替换为x.o,%.c匹配bar.c时被替换为bar.o
$(patsubst %.c,%.o,x.c bar.c)
示例,首先获取当前目录下的所有.c文件,然后获取依赖文件.o文件(.c文件名被替换为.o文件名)
# 定义变量
# 获取当前路径下的所有.c文件 add.c sub.c div.c mult.c main.c
src=$(wildcard ./*.c)
# 把.c文件替换为.o文件
objs=$(patsubst %.c, %.o,$(src))
target=app
$(target):$(objs)
$(CC) $(objs) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
⚪ 伪目标
比如可以定义清除产生的中间文件(.o文件),可以使用clean关键字,结合伪目标可以实现。
# 定义伪目标
.PHONY:clean
clean:
rm $(objs) -f
然后执行如下命令就可以清除所有中间生成的.o文件。
$ make clean
最终完整的Makefile文件如下:
# 定义变量
# 获取当前路径下的所有.c文件 add.c sub.c div.c mult.c main.c
src=$(wildcard ./*.c)
# 把.c文件替换为.o文件
objs=$(patsubst %.c, %.o,$(src))
target=app
$(target):$(objs)
$(CC) $(objs) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
# 定义伪目标
.PHONY:clean
clean:
rm $(objs) -f
如上只是对Makefile的基本了解。
全文参考:课程列表_牛客网 (nowcoder.com)https://www.nowcoder.com/study/live/504/1/10?