1.一切得从Make谈起
大家都知道Make这个单词的意思是“制作”的意思,使用Make命令就是要做出某个文件(通常是可执行文件)。例如:要制作出可执行文件a.txt,可以使用下面的命令:
make a.txt
但是,如果你真的在Linux的终端下输入上面的这条命令,并不会制作出a.txt文件。相反,你会得到下面这个错误信息:
[njust@njust Make_Tutorials]$ make a.txt make: *** 没有规则可以创建目标“a.txt”。 停止。
为什么会出现上面的这个错误呢?因为Make命令本身并不知道怎样才能制作出a.txt,需要有人告诉它如何调用其他命令来完成这个目标。打个比方哈,就像厨房新手做菜时目标是做出来一道硬菜。但是呢,由于是新手还必须得按照菜谱来,第一步该干啥,第二步该干啥.......。因此,Make命令生成可执行文件时,也必须严格按照Makefile文件来进行编译、链接并生成可执行文件,Makefile文件就类似于菜谱,它制定了一系列的规则。因此,学会如何写出一个正确的Makefile文件后,才能愉快地使用Make命令进行编译大型项目代码。
有的读者可能就会有疑问了,学习Make命令干嘛呢?生成可执行文件直接使用gcc或g++命令进行编译就好啦,为啥还得花时间学习如何编写Makefile文件。是的,在一个小的demo程序中,你的确可以使用gcc/g++命令进行编译生成可执行文件,就像下面的示例一样。但是,如果你恰好要同时编译多个.c的源文件,那么gcc这条编译命令将会敲的很长很长,就很不方便还容易出错。
[njust@njust Make_Tutorials]$ gcc -g demo.c -o demo [njust@njust Make_Tutorials]$ ./demo hello world! [njust@njust Make_Tutorials]$ cat demo.c #include <stdio.h> int main() { printf("hello world!\n"); return 0; }
如果我们使用Make命令进行编译时,先写好Makefile文件,然后执行一条简单的Make命令即可生成可执行文件(见下例所示),大大提高了编译操作的便捷性。
[njust@njust Make_Tutorials]$ ls demo.c Makefile [njust@njust Make_Tutorials]$ cat Makefile demo:demo.c gcc -g demo.c -o demo [njust@njust Make_Tutorials]$ make demo gcc -g demo.c -o demo [njust@njust Make_Tutorials]$ ls demo demo.c Makefile [njust@njust Make_Tutorials]$ ./demo hello world!
2.Makefile文件的基本格式
Make命令编译出可执行文件时,所需的构建规则全写在Makefile文件中了。Makefile文件是由一系列规则组成的,每条规则的格式如下:
<目标>:<依赖文件> 按一次TAB键<命令>
上面第一行中冒号前面的为目标,冒号后面的是依赖条件。第二行的开始必须以一个TAB键开头,后面跟着命令。其中,目标是必须写的,不能省略。依赖条件和命令都是可选择不写,但两者中必须至少有一个存在。
(1).目标
一个目标即可构成Makefile文件中的一条规则。目标通常是文件名,表示Make命令所要编译出的对象。目标可以是一个文件名,也可以是多个文件名,多个文件名的话,彼此之间用空格分开。
目标除了可以是文件名外,还可以是某个操作的名字,此时目标也称为“伪目标”。例如,下面的Makefile文件中clear是目标,但它不是文件名,而是一个删除操作的名字,因此也称为伪目标。
clean: rm *.o
值得注意的是,假设当前目录下恰好有一个文件的名字也叫clean,那么make clean命令将不会被执行。这是因为Make发现clean文件已经存在,就认为没有必要重新编译啦,因此也就不会执行rm *.o操作。
[njust@njust Make_Tutorials]$ touch hello.o test.o [njust@njust Make_Tutorials]$ ls demo.c hello.o Makefile test.o [njust@njust Make_Tutorials]$ touch clean [njust@njust Make_Tutorials]$ ls clean demo.c hello.o Makefile test.o [njust@njust Make_Tutorials]$ cat Makefile demo:demo.c gcc -g demo.c -o demo clean: rm *.o [njust@njust Make_Tutorials]$ make clean make: “clean”是最新的。 [njust@njust Make_Tutorials]$ ls clean demo.c hello.o Makefile test.o
为了解决上面的问题,可以明确声明clean是伪目标,重新修改Makefile文件,如下所示。声明clean是伪目标后,Make命令将不会去检查当前目录下是否有一个名为clean的文件,而是每次都会执行对应的命令。如果Make命令运行时没有指定目标,默认会执行Makefile文件中的第一个目标。
(2).依赖条件
依赖条件通常是一组文件名,多个文件名之间用空格分开。它指定了目标是否重新编译的判断标准:只要有一个依赖文件不存在或者已更新,目标就需要重新被编译。
[njust@njust Make_Tutorials]$ cat Makefile demo:demo.c gcc -g demo.c -o demo .PHONY:clean clean: rm *.o [njust@njust Make_Tutorials]$ make clean rm *.o [njust@njust Make_Tutorials]$ ls clean demo.c Makefile
下面的示例代码中,编译result.txt的前提条件是source.txt。如果当前目录中,source.txt文件已经存在,则会正常执行make result.txt。否则,必须再写一条规则用于生成source.txt。
[njust@njust Make_Tutorials]$ ls clean demo.c Makefile source.txt [njust@njust Make_Tutorials]$ make result.txt echo "This is a demo!" This is a demo! [njust@njust Make_Tutorials]$ cat Makefile demo:demo.c gcc -g demo.c -o demo .PHONY:clean clean: rm *.o result.txt:source.txt echo "This is a demo!"
当source.txt文件不存在时,需要修改Makefile文件的内容,如下所示:
[njust@njust Make_Tutorials]$ ls clean demo.c Makefile [njust@njust Make_Tutorials]$ make result.txt echo "This is a demo!" > source.txt cp source.txt result.txt [njust@njust Make_Tutorials]$ ls clean demo.c Makefile result.txt source.txt [njust@njust Make_Tutorials]$ cat Makefile demo:demo.c gcc -g demo.c -o demo .PHONY:clean clean: rm *.o result.txt:source.txt cp source.txt result.txt source.txt: echo "This is a demo!" > source.txt
(3).命令
命令表示如何来更新目标文件,由一行或多行shell命令构成。它是构建目标的具体指令,它的运行结果通常是生成的目标文件。
每行命令前必须有一个TAB键,如果想使用其他键进行替换,可以使用内置变量.RECIPEPREFIX进行声明,如下所示。使用.RECIPEPREFIX指定用>来替代默认的TAB键。于是,每行命令的起始变成了>而不是TAB键了。
[njust@njust Make_Tutorials]$ make all echo Hello,CurryCoder Hello,CurryCoder [njust@njust Make_Tutorials]$ cat Makefile demo:demo.c gcc -g demo.c -o demo .PHONY:clean clean: rm *.o result.txt:source.txt cp source.txt result.txt source.txt: echo "This is a demo!" > source.txt .RECIPEPREFIX = > all: > echo Hello,CurryCoder
值得注意的是,每行命令在一个独立的shell中执行,这些shell之间没有继承关系,如下例所示。执行make var命令后,取不到foo的值。由于两行命令在两个不同的进程执行。
[njust@njust Make_Tutorials]$ cat Makefile demo:demo.c gcc -g demo.c -o demo .PHONY:clean clean: rm *.o result.txt:source.txt cp source.txt result.txt source.txt: echo "This is a demo!" > source.txt var: export foo=bar echo "foo=[$$foo]" [njust@njust Make_Tutorials]$ make var export foo=bar echo "foo=[$foo]" foo=[]
上面的程序执行后的输出结果是foo=[],无法获取foo的值。解决方法一是将两行命令写在同一行中,中间用分号隔开。解决方法二是在换行符前加反斜杠转义。解决方法三是加上.ONESHELL命令。
[njust@njust Make_Tutorials]$ make var # export foo=bar;echo "foo=[$foo]" export foo=bar;\ echo "foo=[$foo]" foo=[bar] [njust@njust Make_Tutorials]$ vim Ma [njust@njust Make_Tutorials]$ vim Makefile [njust@njust Make_Tutorials]$ make var # export foo=bar;echo "foo=[$foo]" # export foo=bar;\ # echo "foo=[$foo]" export foo=bar; export "foo=[$foo]" [njust@njust Make_Tutorials]$ cat Makefile demo:demo.c gcc -g demo.c -o demo .PHONY:clean clean: rm *.o result.txt:source.txt cp source.txt result.txt source.txt: echo "This is a demo!" > source.txt .ONESHELL: var: # export foo=bar;echo "foo=[$$foo]" # export foo=bar;\ # echo "foo=[$$foo]" export foo=bar; export "foo=[$$foo]"