一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
Makefile简介
gcc处理一个C程序分为预处理和编译两个阶段,类似地,make处理Makefile的过程也分为两个阶段:
- 首先从前到后读取所有规则,建立起一个完整的依赖关系图,例如:
- 然后从缺省目标或者命令行指定的目标开始,根据依赖关系图选择适当的规则执行,执行Makefile中的规则和执行C代码不一样,并不是从前到后按顺序执行,也不是所有规则都要执行一遍,例如make缺省目标时不会更新clean目标,因为从上图可以看出,它跟缺省目标没有任何依赖关系。
clean目标是一个约定俗成的名字,在所有软件项目的Makefile中都表示清除编译生成的文件,类似这样的约定俗成的目标名字有:
- all,执行主要的编译工作,通常用作缺省目标。
- install,执行编译后的安装工作,把可执行文件、配置文件、文档等分别拷到不同的安装目录。
- clean,删除编译生成的二进制文件。
- distclean,不仅删除编译生成的二进制文件,也删除其它生成的文件,例如配置文件和格式转换后的文档,执行make distclean之后应该清除所有这些文件,只留下源文件。
隐含规则和模式规则
Makefile样例如下所示:
main: main.o stack.o maze.o gcc main.o stack.o maze.o -o main main.o: main.h stack.h maze.h stack.o: stack.h main.h maze.o: maze.h main.h main.o: main.c gcc -c main.c stack.o: stack.c gcc -c stack.c maze.o: maze.c gcc -c maze.c clean: -rm main *.o .PHONY: clean
上述makefile可以进行相应的优化:
main: main.o stack.o maze.o gcc main.o stack.o maze.o -o main main.o: main.h stack.h maze.h stack.o: stack.h main.h maze.o: maze.h main.h clean: -rm main *.o .PHONY: clean
下面来看一下优化之后makefile执行过程:
$ make 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
如果一个目标在Makefile中的所有规则都没有命令列表,make会尝试在内建的隐含规则(Implicit Rule)数据库中查找适用的规则。make的隐含规则数据库可以用make -p命令打印,打印出来的格式也是Makefile的格式,包括很多变量和规则,其中和我们这个例子有关的隐含规则有:
# 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中表示单行注释,就像C语言的//注释一样。CC是一个Makefile变量,用CC = cc定义和赋值,用$(CC)取它的值,其值应该是cc。Makefile变量像C的宏定义一样,代表一串字符,在取值的地方展开。cc是一个符号链接,通常指向gcc,在有些UNIX系统上可能指向另外一种C编译器。CFLAGS这个变量没有定义,$(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。所以这条模式规则相当于(stack.o, maze.o做同样的处理):
main.o: main.c cc -c -o main.o main.c
写规则的目的是让make建立依赖关系图,不管怎么写,只要把所有的依赖关系都描述清楚了就行。对于多目标的规则,make会拆成几条单目标的规则来处理,例如:
target1 target2: prerequisite1 prerequisite2 command $< -o $@
Makefile中的变量
$@
,表示规则中的目标。$<
,表示规则中的第一个条件。$?
,表示规则中所有比目标新的条件,组成一个列表,以空格分隔。$^
,表示规则中的所有条件,组成一个列表,以空格分隔。AR
,静态库打包命令的名字,缺省值是ar
。ARFLAGS
,静态库打包命令的选项,缺省值是rv
。AS汇编器的名字,缺省值是
as
。ASFLAGS
,汇编器的选项,没有定义。CC
,C编译器的名字,缺省值是cc
。CFLAGS
,C编译器的选项,没有定义。CXX
,C++编译器的名字,缺省值是g++
。CXXFLAGS
,C++编译器的选项,没有定义。CPP
,C预处理器的名字,缺省值是$(CC) -E
。CPPFLAGS
,C预处理器的选项,没有定义。LD
,链接器的名字,缺省值是ld
。LDFLAGS
,链接器的选项,没有定义。TARGET_ARCH
,和目标平台相关的命令行选项,没有定义。OUTPUT_OPTION
,输出的命令行选项,缺省值是-o $@
。LINK.o
,把.o
文件链接在一起的命令行,缺省值是$(CC) $(LDFLAGS) $(TARGET_ARCH)
。LINK.c
,把.c
文件链接在一起的命令行,缺省值是$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
。LINK.cc
,把.cc
文件(C++源文件)链接在一起的命令行,缺省值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
。COMPILE.c
,编译.c
文件的命令行,缺省值是$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
。COMPILE.cc
,编译.cc
文件的命令行,缺省值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
。RM
,删除命令的名字,缺省值是rm -f
。
自动处理头文件的依赖关系
根据隐含规则Makefile可以写成如下所示:
all: main main: main.o stack.o maze.o gcc $^ -o $@ main.o: main.h stack.h maze.h stack.o: stack.h main.h maze.o: maze.h main.h clean: -rm main *.o .PHONY: clean
按照惯例,用all做缺省目标。现在还有一点比较麻烦,在写main.o、stack.o和maze.o这三个目标的规则时要查看源代码,找出它们依赖于哪些头文件,这很容易出错,一是因为有的头文件包含在另一个头文件中,在写规则时很容易遗漏,二是如果以后修改源代码改变了依赖关系,很可能忘记修改Makefile的规则。为了解决这个问题,可以用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
总结
把这些规则包含到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 $@.$$$$