##1、Makefile介绍
把Makefile 文件和源代码放在同一个目录。make 命令会自动读取当前目录下的 Makefile 文件。
Makefile 由一组规则(Rule)组成,第一条规则的目标称为缺省目标,只要缺省目标更新了就算完成任务了,其它工作都是为这个目的而做的。如果在 make 的命令行中指定一个目标(例如 clean),则更新这个目标,如果不指定目标则更新 Makefile 中第一条规则的目标(缺省目标)。
每条规则的格式是:
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 会自动选择那些受影响的源文件重新编译,不受影响的源文件则不重新编译。
2、执行一条规则 A 的步骤如下:
1)检查它的每个条件 P:
- 如果 P 需要更新,就执行以 P 为目标的规则 B。之后,无论是否生成文件 P,都认为 P 已被更新。
- 如果找不到规则 B,并且文件 P 已存在,表示 P 不需要更新。
- 如果找不到规则 B,并且文件 P 不存在,则报错退出。
2) 在检查完规则 A 的所有条件后,检查它的目标 T,如果属于以下情况之一,
就执行它的命令列表:
- 文件 T 不存在。
- 文件 T 存在,但是某个条件的修改时间比它晚。
- 某个条件 P 已被更新(并不一定生成文件 P)
3、clean规则
clean规则如下所示:
clean:
@echo "cleanning project"
-rm main *.o
@echo "clean completed"
执行这条规则,结果如下图所示
$ make clean
cleanning project
rm main *.o
clean completed
clean 目标不依赖于任何条件,并且执行它的命令列表不会生成 clean 这个文件。只要执行了命令列表就算更新了目标,即使目标并没有生成也算。在这个例子还演示了命令前面加@和-字符的效果:如果make 执行的命令前面加了@字符,则不显示命令本身而只显示它的结果;通常make 执行的命令如果出错(该命令的退出状态非 0)就立刻终止,不再执行后续命令,但如果命令前面加了-号,即使这条命令出错, make 也会继续执行后续命令。通常 rm 命令和 mkdir 命令前面要加-号,因为 rm 要删除的文件可能不存在,mkdir 要创建的目录可能已存在,这两个命令都有可能出错,但这种错误是应该忽略的。
如果当前目录下存在 clean 这个文件, clean 目标又不依赖于任何条件, make 就认为它不需要更新了。而我们希望把 clean 当作一个特殊的名字使用,不管它存在不存在都要更新,可以添一条特殊规则,把 clean 声明为一个伪目标:
.PHONY: clean
这条规则写在 clean:规则的后面也行,也能起到声明 clean是伪目标的作用。
4、Makefile的处理过程
Makefile的处理过程分为两个阶段。
1)首先从前到后读取所有规则,建立起一个完整的依赖关系图,例如:
2)从缺省目标或者命令行指定的目标开始,根据依赖关系图选择适当的规则执行,执行 Makefile 中的规则和并不是从前到后按顺序执行,也不是所有规则都要执行一遍,例如 make 缺省目标时不会更新 clean 目标,因为从上图可以看出,它跟缺省目标没有任何依赖关系。
5、隐含规则和模式规则
一个目标在 Makefile 中的所有规则都没有命令列表, make 会尝试在内建的隐含规则(Implicit Rule)数据库中查找适用的规则。有可能触发%.o: %.c规则来编译得到目标文件。 make 的隐含规则数据库可以用 make -p 命令打印。
# default
OUTPUT_OPTION = -o $@
# default
CC = cc
# default
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
%.o: %.c
# commands to execute (built-in):
$(COMPILE.c) $(OUTPUT_OPTION) $<
#号在 Makefile 中表示单行注释。 CC 是一个 Makefile变量,用 CC = cc 定义和赋值,用$(CC)取它的值,其值应该是 cc。 Makefile 变量像 C 的宏定义一样,代表一个字符串,在取值的地方展开。 cc 是一个符号链接,通常指向 gcc。
CFLAGS 、CPPFLAGS 、TARGET_ARCH 这些变量没有定义, 所以$(CFLAGS)、$(CPPFLAGS )、$(TARGET_ARCH )展开是空。这样$(COMPILE.c)展开应该是 cc␣ 空␣ 空␣ 空␣ -c,去掉所有的“空”
得到 cc␣ ␣ ␣ ␣ -c,注意中间留下 4 个空格,所以%.o: %.c 规则的命令$(COMPILE.c)␣ $(OUTPUT_OPTION)␣ $<展开之后是 cc␣ ␣ ␣ ␣ -c␣ -o␣ $@␣$<
$@和$<是两个特殊的变量, $@的取值为规则中的目标, $<的取值为规则中的第一个条件。 %.o: %.c 是一种特殊的规则,称为模式规则(Pattern Rule)。假设在 Makefile 中以 main.o 为目标的规则都没有命令列表,所以 make 会查找隐含规则,发现隐含规则中有这样一条模式规则适用, main.o符合%.o 的模式,现在%就代表 main(称为 main.o 这个名字的 Stem),再替换到%.c 中就是 main.c。所以这条模式规则相当于:
main.o: main.c
cc -c -o main.o main.c
6、多目标规则
多目标的规则, make 会拆成几条单目标的规则来处理,例如
target1 target2: prerequisite1 prerequisite2
command $< -o $@
这样一条规则相当于:
target1: prerequisite1 prerequisite2
command prerequisite1 -o target1
target2: prerequisite1 prerequisite2
command prerequisite1 -o target2
注意两条规则的命令列表是一样的,但$@的取值不同。
7、变量
1)运算符
=运算符 定义变量 会把变量的值推迟到后面定义
:=运算符 定义变量 会立即展开
?=运算符,例如 foo ?= $(bar)的意思是:如果 foo没有定义过,那么?=相当于=,定义 foo 的值是$(bar),但不立即展开;如果先前已经定义了 foo,则什么也不做,不会给 foo 重新赋值。
+=运算符可以给变量追加值,例如
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 后面的空格仍保留。
如果变量还没有定义过就直接用+=赋值,那么+=相当于=。
2)常用的特殊变量有:
- $@,表示规则中的目标。
- $<,表示规则中的第一个条件。
- $?,表示规则中所有比目标新的条件,组成一个列表,以空格分隔。
- $^,表示规则中的所有条件,组成一个列表,以空格分隔
3)定义一个变量的值是一个空格
可以这样:
nullstring :=
space := $(nullstring) # end of the line
nullstring 的值为空, space 的值是一个空格,后面写个注释是为了增加可读性,如果不写注释就换行,则很难看出$(nullstring)后面有个空格。