参考资料:陈浩,《跟我一块儿写makefile》 :http://blog.csdn.net/haoel/article/details/2886/算法
Makefile的格式和规则shell
target ... : prerequisites ...
command
...
...express
target是目标文件,Object File或者可执行文件,或者标签。编程
prerequisites是生成target依赖的全部文件。编程语言
command是make须要执行的命令。(任意的Shell命令)ide
须要注意的是command前必须是一个[Tab]。函数
这是个文件的依赖关系:生成target依赖于prerequisites 里面的文件,生成规则由command给出。实际上,Makefile中最核心的内容是,当prerequisites 中有文件比target新,就执行command中的指令。测试
make识别到一个XXX.o时,能够自动将XXX.c加入到依赖文件中,而且cc –o XXX.c 也能推导出来。所以在书写makefile的时候能够省去那些能够由makefile推导出来的东西。好比以上的makefile简化以后:ui
objects = main.o kbd.o command.o display.o / edit : $(objects) main.o : defs.h .PHONY : clean |
能够看出,每个XXX.o的依赖文件只列出了头文件,下面的command也省略了,由于这两个东西均可以被make自动推导出来。
每个makefile都应该有一个清理规则,clean老是应该放在makefile的最后部分。通常为:
clean: |
更好的作法是:
.PHONY : clean |
.PHONY代表clean是一个伪目标,-rm前面的减号意思是当对某些文件操做失败时,继续后面的操做。
读入全部的Makefile。
读入被include的其它Makefile。
初始化文件中的变量。
推导隐晦规则,并分析全部规则。
为全部的目标文件建立依赖关系链。
根据依赖关系,决定哪些目标要从新生成。
执行生成命令。
makefile的最开始部分应该是make的最终目标,通常状况下一个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的变量在定义时须要赋给一个初值,使用时前面要有一个$符合,通常的使用惯例是“${}”或“$()”,若要使用真正的“$”符号则表示为“$$”。一个简单的例子:
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的函数只有那么多,不能由用户本身来定义,不过也够用了。函数的调用语法以下:
$(<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>中使用$,由于此处指的是变量名。
函数的返回值有几种状况:
若是<variable>历来没有定义过,origin函数返回这个值“undefined”。
若是<variable>是一个默认的定义,好比“CC”这个变量,这种变量咱们将在后面讲述。
若是<variable>是一个环境变量,而且当Makefile被执行时,“-e”参数没有被打开。
若是<variable>这个变量被定义在Makefile中。
若是<variable>这个变量是被命令行定义的。
若是<variable>是被override指示符从新定义的。
若是<variable>是一个命令运行中的自动化变量。
$(shell <command> <var>)
shell函数的做用是在makefile中调用shell命令来处理变量,返回值就是shell命令执行的结果。好比:
files := $(shell echo *.c) |
files最后的值就是全部.c文件。
make的控制函数:
$(error <text ...>)
产生一个致命的错误,<text ...>是错误信息,并结束make。
$(warning <text ...>)
产生一个warning信息,并继续执行make。
“all”
这个伪目标是全部目标的目标,其功能通常是编译全部的目标。
“clean”
这个伪目标功能是删除全部被make建立的文件。
“install”
这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
“print”
这个伪目标的功能是例出改变过的源文件。
“tar”
这个伪目标功能是把源程序打包备份。也就是一个tar文件。
“dist”
这个伪目标功能是建立一个压缩文件,通常是把tar文件压成Z文件。或是gz文件。
“TAGS”
这个伪目标功能是更新全部的目标,以备完整地重编译使用。
“check”和“test”
这两个伪目标通常用来测试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”参数一同使用,来查看这个依赖文件所发生的规则命令。
“-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”选项的做用
一、编译C程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.c”,而且其生成命令是“$(CC) –c $(CPPFLAGS) $(CFLAGS)”
二、编译C++程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.cc”或是“<n>.C”,而且其生成命令是“$(CXX) –c $(CPPFLAGS) $(CFLAGS)”。(建议使用“.cc”做为C++源文件的后缀,而不是“.C”)
七、汇编和汇编预处理的隐含规则。
“<n>.o” 的目标的依赖目标会自动推导为“<n>.s”,默认使用编译品“as”,而且其生成命令是:“$(AS) $(ASFLAGS)”。“<n>.s” 的目标的依赖目标会自动推导为“<n>.S”,默认使用C预编译器“cpp”,而且其生成命令是:“$(AS) $(ASFLAGS)”。
八、连接Object文件的隐含规则。
“<n>”目标依赖于“<n>.o”,经过运行C的编译器来运行连接程序生成(通常是“ld”),其生成命令是:“$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)”。这个规则对于只有一个源文件的工程有效,同时也对多个Object文件(由不一样的源文件生成)的也有效。
隐含规则使用的变量:
一、关于命令的变量。
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来搜索。
一、把T的目录部分分离出来。叫D,而剩余部分叫N。(如:若是T是"src/foo.o",那么,D就是"src/",N就是"foo.o")
二、建立全部匹配于T或是N的模式规则列表。
三、若是在模式规则列表中有匹配全部文件的模式,如"%",那么从列表中移除其它的模式。
四、移除列表中没有命令的规则。
五、对于第一个在列表中的模式规则:
1)推导其"茎"S,S应该是T或是N匹配于模式中"%"非空的部分。
2)计算依赖文件。把依赖文件中的"%"都替换成"茎"S。若是目标模式中没有包含斜框字符,而把D加在第一个依赖文件的开头。
3)测试是否全部的依赖文件都存在或是理当存在。(若是有一个文件被定义成另一个规则的目标文件,或者是一个显式规则的依赖文件,那么这个文件就叫"理当存在")
4)若是全部的依赖文件存在或是理当存在,或是就没有依赖文件。那么这条规则将被采用,退出该算法。
六、若是通过第5步,没有模式规则被找到,那么就作更进一步的搜索。对于存在于列表中的第一个模式规则:
1)若是规则是终止规则,那就忽略它,继续下一条模式规则。
2)计算依赖文件。(同第5步)
3)测试全部的依赖文件是否存在或是理当存在。
4)对于不存在的依赖文件,递归调用这个算法查找他是否能够被隐含规则找到。
5)若是全部的依赖文件存在或是理当存在,或是就根本没有依赖文件。那么这条规则被采用,退出该算法。
七、若是没有隐含规则可使用,查看".DEFAULT"规则,若是有,采用,把".DEFAULT"的命令给T使用。
一旦规则被找到,就会执行其至关的命令,而此时,咱们的自动化变量的值才会生成。