分类:
2010-10-08 23:00:38
GNU Make的使用简记 1
Email: |
zcatt@163.com |
Blog |
http://zcatt.cublog.cn |
文档简要整理了GNU
Make的语法,函数,和命令。备忘和参考。本文的pdf格式已经发布到百度wenku.
GNUmakefile
,makefile
,和Makefile
是make的默认处理的脚本文件。直接使用make命令就会处理当前目录下的makefile脚本
|
如果你的make脚本文件名不是makefile,则应当使用下面三种形式之一.
|
make后面不指定target时,将会更新default target。make后面当然也可以指定要更新的target,脚本中经常定义的target有
|
默认的执行脚本中的第一个target, 若指定target时,使用如下。
|
make返回的exit status如下
|
make命令行可以带一些选项option.
'-n',
'--just-print'
仅打印出要执行的recipe,而不实际执行
'-t'
,
'--touch'
仅更新target文件的时标timestamp,满足时间依赖关系,而不实际执行recipe生成/改变target。例如源文件变了,而不想实际编译产生对应的obj,可以使用这个选项。
'-C dir',
'--directory=dir'
变更路径到dir,然后读入makefile。
'-I',
'--include-dir=dir'
指定搜寻的dir,用于查找included makefile
'-s',
'--silent', '--quiet'
recipe执行时不要打印输出提示。
makefile脚本文件最基本的元素是rule(规则)。 Rule的形式如下
|
|
target时间依赖prerequisites, 也就是说如果prerequisites发生变动了(时间新于target),破坏了target时间依赖于prerequisites的条件,那么这个rule将被选中,执行recipe定义的动作,通常是生成时间更新的target文件。
注意, recipe前是一个tab字符, 而不是空格。makefile脚本中长行需要折行时可以行尾使用'\'
Rule中如果prerequisites是空,这样的rule,总是认为依赖条件满足,recipe不会被选中执行。除非在make中明确指明的调用。
makefile脚本文件通常由多个rule组成, 第一个rule称为default rule。make默认从default rule的target开始, 除非make特别指定其他target。
简单的例子
HelloWorld : helloworld.o display.o gcc -o HelloWorld helloworld.o display.o helloworld.o : helloworld.c helloworld.h display.h gcc -c helloworld.c display.o : display.c display.h gcc -c display.c clean : rm HelloWorld helloworld.o display.o |
小结
1)Rule三要素: target, prerequisites, recipe。
2)prerequesites为空的rule,不会被选择执行,除非make特别指明它执行。
3)make默认从default rule的target开始执行。
变量的定义
|
变量的使用
or
|
例如
|
那么碰到$(Object)
,将被展开称helloworld.o display.o
makefile脚本有五种元素组成
1. explicit rule, 显示规则, 具体列出的方式指定target。
2. implicit rule, 隐式规则, 针对具有相同文件名特征的文件的规则。
3. variable definition, 变量定义语句
4. directive, 命令语句,定义make应当采取的特别处理。
5.
comment, 注释,'#'
开始直到行尾。
make处理脚本分成两个阶段:
阶段1, read-in phase: 读入脚本,展开显示和隐式的变量,分析各个Rule,构造target的依赖图(dependency graph)。
阶段2,target-update phase: 根据依赖图结构, 调用相应的recipe,生成相关的需要创建和更新的target.
变量和函数的扩展处理发生在阶段1的,称作是立即的,immediate;不是immediate的称作是延迟扩展,deferred。
简而言之, target, prerequisite的变量和函数的扩展处理是immediate,recipe中是deferred的。因此,写makefile时应小心recipe中指定的file与target和prerequisite中指定的是否是一致 。
rule各部分的immediate和deferred
|
variable定义的情况
(+=的右项如果是':='设置的简单变量,则是immediate,否则是deferred.) |
小结
1) 变量和函数的扩展在target和prerequisite中是立即的immediate, 在recipe中的是延迟的deferred。
2) 变量定义中 '='的右端是deferred, ':='的右端是immediate的。
文件名中使用的通配符'*'
,
'?'
,
和'[…]'
,
位于target和prerequisite时, 由make负责展开;位于recipe时,由shell负责展开。
注意,变量定义中的通配符实际是在target,prerequisite或recipe中用到时才展开。(todo: deferred的?)
使用.SECONDEXPANSION和$$
(todo)
1)当前路径下target file不存在时,会执行其他路径的搜寻,例如VPATH,vpath指定的路径。
2)如果找到,则路径会暂时记下,作为target file的路径。
3)如果target不需要重建和更新,则这个路径将作为target file的路径。
4)如果target需要重建和更新,则这个路径将被废弃,而使用makefile中指定的target file的路径。
因此,写makefile时应小心target file到底路径是哪个。
如果target的名字不是文件名,这样的target称作Phony Target。尤其如果phony target的prerequisite是空时,rule的时间依赖关系总是认为是满足的,默认不会被make选中执行它的recipe,除非特别指明的调用。
为了防止phony target的名字同文件名的无意冲突, 可以使用.PHONY声明phony target.
例如
|
因为phony target指称的不是实际文件,所以make处理phony target的rule时,会忽略对implicit rule(隐式rule)的搜索。这就是phony target拥有较高性能的原因。
一个例子,phony的方式makefile1优于makefile2
|
.PHONY
声明phony targets。
.SUFFIXES
声明后缀,这些后缀将使用后缀规则suffix rule
.
声明中间文件
.SECONDEXPANSION
这个声明后的所有rule的prerequisite在说有makefile读入后还会执行第二次展开。
.IGNORE
如果一个rule规则的recipe执行发生错误,但想忽略发生的错误,可以用.IGNORE声明。
.SILENT
声明的rule规则在执行recipe时将不输出提示。
.EXPORT_ALL_VARIABLES
输出所有的变量定义到子进程。
.DELETE_ON_ERROR
如果声明,则某个rule的recipe执行错误,则删除它的target文件。
通常一个rule规则仅有一个target目标。但也允许一个rule规则有多个target目标和一个target目标作为多个rule规则的target..
对于后者,只能有一个rule的recipe执行。有多个rule的recipe时,会报错。如果希望多处定义recipe,应当使用'::'
。
静态模式规则用在多个target时,根据target的名字构建prerequisite。
|
targets中的每个target都匹配target-pattern,提取出匹配部分,称作stem,替换prereq-patterns中的标记部分,形成prerequisite.
匹配符使用'%', 在一个pattern中只能使用一次。
例子
|
targets中不符合target-pattern的target可以使用filter函数滤除。
|
一个target出现在多个rule中时,这些rule要么都使用单冒号':'
,要么都使用双冒号'
:
:'
。
双冒号的不同之处在于,每一个rule都将被独立处理,不会影响其他的rule。哪一个rule不满足依赖关系了,哪一个rule的recipe被执行。
如果一个rule的prerequisite和recipe都是空的,并且target不是存在的文件(phony target),那么每次make执行时,这个rule都将被选中,执行更新。依赖这个target的rule自然也都被选中,执行更新。
使用touch创建和更新一个空文件,用于make时间依赖关系中。
一个例子
|
Rule的recipe部分,已tab开头,make只做很少的处理就转交给shell处理。
1) recipe中的comment不是make的comments,而会原封不动转交给shell.
2) recipe中的变量定义(不是变量引用)被认为是shell的内容,而不是make的变量定义, 会直接转交给shell
3) recipe中的条件表达式(ifdef,ifeq,etc)也被视作shell的内容,直接转交给shell处理。
脚本的第一个target就是default goal。但'.'开始的target(不含'/')不能做default goal, 同样, 定义pattern rule的target也不能做default goal。
第一个rule有多个target时,第一个target是default goal。
在target和prerequisite中,如果需要用到'$'符号, 应该使用'$$', 因为变量使用了'$'。
当需要prerequisite发生更新却又想不引起rule的recipe执行,仅当target不存在才执行recipe生成target时,可以使用order-only prerequisite,也就是仅反映存在依赖,而不反映时间依赖时,可以使用order-only prerequisite.
|
一个典型例子:
|
这个例子中,o文件的更新会导致dir更新,因此dir的更新不应要求o文件再更新。
Recipe中变量引用的展开发生在makefile全部读入完毕,也就是deferred。并且仅是那些需要更新target的rule中的变量引用才会被展开。参见2.4。
同makefile其他部分的使用方法一样,实际的'$'
字符需要用'$$'
表示。
make的echo功能是指,make在执行recipe中的语句前会打印出要执行的语句。若不想打印输出,可以在句首前缀'@'
。make传递给shell的语句中会自动去除'@'符号。
'-s'
命令选项可以禁断所有的make echo。
recipe的每一行,make都会调用一个新的子shell执行。如果设置了.ONESHELL,则会在一个shell中执行recipe的所有行。
因此,对于recipe中的命令行,每个子shell的环境是相对独立,不互相影响的。例如,第一个语句的cd命令变更了当前路径,但这不会影响到第二句的当前路径。一个解决的办法是用'&&',将两个命令写到一行中,这已经是shell的语法了。
|
另外,通常make一次仅执行一个recipe,等执行完后,才执行下一个recipe。当然,可以配置make并行执行,具体见文档[1]。
recipe每句话执行完毕(每个子shell)后,make都会检查返回的状态,如果错误,就会放弃recipe的执行,甚至终止make的执行。出于某种需要,如果想忽略这样的错误,继续执行,可以在recipe的语句前加'-'
。.IGNORE也有类似效果。
|
脚本中出现make命令,是为make的递归。递归使用中,makefile脚本中建议使用$(MAKE)而不要直接使用make命令,因为运行环境的不一样,直接使用make跨平台性不好。
父子之间的环境变量传递,可以使用export/unexport
命令。默认的MAKEFLAGS
和MAKEFILES
都会被传递到子make中。MAKELEVEL
是嵌套的深度,从0开始。
如果命令序列会被重复使用,则可以使用define…endef
定义成recipe块,使用的时候简单引用就可以了。这实际上是一种特殊形式的变量定义而已。
|
注意块名实际是变量名,因此不要与已有的变量名冲突。
空recipe的目的是为了避免implicit rule的影响。建议的形式是';'
后跟一个空格
|
隐式规则通常是作用于一类target的规则。当make找不到某个target的显式规则时,就会尝试寻找适用的隐式规则,创建和更新target。
隐式规则的通常形式是根据文件后缀名决定处理的工具,生成的文件。例如.c文件用c编译器,生成.o文件。
同一般规则相比,pattern rule的target中含有通配符'%'。
|