有如下的源代码树:
根目录
|---makefile
|----|include|
| |___myutil.h
|___|src|
|----myutil.c
|__main.c
各文件内容如下:
main.c:
#include <stdio.h>
#include "myutil.h"
int main(void) {
myprint();
return 0;
}
myutil.h:
void myprint();
myutil.c:
#include <stdio.h>
void myprint(void) {
printf("this is myprint function.\n");
}
makefile:
VPATH=src include
CPPFLAGS=-I include
main:main.o myutil.o
gcc $(CPPFLAGS) $^ -o $@
main.o:main.c myutil.h
gcc $(CPPFLAGS) -c $^
myutil.o:myutil.c myutil.h
gcc $(CPPFLAGS) -c $^
通过内置的规则,我们可以将makefile文件缩减到如下:
VPATH=src include
CPPFLAGS=-I include
main:myutil.o
main.o:myutil.h
myutil.o:myutil.h
所有的内置规则都是模式规则的实例。上面的makefile之所以可行是因为make里存在三项内置规则。第一项描述了如何
从一个.c文件编译出一个.o文件:
%.o:%.c
$(COMPILE.c) $(OUTPUT_OPTION) $<
第二项规则描述了如何从.l文件产生一个.c文件:
%.c:%.l
@$(RM) $@
$(LEX.l) $< > $@
最后是一项特殊的规则,描述了如何从.c文件产生出一个不具扩展名的文件
%:%.c
$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
我们可以通过在脚本中更改变量的值来自定义内置规则,一个典型的规则包含一群变量,以所要执行的程序开头,并且
包括用来设定主要命令行选项的变量。可以通过运行make --print-data-base列出make具有哪些默认规则(和变量)。
模式规则中的百分比字符%大体上等效于unix shell中的星号*,它可以代表任意多个字符,百分比字符可以放在模式中的
任何地方,不过只能出现一次。百分比字符的正确用法如下:
%,v
s%.o
wrapper_%
在文件中,百分比以外的字符按照字面进行匹配。一个模式可以包含一个前缀或一个后缀,或者两者同时存在。当make
所有所要的模式规则时,它首先会查找相符的模式规则工作目标。模式规则工作目标必须以前缀开头并且以后缀结尾(如
果它们存在的话)。如果找到相符的模式规则工作目标,则前缀和后缀之前的字符会被作为文件名的词干。接着make会
通过将词干替换到必要条件模式中来检查该模式规则的必要条件。如果产生的文件名存在,或是可以应用另一项规则进行
产生的工作,则会进行比较以及应用规则的动作。
事实上,我们还可以用到一个百分比字符的模式。此模式最常被用来编译Unix可执行程序。例如:
%:%.cpp
$(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@
静态模式规则只能应用在特定的工作目标上。
$(OBJECT):%.o:%.c
$(CC) -c $(CFLAGS) $< -o $@
此规则与一般模式规则的唯一差别是开头的$(OBJECTS):规范。这将使得该规则只能应用在$(OBJECTS)变量中所列举的
文件上。%.o模式会匹配$(OBJECTS)中所列举的每个目标文件并且取出其词干。然后该词干会被替换进%.c模式,以产生
工作目标的必要条件。
后缀规则是用来定义隐含规则的最初方法(也是过时的方法)。后缀规则中的工作目标,可以是一个扩展名或两个被衔接在一
起的扩展名:
.c.o:
$(COMPILE.c) $(OUTPUT_OPTION) $<
这令人有些疑惑,因为必要条件的扩展名被摆到开头,而工作目标退居第二位。这个规则所匹配的工作目标以及必要条件跟
下面的规则一样:
%.o:%.c
$(COMPILE.c) $(OUTPUT_OPTION) $<
上面这个后缀规则就是所谓的双后缀规则,因为它包含了两个扩展名。你还可以使用单后缀规则,单后缀规则只包含一个
扩展名,也就是源文件的扩展名。这个规则可用来创建可至执行文件,因为Unix上的可执行文件不需要扩展名。
.p:
$(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@
这个规则的作用等效于下面这个模式规则:
%:%.p
$(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@
特殊工作目标是一个内置的假想工作目标,用来变更make的默认行为,例如.PHONY这个特殊工作目标用来声明它的必要
条件并不代表一个实际的文件,而且应该被视为尚未更新。.PHONY将会是最常见的特殊工作目标,但是你还会看到其他
的特殊工作目标。
特殊工作目标的语法跟一般工作目标的语法没有不同,也就是target:prerequsite,但是target并非文件而是一个假想工作目
标。它们实际上比较像是用来修改make内部算法的指令。
目前共有12个特殊目标,可分为三类:第一类用来在更新工作目标时修改make的行为;第二类的动作就好像是make的全局
标记,用来忽略相应的工作目标;最后是.SUFFIXES这个工作目标,用来制定旧式的后缀规则。
下面列出(除了.PHONY之外的)最有用的工作目标修饰符。
.INTERMEDIATE
这个特殊工作目标的必要条件会被视为中间文件。如果make在更新另一个工作目标期间创建了该文件,则该文件将会在make
运行结束时被自动删除。如果在make想要更新该文件之际该文件已经存在了,则该文件不会被删除。
.SECONDARY
这个特殊工作目标的必要条件被视为中间文件,但是不会被自动删除。.SECONDARY最常用来标示存储在程序库(library)
里的目标文件(object file)。按照惯例,这些目标文件一旦被加入档案(archive)就会被删除。在项目开发期间保存这些
目标文件,但仍使用make进行程序库的更新,有时会比较方便。
.PRECIOUS
当make在运行期间被中断时,如果自make启动以来该文件被修改过,make将会删除它正在更新的工作目标文件,因此,
make不会再编译树中留下尚未编译完成的文件。但有些时候你却不希望make这么做,特别是在该文件很大而且编译的
代价很高时,如果该文件极为珍贵,你就用.PRECIOUS来标示它,这样make才不会在自己被中断时删除该文件。
.DELETE_ON_ERROR
.DELETE_ON_ERROR的作用于.PRECIOUS相反。将工作目标标示成.DELETE_ON_ERROR,表示如果与规则相应的
任何命令在运行时发生错误的话,就应该删除该工作目标。make通常只有在自己被信号中断时才会删除该工作目标文件。
对于本文中的例子,我们可以轻易地在makefile文件中手动加入目标文件与C头文件的依存关系,但是在正常的程序里,这
是一个烦人的工作。事实上,在大多数程序中,这几乎是不可能的事,因为大多数的头文件还会包含其他头文件所形成的复杂
树状结构。比如,在我的系统上,头文件stdio.h会被扩展成包含15个其他的头文件。以手动方式解析这些关系是一个令人绝望
的工作,但是如果这些文件的重新编译失败,可能会导致数小时的调试。
在gcc中有一个选项,可以编写出这些依存关系。
root@yan-vm:~# echo "#include <stdio.h>" > stdio.c
root@yan-vm:~# gcc -M stdio.c
stdio.o: stdio.c /usr/include/stdio.h /usr/include/features.h \
/usr/include/i386-linux-gnu/bits/predefs.h \
/usr/include/i386-linux-gnu/sys/cdefs.h \
/usr/include/i386-linux-gnu/bits/wordsize.h \
/usr/include/i386-linux-gnu/gnu/stubs.h \
/usr/include/i386-linux-gnu/gnu/stubs-32.h \
/usr/lib/gcc/i686-linux-gnu/4.7/include/stddef.h \
/usr/include/i386-linux-gnu/bits/types.h \
/usr/include/i386-linux-gnu/bits/typesizes.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/i686-linux-gnu/4.7/include/stdarg.h \
/usr/include/i386-linux-gnu/bits/stdio_lim.h \
/usr/include/i386-linux-gnu/bits/sys_errlist.h
有两种方法可用来将自动产生的依存关系纳入makefile:
第一种是编写一个脚本自动加入这些内容到makefile中。
第二种方法就是为make加入一个include指令。编写一个makefile工作目标,此工作目标的动作就是以-M选项对所有源文件
执行gcc,并将结果存入一个依存文件中,然后执行make以便把刚才产生的依存文件引入makefile,这样就可以出发我们
所需要的更新动作,在GNU make你可以使用如下的规则来实现此目的:
depend:count_words.c lexer.c counter.c
$(CC) -M $(CPPFLAGS) $^ > $@
include depend
---------------------------------------------------------------------------------------------------------------------------------------------------------
对include命令的说明:
make程序在处理指示符 include时,将暂停对当前使用指示符“include”的 makefile 文件的读取,而转去依此读取由
“include”指示符指定的文件列表。直到完成所有这些文件以后再回过头继续读取指示符“include”所在的 makefile
文件。通常指示符“include”用在以下场合:
1. 有多个不同的程序,由不同目录下的几个独立的Makefile来描述其创建或者更新规则。它们需要使用一组通用的
变量定义或者模式规则。通用的做法是将这些共同使用的变量或者模式规则定义在一个文件中,在需要使用的Makefile
中使用指示符“include”来包含此文件。
2.当根据源文件自动产生依赖文件时;我们可以将自动产生的依赖关系保存在另外一个文件中,主Makefile使用指示符
“include”包含这些文件。这样的做法比直接在主Makefile中追加依赖文件的方法要明智的多。其它版本的make已经
使用这种方式来处理。