资料(强推跟着资料过一边代码)
1、基本规则
make
命令会自动读取当前目录下的Makefile
文件,完成相应的编译步骤。Makefile由一组规则(Rule)组成,每条规则的格式是:
target ... : prerequisites ...
command1
command2
...
main
是这条规则的目标(Target),main.o
、stack.o
和maze.o
是这条规则的条件(Prerequisite)。目标和条件之间的关系是:欲更新目标,必须首先更新它的所有条件;所有条件中只要有一个条件被更新了,目标也必须随之被更新。
所谓“更新”就是执行一遍规则中的命令列表,命令列表中的每条命令必须以一个Tab开头,注意不能是空格,Makefile的格式不像C语言的缩进那么随意,对于Makefile中的每个以Tab开头的命令,make
会创建一个Shell进程去执行它。
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"
.PHONY: clean
Clean
目标不依赖于任何条件,并且执行它的命令列表不会生成clean
这个文件,刚才说过,只要执行了命令列表就算更新了目标,即使目标并没有生成也算。在这个例子还演示了命令前面加@
和-
字符的效果:如果make
执行的命令前面加了@
字符,则不显示命令本身而只显示它的结果;通常make
执行的命令如果出错(该命令的退出状态非0)就立刻终止,不再执行后续命令,但如果命令前面加了-
号,即使这条命令出错,make
也会继续执行后续命令。通常rm
命令和mkdir
命令前面要加-
号。防止如果存在一个名为Clean的文件,将Clean声明为一个伪变量。
make
处理Makefile的过程分为两个阶段:
1.首先从前到后读取所有规则,建立起一个完整的依赖关系图
2.然后从缺省目标或者命令行指定的目标开始,根据依赖关系图选择适当的规则执行,执行Makefile中的规则和执行C代码不一样,并不是从前到后按顺序执行,也不是所有规则都要执行一遍,例如make
缺省目标时不会更新clean
目标,因为Clean跟缺省目标没有任何依赖关系。
2、隐含规则和模式规则
http://akaedu.github.io/book/ch22s02.html
3、变量
= 变量不会立刻展开
:= 变量立刻展开
+=
运算符可以给变量追加值,例如:
objects = main.o
objects += $(foo)
foo = foo.o bar.o
object
是用=
定义的,+=
仍然保持=
的特性,objects
的值是main.o $(foo)
(注意$(foo)
前面自动添一个空格),但不立即展开,等到后面需要展开$(objects)
时会展开成main.o foo.o bar.o
。
再比如:
objects := main.o
objects += $(foo)
foo = foo.o bar.o
object
是用:=
定义的,+=
保持:=
的特性,objects
的值是main.o $(foo)
,立即展开得到main.o
(这时foo
还没定义),注意main.o
后面的空格仍保留。
如果变量还没有定义过就直接用+=
赋值,那么+=
相当于=
。
$@
,表示规则中的目标。
$<
,表示规则中的第一个条件。
$?
,表示规则中所有比目标新的条件,组成一个列表,以空格分隔。
$^
,表示规则中的所有条件,组成一个列表,以空格分隔。
4、自动处理头文件的依赖关系
gcc
的-M
选项自动生成目标文件和源文件的依赖关系
$ gcc -M main.c
main.o: main.c /usr/include/stdio.h /usr/include/features.h \
/usr/include/sys/cdefs.h /usr/include/bits/wordsize.h \
/usr/include/gnu/stubs.h /usr/include/gnu/stubs-32.h \
/usr/lib/gcc/i486-linux-gnu/4.3.2/include/stddef.h \
/usr/include/bits/types.h /usr/include/bits/typesizes.h \
/usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/i486-linux-gnu/4.3.2/include/stdarg.h \
/usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h main.h \
stack.h maze.h
-M
选项把stdio.h
以及它所包含的系统头文件也找出来了,如果我们不需要输出系统头文件的依赖关系,可以用-MM
选项:
$ gcc -MM *.c
main.o: main.c main.h stack.h maze.h
maze.o: maze.c maze.h main.h
stack.o: stack.c stack.h main.h
接下来的问题是怎么把这些规则包含到Makefile中,GNU make
的官方手册建议这样写:
all: main
main: main.o stack.o maze.o
gcc $^ -o $@
clean:
-rm main *.o
.PHONY: clean
sources = main.c stack.c maze.c
include $(sources:.c=.d)
%.d: %.c
set -e; rm -f $@; \
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
具体解释:http://akaedu.github.io/book/ch22s04.html
5、 常用的
make
命令行选项
-n
选项只打印要执行的命令,而不会真的执行命令,这个选项有助于我们检查Makefile写得是否正确,由于Makefile不是顺序执行的,用这个选项可以先看看命令的执行顺序,确认无误了再真正执行命令。
-C
选项可以切换到另一个目录执行那个目录下的Makefile,比如先退到上一级目录再执行我们的Makefile(假设我们的源代码都放在testmake
目录下):
$ cd ..
$ make -C testmake
make: Entering directory `/home/akaedu/testmake'
cc -c -o main.o main.c
cc -c -o stack.o stack.c
cc -c -o maze.o maze.c
gcc main.o stack.o maze.o -o main
make: Leaving directory `/home/akaedu/testmake'
一些规模较大的项目会把不同的模块或子系统的源代码放在不同的子目录中,然后在每个子目录下都写一个该目录的Makefile,然后在一个总的Makefile中用make -C
命令执行每个子目录下的Makefile。
在make
命令行也可以用=
或:=
定义变量,如果这次编译我想加调试选项-g
,但我不想每次编译都加-g
选项,可以在命令行定义CFLAGS
变量,而不必修改Makefile编译完了再改回来:
$ make CFLAGS=-g
cc -g -c -o main.o main.c
cc -g -c -o stack.o stack.c
cc -g -c -o maze.o maze.c
gcc main.o stack.o maze.o -o main
如果在Makefile中也定义了CFLAGS
变量,则命令行的值覆盖Makefile中的值。