这些源文件可以这样编译:
$ gcc main.c stack.c maze.c -o main
但这不是个好办法,如果编译之后又对maze.c
做了修改,又要把所有源文件编译一遍,即使main.c
、stack.c
和那些头文件都没有修改也要跟着重新编译。一个大型的软件项目往往由上千个源文件组成,全部编译一遍需要几个小时,只改一个源文件就要求全部重新编译肯定是不合理的。
这样编译也许更好一些:
$ gcc -c main.c $ gcc -c stack.c $ gcc -c maze.c $ gcc main.o stack.o maze.o -o main
如果编译之后又对maze.c
做了修改,要重新编译只需要做两步:
$ gcc -c maze.c $ gcc main.o stack.o maze.o -o main
这样又有一个问题,每次编译敲的命令都不一样,很容易出错,比如我修改了三个源文件,可能有一个忘了重新编译,结果编译完了修改没生效,运行时出了Bug还满世界找原因呢。更复杂的问题是,假如我改了main.h
怎么办?所有包含main.h
的源文件都需要重新编译,我得挨个找哪些源文件包含了main.h
,有的还很不明显,例如stack.c
包含了stack.h
,而后者包含了main.h
。可见手动处理这些问题非常容易出错,那有没有自动的解决办法呢?有,就是写一个Makefile
文件和源代码放在同一个目录下:
main: main.o stack.o maze.o gcc main.o stack.o maze.o -o main main.o: main.c main.h stack.h maze.h gcc -c main.c stack.o: stack.c stack.h main.h gcc -c stack.c maze.o: maze.c maze.h main.h gcc -c maze.c
然后在这个目录下运行make
编译:
$ make gcc -c main.c gcc -c stack.c gcc -c maze.c gcc main.o stack.o maze.o -o main
make
命令会自动读取当前目录下的Makefile
文件[33],完成相应的编译步骤。Makefile由一组规则(Rule)组成,每条规则的格式是:
target ... : prerequisites ... command1 command2 ...
例如:
main: main.o stack.o maze.o gcc main.o stack.o maze.o -o main
main
是这条规则的目标(Target),main.o
、stack.o
和maze.o
是这条规则的条件(Prerequisite)。目标和条件之间的关系是:欲更新目标,必须首先更新它的所有条件;所有条件中只要有一个条件被更新了,目标也必须随之被更新。所谓“更新”就是执行一遍规则中的命令列表,命令列表中的每条命令必须以一个Tab开头,注意不能是空格,Makefile的格式不像C语言的缩进那么随意,对于Makefile中的每个以Tab开头的命令,make
会创建一个Shell进程去执行它。
对于上面这个例子,make
执行如下步骤:
-
尝试更新Makefile中第一条规则的目标
main
,第一条规则的目标称为缺省目标,只要缺省目标更新了就算完成任务了,其它工作都是为这个目的而做的。由于我们是第一次编译,main
文件还没生成,显然需要更新,但规则说必须先更新了main.o
、stack.o
和maze.o
这三个条件,然后才能更新main
。 -
所以
make
会进一步查找以这三个条件为目标的规则,这些目标文件也没有生成,也需要更新,所以执行相应的命令(gcc -c main.c
、gcc -c stack.c
和gcc -c maze.c
)更新它们。 -
最后执行
gcc main.o stack.o maze.o -o main
更新main
。
如果没有做任何改动,再次运行make
:
$ make make: `main' is up to date.
make
会提示缺省目标已经是最新的了,不需要执行任何命令更新它。再做个实验,如果修改了maze.h
(比如加个无关痛痒的空格)再运行make
:
$ make gcc -c main.c gcc -c maze.c gcc main.o stack.o maze.o -o main
make
会自动选择那些受影响的源文件重新编译,不受影响的源文件则不重新编译,这是怎么做到的呢?
-
make
仍然尝试更新缺省目标,首先检查目标main
是否需要更新,这就要检查三个条件main.o
、stack.o
和maze.o
是否需要更新。 -
make
会进一步查找以这三个条件为目标的规则,然后发现main.o
和maze.o
需要更新,因为它们都有一个条件是maze.h
,而这个文件的修改时间比main.o
和maze.o
晚,所以执行相应的命令更新main.o
和maze.o
。 -
既然
main
的三个条件中有两个被更新过了,那么main
也需要更新,所以执行命令gcc main.o stack.o maze.o -o main
更新main
。
现在总结一下Makefile的规则,请读者结合上面的例子理解。如果一条规则的目标属于以下情况之一,就称为需要更新:
-
目标没有生成。
-
某个条件需要更新。
-
某个条件的修改时间比目标晚。
在一条规则被执行之前,规则的条件可能处于以下三种状态之一:
-
需要更新。能够找到以该条件为目标的规则,并且该规则中目标需要更新。
-
不需要更新。能够找到以该条件为目标的规则,但是该规则中目标不需要更新;或者不能找到以该条件为目标的规则,并且该条件已经生成。
-
错误。不能找到以该条件为目标的规则,并且该条件没有生成。
执行一条规则A的步骤如下:
-
检查它的每个条件P:
-
如果P需要更新,就执行以P为目标的规则B。之后,无论是否生成文件P,都认为P已被更新。
-
如果找不到规则B,并且文件P已存在,表示P不需要更新。
-
如果找不到规则B,并且文件P不存在,则报错退出。
-
-
在检查完规则A的所有条件后,检查它的目标T,如果属于以下情况之一,就执行它的命令列表:
-
文件T不存在。
-
文件T存在,但是某个条件的修改时间比它晚。
-
某个条件P已被更新(并不一定生成文件P)。
-
通常Makefile都会有一个clean
规则,用于清除编译过程中产生的二进制文件,保留源文件:
clean: @echo "cleanning project" -rm main *.o @echo "clean completed"
把这条规则添加到我们的Makefile末尾,然后执行这条规则:
$ make clean cleanning project rm main *.o clean completed
如果在make
的命令行中指定一个目标(例如clean
),则更新这个目标,如果不指定目标则更新Makefile中第一条规则的目标(缺省目标)。
和前面介绍的规则不同,clean
目标不依赖于任何条件,并且执行它的命令列表不会生成clean
这个文件,刚才说过,只要执行了命令列表就算更新了目标,即使目标并没有生成也算。在这个例子还演示了命令前面加@
和-
字符的效果:如果make
执行的命令前面加了@
字符,则不显示命令本身而只显示它的结果;通常make
执行的命令如果出错(该命令的退出状态非0)就立刻终止,不再执行后续命令,但如果命令前面加了-
号,即使这条命令出错,make
也会继续执行后续命令。通常rm
命令和mkdir
命令前面要加-
号,因为rm
要删除的文件可能不存在,mkdir
要创建的目录可能已存在,这两个命令都有可能出错,但这种错误是应该忽略的。例如上面已经执行过一遍make clean
,再执行一遍就没有文件可删了,这时rm
会报错,但make
忽略这一错误,继续执行后面的echo
命令:
$ make clean cleanning project rm main *.o rm: cannot remove `main': No such file or directory rm: cannot remove `*.o': No such file or directory make: [clean] Error