make 要点简记
1.隐式推导
make可以自动推导文件及其文件依赖关系后面的命令,所以我们没有必要在每一个.o文件后面都写上类似的命令,因为make 会自动识别并且自动推导命令.
objects = main.o
main.o:main.cc
main:$(objects)
c++ $(objects) -o main
# 伪目标,防止目录下有同名为clean的文件造成make clean执行失败
.PHONY:clean
clean:
-rm $(objects) main
2.在规则中使用通配符
objects = $(wildcard *.o)
表示将object变量定义为所有.o文件组成的集合。在定义变量的时候,不能使用通配符,否则上述变量objects的值就为.o,除非使用wildcard*函数.
3.引用其他的makefile文件
可以通过include 命令引用其他的makefile文件,如下所示:
include foo.make *.mk
上面表示引用当前目录下的foo.make文件以及所有后缀为mk的文件。
4.make 的工作方式
1. 读入所有的makefile
2. 读入被include 的其他makefile
3. 初始化文件中的变量
4. 推导隐晦规则,分析所有规则
5. 为所有的目标文件创建依赖关系链
6. 根据依赖关系,确定那些目标需要重新生成
7. 执行生成命令
5.文件搜寻
为了指明更多的文件搜索路径,可以定义vpath变量。vpath的定义方式有下面3种:
- vpath 为符合模式pattern的文件指定搜索路径为directories
- vpath 清除符合模式为pattern的文件搜索路径
- vpath 清除所有已经设置的文件搜索路径
vpath %.h ../header #表示要求make在"../headers"目录下搜索所有以".h"结尾的文件
6.静态模式
静态模式可以更加容易地定义多目标的规则,可以让我们的规则更有弹性。
语法:
<targets ...>:<target-pattern>:<prereq-patterns>
<commands>
在上面的命令中:
- targets定义了一系列的目标文件,是目标的一个集合,可以有通配符。
- target-pattern指明了target的模式,也就是目标集模式。
- prereq-patterns是目标的依赖模式,它是对target-pattern形成的模式再进行一次依赖目标的定义。
例子:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o:%.c
$(CC) -c $(CFLAGS) $< -o $@
上面的代码和下述代码等价:
foo.o: foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o: bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
可以使用filter函数去除指定模式的文件,如下所示:
files = foo.elc bar.o
$(filter %.o,$(files)):%.o:%.c
$(CC) -c $(CFLAGS) $< -o $@
7.嵌套执行make
在一些大型的工程中,我们会把不同的模块或者不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录下的makefile文件,这有利于维护我们的makefile.
例如我们有一个子目录叫subdir, 这个目录下有一个makefile文件,来指明了这个目录下的文件的编译规则,那么我们总控的makefile文件可以这么写。
subsystem:
cd subdir $$(MAKE)
其等价于:
subsystem:
$(MAKE) -C subdir
如果想要传递变量到下级makefile,可以使用这样的声明:
export <variable ...>
如果不想让某些变量传递到下级makefile中,可以这样声明:
unexport <variable ...>
8.变量中的变量
在定义变量的值时,可以使用其它变量来构造变量的值。有下述两种方式:
1. 采用=定义变量
使用=定义变量可以使用未定义的值,也可以是后面定义的值,如下所示:
foo = $(bar)
$bar=huh?
上面例子中$(foo)最终的值为huh?
2.采用:=定义变量
使用:=定义变量,必须使用当前已经定义好的变量,不能使用当前未定义的变量。
9.追加变量值
我们可以使用"+="操作符给变量追加值。例如:
objects: main.o foo.o bar.o utils.o
objects+=another.o
这样,上面例子中的$(objects)最终的值为main.o foo.o bar.o utils.o another.o
10.使用条件判断
用两个例子来介绍这一部分的内容:
例1:
libs_for_gcc = -lgnu
normal_libs =
foo:$(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
与ifeq相对应的有ifneq,即如果不等于
例2:
bar =
foo = $(bar)
ifdef foo
frobozz=yes
els
frobozz=no
endif
类似于C++中宏定义中的#ifdef
11.函数
1.字符串处理函数
1.subst
$(subst <from>,<to>,<text>)
名称:字符串替换函数-subst函数
功能:将字符串<test>中的<from>字符串替换为<to>
返回:函数返回被替换过后的字符串
2.patsubst
$(patsubst <pattern>,<replacement>,<text>)
名称:模式字符串替换函数-patsubst
功能:查找<test>中的单词是否符合模式pattern,如果匹配,就以<replacement>进行替换,这里的<pattern>可以包括通配符"%"表示任意长度的字符串。
例子:
patsubst(%.c,%.o,x.c.c bar.c)
将字符串"x.c.c bar.c"中符合模式[%.c]的单词替换为[%.o],故返回的结果为x.c.o bar.o
3.strip
$(strip <string>)
名称:去空格函数
功能:去掉<string>字符串中开头和结尾的空字符串
返回:返回去掉空格的字符串值
4.findstring
$(findstring ,)
名称:查找字符串函数-findstring
功能:在字符串中查找字符串
返回:如果找到,返回,否则返回空字符串
5.filter
$(filter <pattern...>,<text>)
名称:过滤函数
功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern> 的单词。可以有多个模式
例子:
sources = foo.c bar.c baz.s ugh.h
foo:$(source)
cc $(filter %.c %.s,$(sources)) -o foo
其中$(filter %.c %.s,$(sources))返回的值为"foo.c bar.c baz.s"
6.filter-out
$(filter-out <pattern ...>,<text>)
名称:反过滤函数-filter-out
功能:以<pattern>模式过滤<text>字符串中的单词,去掉符合模式<pattern>的单词,可以有多个模式
返回:返回不符合模式<pattern>的字符串。
例子:
objects = main1.o main2.o foo.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects))
上述经过filter-out的返回值为bar.o以及foo.o
7.sort
$(sort <list>)
名称:排序函数-sort
功能:给字符串<list>中的单词排序
返回:返回排序后的字符串
示例:$(sort foo bar lose)返回"bar foo lose"
备注:sort函数会去除list之中重复的单词
8.word
$(word <n>,<text>)
名称:去单词函数-word
功能:取出字符串text中的第<n>个单词(从1开始)
返回:返回字符串<text> 中的第<n>个单词,若<n>比 <text>中的单词数要大,那么返回空字符串
示例:$(word 2,foo bar baz) 返回值为bar
2.文件名操作函数
1.dir
$(dir <names ...>)
名称:取目录函数 --dir
功能:从文件名序列<names>中取出目录部分,目录部分指的是最后一个反斜杠 "/"之前的部分,如果没有反斜杠,则返回“./”
返回:返回文件名序列<names>中的目录部分
示例:$(dir src/foo.c hacks)返回值为 "src/ ./"
2.notdir
$(notdir <names...>)
名称:取文件函数
功能:从文件名序列<names>中取出非目录的部分。菲目录部分是最后一个/之后的部分
返回:返回文件名序列<names>中的非目录部分
示例:$(notdir src/foo.c hacks)返回值为"foo.c hacks"
3.suffix
$(suffix <names...>)
名称:取后缀函数--suffix
功能:从文件名序列<names> 中取出各个文件名的后缀
返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字符串。
示例:$(suffix src/foo.c src-1.0/bar.c hacks) 返回值为".c .c"
4.basename
名称:取前缀函数--basename
功能:从文件名序列中取出各个文件名的前缀部分
返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字符串。
示例:$(basename src/foo.c src-1.0/bar.c hacks)的返回值为"src/foo src-1.0/bar hacks"
5.addsufix
$(addsuffix <suffix>,<names...>)
名称:加后缀函数-addsuffix
功能:把后缀<suffix>加到 <names>中的每个单词后面
返回:返回加过后缀的文件名序列
示例:$(addsuffix .c,foo bar)返回值为"foo.c bar.c"
6.addprefix
$(addprefix <suffix>,<names...>)
名称:加后缀函数--addsuffix
功能:把后缀<suffix>加到<names>中的每个单词后面。
返回:返回加过后缀的文件名序列
示例:$(addprefix .c,foo bar)返回值为"foo.c bar.c"
7.join
$(join <list1>,<list2>)
名称:连接函数--join
功能:把<list2>中的单词相应地添加到<list1>的单词后面。如果<list1>中的单词个数要比<list2>多,那么<list1>中多出来的单词将保持原样。如果<list2>中的单词数量要比<list1>多,那么<list2>中多出来的单词要被复制到<list1>中。
返回:返回连接过后的字符串
示例:$(join aaa bbb,111 222 333) 返回值为"aaa111,bbb222,3333"
3.foreach函数
foreach函数用于循环。语法如下:
$(foreach <var>,<list>,<text>)
这个函数的意思是,将<list>中的单词逐一取出放到参数<var>所指定的变量之中,然后再执行<text>所包含的表达式。每次<text>会返回一个字符串,循环过程中,<text>返回的每个字符会以空格分隔,当循环结束的时候,<text> 返回的每个字符串所组成的整个字符串会是foreach函数的返回值。
例子:
names:=a b c d
files := $(foreach n,$(names),$(n).o)
执行完毕后,变量$(files)的值为"a.o b.o c.o d.o"
4.if函数
1.$(if <condition>, <then-part>)
2.$(if <condition>,<then-part>,<else-part>)
if函数可以包含else或者不包含。即if函数的参数可以是两个,也可以是3个。<condition>参数是if的表达式,如果器返回为非空字符串,那么这个表达式就为真,于是<then-part>会被计算,否则<else-part>会被计算。
如果<condition>为真,那么<then-part>会为整个函数的返回值,否则<else-part>会作为函数的返回值。如果<else-part>没有定义,那么函数会返回空字符串。
5.shell函数
shell函数将执行shell命令的返回值作为函数返回值。
例子:
contents:=$(shell cat foo)
files:=$(shell echo *.c)
12.构建多个目标
通过构建伪目标all的方式来同时指定多个目标为终极目标。
例子:
.PHONY:all
all:exec1 exec2
这个makefile的终极目标由exec1和exec2构成
13.隐含规则
foo:foo.o bar.o
cc- o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
我们注意到,上述makefile没有写下如何生成foo.o和bar.o这两个目标的规则和命令,因为make的隐含规则功能为我们自动推导这两个目标的依赖目标。
隐含规则一览
1.C程序
<n>.o的依赖目标会自动推导为<n>.c,并且其生成命令为 $(CC) -c $(CPPFLAGS) $(CFLAGS)
2.C++程序
<n>.o的依赖目标会自动推导为<n>.c或者<n>.cc,并且其生成命令为 $(CC) -c $(CPPFLAGS) $(CXXFLAGS)
因此建议使用.cc而不是.c或者.cpp作为C++程序的后缀。
链接object文件的隐含规则
对于下述makefile
x:y.o z.o
如果y.c 与 z.c文件都存在,隐含规则会执行下述命令:
cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -f z.o
14.隐含规则使用的变量
命令
- AR 函数库打包的程序,默认为ar
- AS 汇编语言编译程序,默认为as
- CC C语言编译程序,默认为"cc"
- CXX C++语言编译程序,默认为"g++"
- RM 删除文件命令,默认为rm
参数
- ARFLAGS 函数库打包程序AR命令的参数,默认为rv
- ASFLAGS 汇编语言编译器参数
- CFLAGS C语言编译器参数
- CXXFLAGS C++语言编译器参数
- CPPFLAGS C预处理器参数
- LDFLAGS 链接器参数
15.自定义模式规则
举例说明,下述makefile文件将所有的目录下所有的.c文件都编译成为.o文件.
%.o:%.c
$(CC) -c $(CFLAGS) $< -o $@
上述makefile自定义了一个针对.o文件的隐含规则
16.自动化变量
- $@ 表示规则中的目标文件集,在模式规则中,如果有多个目标,那么 $@ 就是匹配于目标中模式定义的集合。
- $% 仅当目标是函数库文件时,表示规则中的目标成员名。例如一个目标是
foo.a(bar.o)
,那么$%表示bar.o, $@ 表示foo.a。 - $< 依赖目标中的第一个目标文字。如果依赖目标是以模式%定义的,那么$<将是符合模式的一系列文件集,请注意是一个一个取出来的。
- $? 所有比目标新的依赖目标的几个,以空格分隔。
- $^ 所有依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的。那么这个变量会去除重复的依赖目标,只保留一份。
- $+ 是所有依赖目标的集合,但是不去除重复的依赖目标。
17.使用make更新函数库文件
可以使用下述格式来指定函数库文件及其组成。
archive(member)
例子:
foolib(hack.o foo.o):hack.o foo.o
ar cr foolib hack.o foo.o
上述命令在文件foo.o和hack.o文件的基础之上构建了foolib.a文件。
在进行函数库打包的过程中,若多个ar命令同时运行在同一个函数库打包文件时,可能损坏这个函数库文件,所以对于这种情况要谨慎使用make -j 命令。
函数库在生成过程中可能也会触发.o文件的隐含规则