参考资料:陈浩,《跟我一起写makefile》 :http://blog.csdn.net/haoel/article/details/2886/
Makefile的格式和规则
target ... : prerequisites ...
command
...
...
target是目标文件,Object File或者可执行文件,或者标签。
prerequisites是生成target依赖的所有文件。
command是make需要执行的命令。(任意的Shell命令)
需要注意的是command前必须是一个[Tab]。
这是个文件的依赖关系:生成target依赖于prerequisites 里面的文件,生成规则由command给出。实际上,Makefile中最核心的内容是,当prerequisites 中有文件比target新,就执行command中的指令。
make的自动推导
make识别到一个XXX.o时,可以自动将XXX.c加入到依赖文件中,并且cc –o XXX.c 也能推导出来。因此在书写makefile的时候可以省去那些可以由makefile推导出来的东西。比如以上的makefile简化之后:
objects = main.o kbd.o command.o display.o / edit : $(objects) main.o : defs.h .PHONY : clean |
可以看出,每一个XXX.o的依赖文件只列出了头文件,下面的command也省略了,因为这两个东西都可以被make自动推导出来。
clean规则
每一个makefile都应该有一个清理规则,clean总是应该放在makefile的最后部分。一般为:
clean: |
更好的做法是:
.PHONY : clean |
.PHONY表明clean是一个伪目标,-rm前面的减号意思是当对某些文件操作失败时,继续后面的操作。
make的工作流程
读入所有的Makefile。
读入被include的其它Makefile。
初始化文件中的变量。
推导隐晦规则,并分析所有规则。
为所有的目标文件创建依赖关系链。
根据依赖关系,决定哪些目标要重新生成。
执行生成命令。
makefile的最开始部分应该是make的最终目标,一般情况下一个makefile都只有一个最终目标。
makefile的文件搜寻
当工程的文件放在不同的目录时,写makefile时需要指定搜寻的目录和规则。
VPATH = ….
makefile中有一个特殊变量VPATH,通过指定VPATH告诉make在VPATH所指的目录寻找依赖的文件(当然是在当前目录没有找到的前提下),多个目录用冒号隔开。
vpath <pattern> <directories>
<pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件。<pattern>指定了要搜索的文件集,而<directories>则指定了<pattern>的文件集的搜索的目录,多个目录用冒号隔开。
vpath和上面的VPATH并不相同,vpath是makefile中的关键字,它可以指定不同的文件在不同的搜索目录中,比如:
vpath %.h ../headers
表示在../headers目录搜索.h头文件。
伪目标
前面提到过伪目标,伪目标其实和真正的目标区别不大,最大的区别就是伪目标只是一个便签,不生成真正的目标文件。我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。如:
.PHONY clean
伪目标可以非常有用。之前我们说过,makefile一般只有一个最终目标,就是其第一个目标,如果想一次性生成多个目标怎么办呢?这就可以借助于伪目标来实现。比如:
all : prog1 prog2 prog3 prog1 : prog1.o utils.o prog2 : prog2.o prog3 : prog3.o sort.o utils.o |
这里的最终目标是一个伪目标all,在执行make 时候伪目标总是会被执行,因此依赖的三个文件会被生成。
再比如,我们在清理的时候可能需要执行不同级别的清理,那么也可以用伪目标来区分。
多目标
上文提到要生成多个目标时候可以使用伪目标来实现,然而实际上makefile的target可以不止一个。有些情况下,这一组目标有某个或者某些相同的依赖文件而且生成的规则相似,那么就可以使用makefile的多目标来生成。如:
bigoutput littleoutput : text.g | bigoutput : text.g |
左边的规则与右边的等价。简单解释一下,由于bigoutput和littleoutput都依赖文件text.g,且生成规则相似,则写成下面一行的命令形式。其中-$(subst output,,$@)中的”$”表示要执行一个makefile函数,函数名是subst,后面都是参数。“$@”表示目标的集合。这个函数的作用是截取字符串。
在定义多目标规则时,利用静态模式将更加有用。静态模式的基本语法是:
<targets ...>: <target-pattern>: <prereq-patterns ...> |
targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-parrtern是指明了targets的模式,也就是的目标集模式。
prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。
换一种更加通俗的说法,target定义的是一个目标的集合,里面包含了很多的目标,而target-pattern定义的是本次命令执行的目标模式,也就是说从target目标集合里选出匹配target-pattern模式的目标来作为本次命令的真正目标。而prereq-patterns定义的是依赖文件的匹配模式。举个简单的例子,如果target-pattern为“%.o”,那么目标集合就是target中所有以[.o]为后缀的目标文件。而如果prereq-patterns定义为“%.c”,于是依赖的文件就是target-pattern匹配的所有的[.o]目标文件对应的[.c]文件。
一个例子:
objects = foo.o bar.o all: $(objects) $(objects): %.o: %.c |
由之前的分析可以看出,target-pattern为[%.o]匹配了objects中所有的[.o]目标文件,即foo.o bar.o,而[%.c]表明命令的依赖文件是foo.c bar.c。命令中的[$<]表示所有的依赖目标集(也就是foo.c bar.c),[$@]表示目标集(也就是foo.o bar.o)。
自动生成依赖性
makefile的变量
makefile的变量在定义时需要赋给一个初值,使用时前面要有一个$符合,一般的使用惯例是“${}”或“$()”,若要使用真正的“$”符号则表示为“$$”。一个简单的例子:
edit : main.o kbd.o command.o display.o / main.o : main.c defs.h |
这个makefile的edit依赖文件很多,可以用一个变量来代替它。
objects = main.o kbd.o command.o display.o / |
于是,第一行就可以变为:
edit : $(objects) |
这样为书写makefile,以及在大的工程中修改makefile带了很大的便利,也让makefile更加简洁易懂。makefile中的目标、依赖、命令都可以用变量来表示。makefile定义变量的方法有三种,一种是像通常的编程语言一样使用类似如下的方式。这种方式定义变量的好处在于可以将变量的定义放在后面,换句话说,前面的命令可以使用后面定义的变量。
objects = main.o kbd.o command.o display.o
另外一种变量定义方式是使用“:=”操作符,这种定义方式不能使用后面定义的变量。例如:
x := foo |
等价于
y := foo bar |
而,
y := $(x) bar x := foo |
却等价于
y := bar x := foo |
还有一种用于定义变量的操作符“?=”,
x ?= foo
表示如果x没有定义过,那么x = foo,否则什么也不做。
变量值的替换的例子:
foo := a.o b.o c.o | foo := a.o b.o c.o |
上面两种表示的效果是一样的——变量替换,让$(bar)变量的值为“a.c b.c c.c”,但是有一点点不同。左边$(foo:.o=.c)表示的是,变量foo中所有.o结尾的字符串,替换成.c结尾的字符串。右边$(foo:%.o=%.c)这种形式是静态模式的表示形式,变量foo中匹配模式%.o的替换成了%.c,%是必需的。
我们可以使用“+=”操作符给变量追加值,即增加一个值。
objects = main.o foo.o bar.o utils.o
objects += another.o
define关键字可以用来定义多行变量,如:
define two-lines | 以define开头,以endef结尾。命令前面必需有[Tab],相当于 two-lines = foo $(bar) |
有一种非常有用的变量叫目标变量,定义语法和举例如下:
<target ...> : <variable-assignment> <target ...> : overide <variable-assignment> | prog : CFLAGS = -g prog.o : prog.c foo.o : foo.c bar.o : bar.c |
当设置了目标变量时,这个变量会作用到由这个目标所引发的所有的规则中去。如上例,prog : CFLAGS = -g中定义了目标变量CFLAGS,因此在所有由prog引起的命令中,这个CFLAGS都会起作用。target还可以用模式变量。如:
<pattern ...> : <variable-assignment>
<pattern ...> : override <variable-assignment>
%.o : CFLAGS = -O
条件判断
libs_for_gcc = -lgnu foo: $(objects) |
条件判断由ifeq开始,后面括号里是进行比较的两个变量或者常量,ifeq ($(CC),gcc)表示如果变量CC是gcc的话,执行下一条命令,否则(else)执行else下的命令,判断由endif结束。实际上,ifeq可以由下面几种方式来使用,而上例使用的是第一种方式。
ifeq (<arg1>, <arg2>) |
另外一个条件判断关键字是ifneq,意思很明白,和ifeq的恰好相反,但是用法相同。
第三个条件关键字是“ifdef”,
ifdef <variable-name>
如果变量<variable-name>的值非空,那到表达式为真。否则,表达式为假。实际上,ifdef就是测试变量是否有值。另外还有一个与之对应的ifndef,意思非常明确就不多说了。
在条件判断中最好不要出现自动化变量(如”$@”),因为自动化变量在运行时才会有值。
makefile的函数
实际上在之前的例子中已经多次提到过makefile的函数了。当然这个函数和我们程序语言的函数有相同点也有很大的不同,makefile函数调用后有返回值,而且我们可以使用这个返回值,但是makefile的函数只有那么多,不能由用户自己来定义,不过也够用了。函数的调用语法如下:
$(<function> <arguments1,arguments2,arguments3>) 或者使用{}括号 |
<function>是函数名,后面是参数列表,函数名和参数之间用空格隔开,参数之间用逗号隔开。
字符串处理函数:
$(subst <from>,<to>,<text>)
名称:字符串替换函数——subst。 |
$(patsubst <pattern>,<replacement>,<text>)
名称:模式字符串替换函数——patsubst。 返回:函数返回被替换过后的字符串。 |
$(strip <string>)
名称:去空格函数——strip。 |
$(findstring <find>,<in>)
名称:查找字符串函数——findstring。 |
$(filter <pattern...>,<text>)
名称:过滤函数——filter。 |
$(filter-out <pattern...>,<text>)
名称:反过滤函数——filter-out。 |
$(sort <list>)
名称:排序函数——sort。 备注:sort函数会去掉<list>中相同的单词。 |
$(word <n>,<text>)
名称:取单词函数——word。 |
$(wordlist <s>,<e>,<text>)
名称:取单词串函数——wordlist。 |
$(words <text>)
名称:单词个数统计函数——words。 备注:如果我们要取<text>中最后的一个单词,我们可以这样:$(word $(words <text>),<text>)。 |
$(firstword <text>)
名称:首单词函数——firstword。 |
文件名操作函数:
$(dir <names...>)
名称:取目录函数——dir。 |
$(notdir <names...>)
名称:取文件函数——notdir。 |
$(suffix <names...>)
名称:取后缀函数——suffix。 |
$(basename <names...>)
名称:取前缀函数——basename。 |
$(addsuffix <suffix>,<names...>)
名称:加后缀函数——addsuffix。 |
$(addprefix <prefix>,<names...>)
名称:加前缀函数——addprefix。 |
$(join <list1>,<list2>)
名称:连接函数——join。 |
特殊函数:
$(foreach <var>,<list>,<text>)
foreach函数和shell中的for语句类似,用于循环控制。这个命令的解释是:将<list>中的字符串逐一取出放入临时变量<var>中(循环完成,<var>便不复存在),然后再执行<text>中包含的命令,最后将执行之后的结果返回。每次返回的结果由一个空格隔开,循环完成之后的返回值就是由空格隔开的一系列结果。
例如:
names := a b c d files := $(foreach n,$(names),$(n).o) |
$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,$(files)的值是“a.o b.o c.o d.o”。
$(if <condition>,<then-part>,<else-part>)
if函数是一个条件控制语句,首先计算<condition>,如果<condition>返回非空字符,则计算<then-part>,否则计算<else-part>,当然和编程语言一样<else-part>是可选的。最后函数返回计算部分的结果。
$(call <expression>,<parm1>,<parm2>,<parm3>...)
call函数计算<expression>的结果,<expression>中的变量,如$(1),$(2),$(3),会被parm1,parm2,parm3替换。最后函数返回<expression>的结果。
例如:
reverse = $(1) $(2) foo = $(call reverse,a,b) |
此时的foo的结果就是“a b”。
$(origin <variable>)
origin函数返回变量<variable>是在哪儿定义的。一般不在<variable>中使用$,因为此处指的是变量名。
函数的返回值有几种情况:
- “undefined”
如果<variable>从来没有定义过,origin函数返回这个值“undefined”。
- “default”
如果<variable>是一个默认的定义,比如“CC”这个变量,这种变量我们将在后面讲述。
- “environment”
如果<variable>是一个环境变量,并且当Makefile被执行时,“-e”参数没有被打开。
- “file”
如果<variable>这个变量被定义在Makefile中。
- “command line”
如果<variable>这个变量是被命令行定义的。
- “override”
如果<variable>是被override指示符重新定义的。
- “automatic”
如果<variable>是一个命令运行中的自动化变量。
$(shell <command> <var>)
shell函数的作用是在makefile中调用shell命令来处理变量,返回值就是shell命令执行的结果。比如:
files := $(shell echo *.c) |
files最后的值就是所有.c文件。
make的控制函数:
$(error <text ...>)
产生一个致命的错误,<text ...>是错误信息,并结束make。
$(warning <text ...>)
产生一个warning信息,并继续执行make。
GNU makefile中的一些常见目标
“all”
这个伪目标是所有目标的目标,其功能一般是编译所有的目标。
“clean”
这个伪目标功能是删除所有被make创建的文件。
“install”
这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
“print”
这个伪目标的功能是例出改变过的源文件。
“tar”
这个伪目标功能是把源程序打包备份。也就是一个tar文件。
“dist”
这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。
“TAGS”
这个伪目标功能是更新所有的目标,以备完整地重编译使用。
“check”和“test”
这两个伪目标一般用来测试makefile的流程。
makefile的检查规则
“-n”
“--just-print”
“--dry-run”
“--recon”
不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行
“-t”
“--touch”
这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。
“-q”
“--question”
这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。
“-W <file>”
“--what-if=<file>”
“--assume-new=<file>”
“--new-file=<file>”
这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。
make的参数
“-b”
“-m”
这两个参数的作用是忽略和其它版本make的兼容性。
“-B”
“--always-make”
认为所有的目标都需要更新(重编译)。
“-C <dir>”
“--directory=<dir>”
指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。
“—debug[=<options>]”
输出make的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。下面是<options>的取值:
a —— 也就是all,输出所有的调试信息。(会非常的多)
b —— 也就是basic,只输出简单的调试信息。即输出不需要重编译的目标。
v —— 也就是verbose,在b选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编译的依赖文件(或是依赖目标)等。
i —— 也就是implicit,输出所以的隐含规则。
j —— 也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等。
m —— 也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息。
“-d”
相当于“--debug=a”。
“-e”
“--environment-overrides”
指明环境变量的值覆盖makefile中定义的变量的值。
“-f=<file>”
“--file=<file>”
“--makefile=<file>”
指定需要执行的makefile。
“-h”
“--help”
显示帮助信息。
“-i”
“--ignore-errors”
在执行时忽略所有的错误。
“-I <dir>”
“--include-dir=<dir>”
指定一个被包含makefile的搜索目标。可以使用多个“-I”参数来指定多个目录。
“-j [<jobsnum>]”
“--jobs[=<jobsnum>]”
指同时运行命令的个数。
“-k”
“--keep-going”
出错也不停止运行。
“-l <load>”
“--load-average[=<load]”
“—max-load[=<load>]”
指定make运行命令的负载。
“-n”
“--just-print”
“--dry-run”
“--recon”
仅输出执行过程中的命令序列,但并不执行。
“-o <file>”
“--old-file=<file>”
“--assume-old=<file>”
不重新生成的指定的<file>,即使这个目标的依赖文件新于它。
“-p”
“--print-data-base”
输出makefile中的所有数据,包括所有的规则和变量。
“-r”
“--no-builtin-rules”
禁止make使用任何隐含规则。
“-R”
“--no-builtin-variabes”
禁止make使用任何作用于变量上的隐含规则。
“-s”
“--silent”
“--quiet”
在命令运行时不输出命令的输出。
“-S”
“--no-keep-going”
“--stop”
取消“-k”选项的作用
隐含规则
1、编译C程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.c”,并且其生成命令是“$(CC) –c $(CPPFLAGS) $(CFLAGS)”
2、编译C++程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.cc”或是“<n>.C”,并且其生成命令是“$(CXX) –c $(CPPFLAGS) $(CFLAGS)”。(建议使用“.cc”作为C++源文件的后缀,而不是“.C”)
7、汇编和汇编预处理的隐含规则。
“<n>.o” 的目标的依赖目标会自动推导为“<n>.s”,默认使用编译品“as”,并且其生成命令是:“$(AS) $(ASFLAGS)”。“<n>.s” 的目标的依赖目标会自动推导为“<n>.S”,默认使用C预编译器“cpp”,并且其生成命令是:“$(AS) $(ASFLAGS)”。
8、链接Object文件的隐含规则。
“<n>”目标依赖于“<n>.o”,通过运行C的编译器来运行链接程序生成(一般是“ld”),其生成命令是:“$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)”。这个规则对于只有一个源文件的工程有效,同时也对多个Object文件(由不同的源文件生成)的也有效。
隐含规则使用的变量:
1、关于命令的变量。
AR
函数库打包程序。默认命令是“ar”。
AS
汇编语言编译程序。默认命令是“as”。
CC
C语言编译程序。默认命令是“cc”。
CXX
C++语言编译程序。默认命令是“g++”。
CO
从 RCS文件中扩展文件程序。默认命令是“co”。
CPP
C程序的预处理器(输出是标准输出设备)。默认命令是“$(CC) –E”。
RM
删除文件命令。默认命令是“rm –f”。
ARFLAGS
函数库打包程序AR命令的参数。默认值是“rv”。
ASFLAGS
汇编语言编译器参数。(当明显地调用“.s”或“.S”文件时)。
CFLAGS
C语言编译器参数。
CXXFLAGS
C++语言编译器参数。
CPPFLAGS
C预处理器参数。( C 和 Fortran 编译器也会用到)。
LDFLAGS
链接器参数。(如:“ld”)
自动化变量
所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。
$@
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
$%
仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。
$<
依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$?
所有比目标新的依赖目标的集合。以空格分隔。
$^
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
$+
这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
$*
这个变量表示目标模式中"%"及其之前的部分。
$(@D)
表示"$@"的目录部分(不以斜杠作为结尾),如果"$@"值是"dir/foo.o",那么"$(@D)"就是"dir",而如果"$@"中没有包含斜杠的话,其值就是"."(当前目录)。
$(@F)
表示"$@"的文件部分,如果"$@"值是"dir/foo.o",那么"$(@F)"就是"foo.o","$(@F)"相当于函数"$(notdir $@)"。
"$(*D)"
"$(*F)"
和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子,"$(*D)"返回"dir",而"$(*F)"返回"foo"
"$(%D)"
"$(%F)"
分别表示了函数包文件成员的目录部分和文件部分。这对于形同"archive(member)"形式的目标中的"member"中包含了不同的目录很有用。
"$(<D)"
"$(<F)"
分别表示依赖文件的目录部分和文件部分。
"$(^D)"
"$(^F)"
分别表示所有依赖文件的目录部分和文件部分。(无相同的)
"$(+D)"
"$(+F)"
分别表示所有依赖文件的目录部分和文件部分。(可以有相同的)
"$(?D)"
"$(?F)"
分别表示被更新的依赖文件的目录部分和文件部分。
隐含规则搜索算法
比如我们有一个目标叫 T。下面是搜索目标T的规则的算法。请注意,在下面,我们没有提到后缀规则,原因是,所有的后缀规则在Makefile被载入内存时,会被转换成模式规则。如果目标是"archive(member)"的函数库文件模式,那么这个算法会被运行两次,第一次是找目标T,如果没有找到的话,那么进入第二次,第二次会把"member"当作T来搜索。
1、把T的目录部分分离出来。叫D,而剩余部分叫N。(如:如果T是"src/foo.o",那么,D就是"src/",N就是"foo.o")
2、创建所有匹配于T或是N的模式规则列表。
3、如果在模式规则列表中有匹配所有文件的模式,如"%",那么从列表中移除其它的模式。
4、移除列表中没有命令的规则。
5、对于第一个在列表中的模式规则:
1)推导其"茎"S,S应该是T或是N匹配于模式中"%"非空的部分。
2)计算依赖文件。把依赖文件中的"%"都替换成"茎"S。如果目标模式中没有包含斜框字符,而把D加在第一个依赖文件的开头。
3)测试是否所有的依赖文件都存在或是理当存在。(如果有一个文件被定义成另外一个规则的目标文件,或者是一个显式规则的依赖文件,那么这个文件就叫"理当存在")
4)如果所有的依赖文件存在或是理当存在,或是就没有依赖文件。那么这条规则将被采用,退出该算法。
6、如果经过第5步,没有模式规则被找到,那么就做更进一步的搜索。对于存在于列表中的第一个模式规则:
1)如果规则是终止规则,那就忽略它,继续下一条模式规则。
2)计算依赖文件。(同第5步)
3)测试所有的依赖文件是否存在或是理当存在。
4)对于不存在的依赖文件,递归调用这个算法查找他是否可以被隐含规则找到。
5)如果所有的依赖文件存在或是理当存在,或是就根本没有依赖文件。那么这条规则被采用,退出该算法。
7、如果没有隐含规则可以使用,查看".DEFAULT"规则,如果有,采用,把".DEFAULT"的命令给T使用。
一旦规则被找到,就会执行其相当的命令,而此时,我们的自动化变量的值才会生成。