在之前我们已经学习了一个文件的编译过程,但是做过项目的都知道,一个工程中的源文件不计其数,其按类型、功能、模块会分别放在若干个目录中,而这些文件如何编译就需要有一个编译规则,虽然现在很多大型的项目都是直接用诸如CMake、Autotools、SCons等工具生成的,但是对于一些小项目,使用工具就没有什么必要了,而且对于项目编写来说,掌握Makefile的基本知识对我们来说也是很有必要的。
我们本篇文章所要讲解的就是这个编译规则的文件makefile的规则及如何进行编写。makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
1.0从一个简单的例子开始
这里,我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册,在这个示例中,在这个例子中,我们会看到一份主程序代码(main.c)、三份函数代码(getop.c、stack.c、getch.c)以及一个头文件(calc.h)。通常情况下,我们需要这样编译它:
gcc -o calc main.c getch.c getop.c stack.c
如果没有makefile,在开发+调试程序的过程中,我们就需要不断地重复输入上面这条编译命令,要不就是通过终端的历史功能不停地按上下键来寻找最近执行过的命令。这样做两个缺陷:
-
一旦终端历史记录被丢失,我们就不得不从头开始;
-
任何时候只要我们修改了其中一个文件,上述编译命令就会重新编译所有的文件,当文件足够多时这样的编译会非常耗时。
为了减少麻烦和节约时间,我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是:
1.如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
2.如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
3.如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。
只要我们的Makefile写得够好,所有的这一切,我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。
1.1 Makefile的规则
target ... : prerequisites ...
command
...
...
target也就是一个目标文件,可以是Object File,也可以是执行文件。
prerequisites就是,要生成那个target所需要的文件或是目标。
command也就是make需要执行的命令。(任意的Shell命令)
这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。
通过了解规则,我们就掌握了makefile的核心内容,但是仅仅靠这个是无法自己编写好一个完美的makefile文件的。
这里我们看回到我们之前的示例,按照这个规则我们可以写出如下的一个简单的makefile文件:
calc: main.c getch.c getop.c stack.c gcc -o calc main.c getch.c getop.c stack.c
我么可以看到它主要分成了三个部分,第一行冒号之前的calc,就是我们所要编译的这个程序calc。冒号后面的部分(main.c getch.c getop.c stack.c),就是我们称之为依赖关系表,也就是编译calc所需要的文件,这些文件只要有一个发生了变化,就会触发该语句的第三部分,我们称其为命令部分。
这里值得注意的是:请注意,在第二行的“gcc”命令之前必须要有一个tab缩进。语法规定Makefile中的任何命令之前都必须要有一个tab缩进,否则make就会报错。
这样,一个可以进行编译的makefile文件写出来了,但是,我们可以看出,整个makefile文件比较基础,没有进行过优化,这样,在编译比较大且文件多的工程的时候效率是远远达不到的,所以我们需要通过下面的学习来进行重构,以提高整个makefile的编译效率。
1.3makefile中使用变量
有时候我们会需要在makefile中增添文件,我们这个makefile显然可以直接在依赖链和指令中直接添加文件名来完成,但是当文件较大时想要直接添加可能就比较复杂和耗费时间了,所以,为了makefile的易维护,在makefile中我们可以使用变量。makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。
cc = gcc prom = calc src = main.c getch.c getop.c stack.c $(prom): $(src) $(cc) -o $(prom) $(src)
1.4、make是如何工作的
make f 文件名
2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“calc”这个文件,并把这个文件作为最终的目标文件。
3、如果calc文件不存在,或是calc所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
4、如果calc所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
5、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件calc了。
cc = gcc prom = calc deps = calc.h obj = main.o getch.o getop.o stack.o $(prom): $(obj) $(cc) -o $(prom) $(obj) main.o: main.c $(deps) $(cc) -c main.c getch.o: getch.c $(deps) $(cc) -c getch.c getop.o: getop.c $(deps) $(cc) -c getop.c stack.o: stack.c $(deps) $(cc) -c stack.c
这样一来,上面的问题显然是解决了,但是现在makefile文件又变的啰嗦了,这里还是可以用变量的方法来对其进行简化
cc = gcc prom = calc deps = calc.h obj = main.o getch.o getop.o stack.o $(prom): $(obj) $(cc) -o $(prom) $(obj) %.o: %.c $(deps) $(cc) -c $< -o $@
1.5清空目标文件的规则
cc = gcc prom = calc deps = calc.h obj = main.o getch.o getop.o stack.o $(prom): $(obj) $(cc) -o $(prom) $(obj) %.o: %.c $(deps) $(cc) -c $< -o $@ .PHONY:clean clean: rm -rf $(obj) $(prom)
有了上面最后两行代码,当我们在终端中执行make clean命令时,它就会去删除该工程生成的所有编译文件,从而保持整个工程整洁。.PHONY意思表示clean是一个“伪目标”,。而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean从来都是放在文件的最后”。
1.6使用函数使得makefile更智能
如果我们需要往工程中添加一个.c或.h,可能同时就要再手动为obj常量再添加第一个.o文件,如果这列表很长,代码会非常难看,为此,我们需要用到Makefile中的函数,这里我们演示两个:
cc = gcc prom = calc deps = $(shell find ./ -name "*.h") src = $(shell find ./ -name "*.c") obj = $(src:%.c=%.o) $(prom): $(obj) $(cc) -o $(prom) $(obj) %.o: %.c $(deps) $(cc) -c $< -o $@ clean: rm -rf $(obj) $(prom)
推荐书籍下载链接
http://download.csdn.net/download/noticeable/10242106
Makefile工具下载
SCons: A software construction tool
https://www.lrde.epita.fr/~adl/autotools.html
参考资料
http://blog.csdn.net/ruglcc/article/details/7814546
https://www.gnu.org/software/make/manual/make.html
https://www.zhihu.com/question/23792247
https://yq.aliyun.com/articles/9243
http://blog.csdn.net/liang13664759/article/details/1771246