makefile
[toc]
Makefile_learning
教程:http://c.biancheng.net/makefile/
makefile描述的是文件编译的相关规则,由依赖的关系和执行的命令两部分组成
- 结构:
targets: prerequisites
command
- targets:规则的目标,可以是中间文件(*.o),也可以是执行文件(*.elf),还可以是一个标签(clean等)
- prerequisites:依赖文件,要生成targets需要的文件或目标
- command:make需要执行的命令(任意的shell命令)
attention:目标和依赖之间要用冒号分隔开,命令的开始一定要使用
tab键
makefile的工作流程
默认情况下,make执行的是Makefile中的第一规则(Makefile中出现的第一个依赖关系),此规则的第一目标称为“最终目标”,根据依赖关系查找下一条要执行的规则。
这里我们知道,编译时生成的 “.o” 文件。作用是检查某个源文件是不是进行过修改,最终目标文件是不是需要重建。
清除过程文件:
.PHONY: clean
clean:
rm -rf *.0 test
Makefile文件所在目录有文件名为clean的文件,命令行“.PHONY: clean”又没添加的话,执行make clean是无效的,“.PHONY: clean”就是保证即使目录下有文件名为clean的文件,也能正常执行make clean
通配符的使用
通配符可以出现在模式的规则中,也可以出现在命令中
| 通配符 | 使用说明 |
|---|---|
| * | 匹配0个或者是任意个字符 |
| ? | 匹配任意一个字符 |
| [] | 我们可以指定匹配的字符放在 “[]” 中 |
| % | 匹配任意个字符,储存名字在一个列表中,挨个查找 |
- 如果我们的通配符使用在依赖的规则中的话一定要注意这个问题:不能通过引用变量的方式来使用,但就是想要通过引用变量的话,我们要使用一个函数 “wildcard”,这个函数在我们引用变量的时候,会帮我们展开
OBJ = $(wildcard *.c)
test: $(OBJ)
gcc -o $@ $^
变量的定义和使用
- 基本语法
VALUE_LIST = one two three
调用变量的时候用$(VALUE_LIST)或${VALUE_LIST}来替换
- 四种基本赋值方式
- 简单赋值(:=)只对当前语句的变量有效
- 递归赋值(=)所有目标变量相关的其他变量都受影响
- 条件赋值(?=)如果变量未定义,则使用符号中的值(等号后的值)定义变量,如果变量已经赋值,则该赋值语句无效(保持原定义的值)
- 追加赋值(+=)原变量用空格隔开的方式追加一个新值
makefile自动化变量
自动化变量可以理解为由 Makefile 自动产生的变量,自动化变量的取值根据执行的规则来决定,取决于执行规则的目标问价和依赖文件。
| 自动化变量 | 说明 |
|---|---|
| $@ | 表示规则的目标文件名。如果目标是一个文档文件(Linux 中,一般成 .a 文件为文档文件,也称为静态的库文件), 那么它代表这个文档的文件名。在多目标模式规则中,它代表的是触发规则被执行的文件名。 |
| $% | 当目标文件是一个静态库文件时,代表静态库的一个成员名。 |
| $< | 规则的第一个依赖的文件名。如果是一个目标文件使用隐含的规则来重建,则它代表由隐含规则加入的第一个依赖文件。 |
| $? | 所有比目标文件更新的依赖文件列表,空格分隔。如果目标文件是静态库文件,代表的是库文件(.o 文件)。 |
| $^ | 代表的是所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它所代表的只能是所有的库成员(.o 文件)名。 一个文件可重复的出现在目标的依赖中,变量“$^”只记录它的第一次引用的情况。就是说变量“$^”会去掉重复的依赖文件。 |
| $+ | 类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。 |
| $* | 在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分(当文件名中存在目录时, “茎”也包含目录部分)。 |
test:test.o test1.o test2.o
gcc -o $@ $^
test.o:test.c test.h
gcc -o $@ $<
test1.o:test1.c test1.h
gcc -o $@ $<
test2.o:test2.c test2.h
gcc -o $@ $<
GNU make 中在这些变量中加入字符 “D” 或者 “F” 就形成了一系列变种的自动化变量,这些自动化变量可以对文件的名称进行操作。
| 变量名 | 功能 |
|---|---|
| $(@D) | 表示文件的目录部分(不包括斜杠)。如果 “$@” 表示的是 “dir/foo.o” 那么 “$(@D)” 表示的值就是 “dir”。如果 “$@” 不存在斜杠(文件在当前目录下),其值就是 “."。 |
| $(@F) | 表示的是文件除目录外的部分(实际的文件名)。如果 “$@” 表示的是 “dir/foo.o”,那么 “$@F” 表示的值为 “dir”。 |
| $(*D) $(*F) | 分别代表 “茎” 中的目录部分和文件名部分 |
| $(%D) $(%F) | 当以 “archive(member)” 形式静态库为目标时,分别表示库文件成员 “member” 名中的目录部分和文件名部分。(?) |
| $(<D) $(<F) | 表示第一个依赖文件的目录部分和文件名部分。 |
| $(^D) $(^F) | 分别表示所有依赖文件的目录部分和文件部分。 |
| $(+D) $(+F) | 分别表示所有的依赖文件的目录部分和文件部分。 |
| $(?D) $(?F) | 分别表示更新的依赖文件的目录部分和文件名部分。 |
makefile目标文件搜索(VPATH和vpath)
VAPTH和vpath区别:VPATH是环境变量,使用时需要指定文件的路径;vpath是关键字,按照模式搜索,也可以说是选择搜索。搜索时不仅需要加上文件的路径,还需要加上相应的限制条件。
VAPTH的使用 -> 检索路径下的所有文件
VPATH := src #把src的值赋值给变量VPATH
存在多个路径,使用空格或冒号分隔开,搜索顺序为书写顺序
- vpath的使用(选择性搜索)-> 文件数量大时可以提高搜索效率
VPATH 是搜索路径下所有的文件,而 vpath 更像是添加了限制条件,会过滤出一部分再去寻找。
vpath PATTERN DIRECTORIES # PATTERN:要寻找的条件 DIRECTORIES:寻找的路径
vpath PATTERN # 可以使用通配符% e.g. "%.c"搜索所有的.c文件
vpath
- vpath PATTERN DIRECTORIES 为所有符合模式“PATTERN”的文件指定搜索目录“DIRECTORIES” 。多个目录使用空格或者冒号(:)分开。
- vpath PATTERN 清除之前为符合模式“PATTERN”的文件设置的搜索路径。
- vpath 清除所有已被设置的文件搜索路径。
makefile隐含规则
test:test.o
gcc -o test test.o
test.o:test.c
隐含条件只能省略中间目标文件重建的命令和规则,最终目标的命令和规则不能省略
工作流程:make执行过程中找到隐含规则,提供了此目标的基本依赖关系。确定目标的依赖文件和重建目标需要使用的命令行。隐含规则所提供的依赖文件只是一个基本的。当需要增加这个文件的依赖文件的时候要在 Makefile 中使用没有命令行的规则给出。
makefile条件判断
条件语句可以根据一个变量的值来控制make执行或者忽略makefile的特定部分,条件语句可以是两个不同的变量或者是常量和变量之间的比较。
条件语句只能用于控制make实际执行的makefile部分,不能控制规则的shell命令执行的过程
| 关键字 | 功能 |
|---|---|
| ifeq | 判断参数是否相等,相等为 true,不相等为 false。 |
| ifneq | 判断参数是否不相等,不相等为 true,相等为 false。 |
| ifdef | 判断是否有值,有值为 true,没有值为 false。 |
| ifndef | 判断是否有值,没有值为 true,有值为 false。 |
ifeq (VAL1,VAL2)# 或者 ifeq 'VAL1' 'VAL2'
ifdef VARIABLE-NAME
makefile伪目标
伪目标不会创建目标文件,只是想执行这个目标下面的命令。
- 使用伪目标的原因
- 避免makefile中定义的只执行的命令和工作目录下实际文件出现名字冲突
- 提高执行make的效率
- 将一个目标声明为伪目标的方法是将它作为特殊的目标
.PHONY的依赖
.PHONY:clean
clean:
rm -rf *.o test
- make在并行和递归执行的过程中,存在一个变量定义为所有需要make的子目录。
SUBDIRS = foo bar baz
.PHONY:subdirs $(SUBDIRS)
subdirs:$(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@
foo:baz #规定三个子目录的编译顺序,baz比foo先执行,最后执行bar
- 伪目标实现多文件编译
.PHONY:all
all:test1 test2 test3
test1:test1.o
gcc -o $@ $^
test2:test2.o
gcc -o $@ $^
test3:test3.o
gcc -o $@ $^
makefile常用字符串处理函数
函数的语法结构
$(<function> <arguments>) # or ${<function> <arguments>}
其中,function是函数名,arguments是函数的参数,参数之间要用逗号分隔开,而函数名和参数之间用空格分开。调用函数的时候要使用字符$,后面跟小括号或者花括号。
- 模式字符串替换函数
$(patsubst <pattern>,<replacement>,<text>)
查找text中的单词是否符合模式pattern,如果匹配的话,则用replacement替换。返回值为替换后的新字符串。
- 字符串替换函数
$(subst <form>,<to>,<text>)
将字符串中的form替换成to,返回值为替换后的新字符串。
- 去空格函数
$(strip <string>)
去掉字符串开后和结尾的空格,并将字符中间的多个连续的空格合并成为一个空格,返回值为去掉空格的字符串。
- 查找字符串函数
$(findstring <find>,<in>)
查找in中的find,如果我们的目标字符串存在。返回值为目标字符串,如果不存在就返回空值。
- 过滤函数
$(filter <pattern>,<text>)
过滤出text中符合模式pattern的字符串,可以有多个pattern。返回值为过滤后的字符串。
- 反过滤函数
$(filter-out <pattern>,<text>)
- 排序函数
$(sort <list>)
将list中的单词排序(升序)。返回值为排序后的字符串。
sort会去除重复的字符串
- 取单词函数
$(word <n>,<text>)
取出text中的第n个单词。
makefile常用文件名操作函数
下面每个函数的参数字符串都会被当作是一个系列的文件名来看待
- 取目录函数
$(dir <names>)
从文件名序列names中取出目录部分,如果names中没有/,取出的值为./。返回值为目录部分,指的是最后一个反斜杠之前的部分。
- 取文件函数名
$(notdir <names)
从文件名序列names中取出非目录部分。
- 取后缀名函数
$(suffix <names>)
从文件名序列names中取出各个问价的后缀名。返回值为后缀序列,如果没有后缀名,返回空字符串。
- 取前缀函数
$(basename <names>)
获取的是文件的前缀名,包含文件路径的部分。
- 添加后缀名函数
$(addsuffix <suffix>,<names>)
- 添加前缀名函数
$(addprefix <prefix>,<names>)
可以使用这个函数给文件添加路径
- 链接函数
$(join <list1>,<list2>)
把list2中的单词对应地拼接到list1的后面。如果list1的单词比list2多,那么list1中多出来的单词将保持原样;如果list1中的单词比list2的少,那么list2中多出来的单词将保持原样。
OBJ = $(join src car,abc zxc qwe)
all:
@echo $(OBJ)
结果输出为srcabc carzxc qwe
- 获取匹配模式文件函数名
$(wildcard PATTERN)
列出当前目录下的所有符合模式的PATTERN格式的文件名。返回值为空格分隔并且存在当前目录下的所有符合模式PATTERN的文件名。
OBJ = $(*.c *.h)# 这里可以不适用wildcard函数 因为echo属于shell函数,通配符会自动展开,注意这里不是规则,是执行语句
all:
@echo $(OBJ)
makefile中其他常用函数
- foreach
$(foreach <var>,<list>,<text>)
把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式。每一次<text>会返回一个字符串,循环过程中,<text>所返回的每个字符串会以空格分割,最后当整个循环结束的时候,<text>所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。所以<var>最好是一个变量名,<list>可以是一个表达式,而<text>中一般会只用<var>这个参数来一次枚举<list>中的单词。
name := a b c d
files := $(foreach val, $(names), $(val).o)
all:
@echo $(files)
输出为a.o b.o c.o d.o
foreah中的var是局部变量,作用域只在函数内
- if
$(if <condition>,<then-part>) # or $(if <condition>,<then-part>,<else-part>)
- call
$(call <expression>,<parm1>,<parm2>,<parm3>,...)
call函数是唯一一个可以用来创建新的参数化的函数,可以用来写一个非常复杂的表达式,用call来向这个表达式传递参数。当 make 执行这个函数的时候,expression参数中的变量$(1)、$(2)、$(3)等,会被参数parm1,parm2,parm3依次取代。而expression的返回值就是 call 函数的返回值。
- origin
$(origin <variable>)
origin函数告诉你变量从哪里来
下面是origin函数返回值:
“undefined”:如果
<variable>从来没有定义过,函数将返回这个值。“default”:如果
<variable>是一个默认的定义,比如说“CC”这个变量。“environment”:如果
<variable>是一个环境变量并且当Makefile被执行的时候,“-e”参数没有被打开。“file”:如果
<variable>这个变量被定义在Makefile中,将会返回这个值。“command line”:如果
<variable>这个变量是被命令执行的,将会被返回。“override”:如果
<variable>是被override指示符重新定义的。“automatic”:如果
<variable>是一个命令运行中的自动化变量。
makefile命令的编写
命令回显
通常make在执行命令行之前会把要执行的命令行输出到标准输出设备,我们称之为“回显”。如果规则的命令行以字符
@开始,则make在执行的时候不会显示这个将要被执行的命令。我们在执行 make 时添加上一些参数,可以控制命令行是否输出。
当使用 make 的时候加上参数
-n或者是--just-print,执行时只显示所要执行的命令,但不会真正的执行这个命令。只有在这种情况下 make 才会打印出所有的 make 需要执行的命令,其中包括了使用的“@”字符开始的命令。而 make 参数
-s或者是--slient则是禁止所有的执行命令的显示。就好像所有的命令行都使用“@”开始一样。命令执行
当规则中的目标需要被重建的时候,此规则所定义的命令将会被执行,如果是多行的命令,那么每一行命令将是在一个独立的子 shell 进程中被执行。因此,多命令行之间的执行命令时是相互独立的,相互之间不存在往来。
在 Makefile 中书写在同一行中的多个命令(用
;分隔开)属于一个完整的 shell 命令行,书写在独立行的一条命令是一个独立的 shell 命令行。使用反斜杠\来对处于多行的命令进行连接,表示他们是一个完整的shell命令行。并发执行命令
通过 make 命令行选项 “-j” 或者 “–jobs” 来告诉 make 在同一时刻可以允许多条命令同时执行。如果选项 “-j” 之后存在一个整数,其含义是告诉 make 在同一时刻可以允许同时执行的命令行的数目。这个数字被称为
job slots。当 “-j” 选项中没有出现数字的时候,那么同一时间执行的命令数目没有要求。使用默认的job solts,值为1,表示make将串行的执行规则的命令(同一时刻只能由一条命令被执行)。
makefile include文件包含
当make读取到“lnclude”关键字的时候,会暂停读物当前的makefile,而是去读“include”包含的文件,读取结束后再继续读取当前的makefile文件。
include <filenames>
include使用场景
- 将共同使用的变量或者模式规则定义在一个文件中,需要的时候用 “include” 包含这个文件。
- 当根据源文件自动产生依赖文件时,我们可以将自动产生的依赖关系保存在另一个文件中。然后在 Makefile 中包含这个文件。
如果使用 “include” 包含文件的时候,指定的文件不是文件的绝对路径或者是为当前文件下没有这个文件,make 会根据文件名会在以下几个路径中去找,首先我们在执行 make 命令的时候可以加入选项 “-I” 或 “–include-dir” 后面添加上指定的路径,如果文件存在就会被使用,如果文件不存在将会在其他的几个路径中搜索: “usr/gnu/include”、“usr/local/include” 和 “usr/include”。
如果在上面的路径没有找到 “include” 指定的文件,make 将会提示一个文件没有找到的警示提示,但是不会退出,而是继续执行 Makefile 的后续的内容。当完成读取整个 Makefile 后,make 将试图使用规则来创建通过 “include” 指定但不存在的文件。当不能创建的时候,文件将会提示致命错误并退出。
使用时,通常用 “-include” 来代替 “include” 来忽略文件不存在或者是无法创建的错误提示
-include <filename>
这种情况下,只有在不能正确完成终极目标的重建时,才会提示错误并退出。
为了和其它的make程序进行兼容。也可以使用“sinclude”来代替“-include”(GNU所支持的方式)。
makefile的嵌套执行make
不同模块有自己的makefile文件,我们只需要控制其他模块中的makefile就可以实现总体的控制。
我们把最外层的 Makefile 称为是总控 Makefile
subsystem:
cd subdir && $(MAKE)
# or
subsystem:
$(MAKE) -C subdir
变量“CURDIR”,代表make的工作目录。当使用make的选项 -C 的时候,命令会进入指定的目录中,这时该变量会被重新赋值为指定目录。
- export的使用
如果需要变量的传递(到下一层make),可以使用下列语句
export <variable>
不需要使用“$“字符。如果所有变量都需要传递,那么只需要使用”export“即可
Makefile中有两个变量不需使用关键字”export“声明,他们总会传递到下层的Makefile中,分别是 SHELL 和 MAKEFLAGS。其中MAKEFLAGS包含了make的参数信息,这是一个系统级别的环境变量。
make命令中有几个参数选项并不传递,它们是:"-C”、"-f"、"-o"、"-h" 和 “-W”。如果我们不想传递 MAKEFLAGS 变量的值,在 Makefile 中可以这样来写:
subsystem:
cd subdir && $(MAKE) MAKEFLAGS=
- 嵌套执行make
要提供正确的依赖关系
当 make 发觉它正在递归调用另一个 make 时,他会启 用–print-directory(-w) 选项,这会使得 make 输出 Entering directory(进入目录) 和 Leaving directory(离开目录) 的信息。当 –directory(-C) 选项被使用时,也会启用这个选项。我们还可以看到每一行中,MAKELEVEL 这个 make 变量的值加上方括号之后被一起输出。
makefile变量的高级用法
- 替换引用
字符串后缀名的替换(同patsubst函数)
foo:=a.c b.c d.c
obj:=$(foo:.c=.o) # 变量名的后面要使用冒号和参数选项分开,表达式中间不能使用空格
# 更通用的写法
obj:=$(foo:%.c:%.o) # e.g. $(foo:a%c:x%y)
all:
@echo $(obj)
- 嵌套引用
在一个变量的赋值中引用其他的变量(避免使用-别人可能看不懂)
first_pass=hello
bar=first
var:=$(bar)_pass
all:
@echo $(var) # 输出结果是hello
makefile控制函数error和warning
当make执行过程中检测到某些错误时为用户提供消息,并可以控制make执行过程是否继续
$(error TEXT...)
函数说明如下:
- 函数功能:产生致命错误,并提示 “TEXT…” 信息给用户,并退出 make 的执行。需要说明的是:“error” 函数是在函数展开时(函数被调用时)才提示信息并结束 make 进程。因此如果函数出现在命令中或者一个递归的变量定义时,读取 Makefile 时不会出现错误。而只有包含 “error” 函数引用的命令被执行,或者定义中引用此函数的递归变量被展开时,才会提示知名信息 “TEXT…” 同时退出 make。
- 返回值:空
- 函数说明:“error” 函数一般不出现在直接展开式的变量定义中,否则在 make 读取 Makefile 时将会提示致命错误。
$(warning TEXT...)
函数说明如下:
- 函数功能:函数 “warning” 类似于函数 “error” ,区别在于它不会导致致命错误(make不退出),而只是提示 “TEXT…",make 的执行过程继续。
- 返回值:空
- 函数说明:用法和 “error” 类似,展开过程相同。