1 什么是Makefile?为什么要用Makefile?
Linux 环境下的程序员需要构建和管理自己的工程,Makefile描述了整个工程的编译、连接等规则。包括:工程中的哪些源文件需要编译以及如何编译、需要创建那些库文件以及如何创建这些库文件、如何最后产生我们想要的可执行文件。一个工程的源文件数量很多,并且根据属性,功能的不同放在不同的目录中,Makefile定义了一系列规则指定了哪些文件要先编译,哪些文件要后编译等待。程序员通过make命令来解释Makefile中的指定,从而完成整个工程的自动化编译。
我们知道程序编译的过程是先将原文件编译成中间目标文件(.obj或者.o),然后将大量的中间目标文件链接成可执行文件。编译时只要语法,函数与变量的声明正确就能编译成中间目标文件,有多少个源文件就会生成多少个中间目标文件;链接时将中间目标文件连接成我们的应用程序
2 Makefile格式
目标文件target:源文件或者目标
命令(任意shell命令)
上面就是表示目标文件是由哪些文件生成。
例如:main.o : main.c defs.hcc -c main.c (命令一定要以一个Tab键作为开头)
此代码表示通过main.c和defs.h文件生成一个中间目标文件main.o,其实就是执行gcc -c -I defs.h地址 main.c -o main.o
3 变量的使用
和C,Java中一样,Makefile中也有变量,作用类似于C语言中的宏,当某个字符串在Makefile文件中多次使用,而一旦此字符串需要修改时,就不得不找出文件中所有的此字符串进行修改,Makefile中变量的作用就是将此字符串定义成一个变量,当修改时只需要在变量定义的地方进行修改就可以了;还有一个作用就是将某些通用的长字符串提取出来,用的时候可以直接用一个变量名替代,非常的方便
给个例子:
edit : main.o kbd.o command.o display.o \ (\ 表示换行)
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
这个例子中clean中main.o kbd.o command.o display.o insert.o search.o files.o utils.o与第一行代码中是一样的,所以我们就可以:
OBJ = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
这样上述代码就变成
OBJ = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(OBJ)
cc -o edit $(OBJ)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(OBJ)
是的,定义变量之后,我们通过$(变量名)使用这个变量 。
4 Makefile其实是有自己强大的自动推导能力的,对于一个.o文件,Makefile会自动将相同名称的.c文件加入到它的依赖关系中,比如上面代码中省略的部分中有
main.o : main.c defs.h
cc -c main.c
对于Makefile,它会自动将main.c文件加入到main.o:之后,所以上面完全可以写成
main.o : defs.h
cc -c main.c
所以上面的例子就可以写成:
OBJ = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(OBJ)
cc -o edit $(OBJ)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
clean :
rm edit $(OBJ)
既然能自动推导出同名的.c文件,那么头文件能不能也自动生成呢?答案是肯定的,不然在大型工程中,我们还得知道所有C文件中包含哪些头文件,要是修改了一个头文件难道还得在Makefile中添上吗?当然不是,一般的C/C++编译器都支持“-M”的选项(GUN的C/C++编译器中得用“-MM”),自动寻找文件中包含的头文件,并生成一个依赖关系。在Makefile中,我们可以将 gcc -M C文件 生成的依赖关系放在一个.d文件中,让make自动更新.d文件,并将其包含在我们的主Makefile文件中。
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
5 clean
上面的代码最后
clean :
rm edit $(OBJ)
这是用来清除所有的目标文件的,包括(中间目标文件和可执行文件),用户可以用“make clean”命令执行此代码来清除目标文件
6 Makefile的工作方式
在默认的方式下,也就是我们只输入make命令。那么:
1)make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
2)如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
3)如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
4)如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
5)当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o文件生命make的终极任务,也就是执行文件edit了。
这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
当然,如果你很有个性的表示我想给Makefile重起一个名字可以吗?当然可以,只要你在执行make的时候:make -f 文件名 就可以了
Makefile 编译规则:
1) 如果工程未编译过,那么所有的C文件都要编译并被链接
2) 如果工程中某几个文件被修改,那么直编译被修改的文件并链接目标文件
3) 如果工程的头文件被改变了,那么需要编译引用了这几个头文件的C文件,并链接目标文件。
注释:# 与C或者Java等语言编辑不同,在Makefile中用“#”来进行行注释,作用类似于C中的"//"
引用其他Makefile:include Makefile文件路径 这在大型项目中是十分常见的,它其实也类似于C中的include
- 如果你在命令前加上了一个“-”符号,如-include,就表示不要报错,继续执行
命令前一定是tab键
\ 如果命令过长,可以利用“\”进行换行
VPATH变量 这个特殊的变量指定了一个目录,表示如果在当前目录中找不到依赖文件或者目标文件,那么就到这个目录中找,这个变量可以定义多个目录路径,用“:”分割,搜索目录的顺序根据此变量值的顺序排序
还有很多细节,可以参照陈皓的《跟我一起写 Makefile 》,我也整理过一些常见的函数,变量等。