Make file学习总结
一. make file有什么?
-
Make file主要包含了五个东西,显示规则,隐晦规则,变量定义,文件指示和注释说明
1.1. 显示规则:说明了如何生成一个或多个目标文件
1.2. 隐晦规则:利用make 的自动推导功能,可以使我们可以粗糙简略地写make
1.3. 变量定义:类似C的宏
1.4. 文件指示:在一个makefile中引用另一个makefile,类似C中的include;
根据某种情况指定makefile中的有效部分,类似C的预编译
定义一个多行的命令
1.5. 注释说明:和shell script中一样,使用#,不支持多行注释
注意:在makefile中,命令行,必须要用tab键开始
-
Make file的引用
在makefile中,使用include<file name>来引用其他的makefile,类似C的include,filename可以是包含路径和通配符。
Make命令开始时,会找寻include中的makefile,并把文件安置在此位置。如果文件没有路径,就先会在当前目录寻找,没有找到的话,会提示警告信息。
在include前面叫一个“-”号,可以强制不显示警告信息
注意:include前面不可以使用tab键,可以使用空格。
-
Makefile的工作方式
3.1. 读入makefile
3.2. 读入include中的makefile
3.3. 初始化文件的变量
3.4. 推导隐晦规则,并分析所有规则
3.5. 为所有的目标文件创建依赖关系
3.6. 根据依赖关系,确定那些文件要重新生成
3.7. 执行生成命令
1-5步为第一阶段,6-7步为第二阶段。在第一阶段中,如果定义的变量被使用,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make会使用拖延战术,例如变量出现在依赖关系的规则中,那么仅当这条依赖决定要使用,变量才会在其内部展开。
-
Make file的语法
-
规则的语法
主要包含有三块内容,目标(Targets),依赖和命令;目标和依赖(prerequisites)用冒号隔开,命令(Command)可以有两种方式,第一种:换一行,并且必须在命令前面有一个tab键(测试必须8个空格的长度),第二种方式:在依赖后面加上分号,可以直接连接命令
語法1:
Targets : prerequisites
語法2:
Targets : prerequisites ; command#command前面用“;”
-
通配符的使用
如果想定义一系列类似的文件,我们很自然地就能想到使用通配符,在makefile中支持三种通配符。
-
1通配符的类型
Make 支持三种通配符:* ?[...] 和shell是相同的
2.2通配符的使用环境
通配符可以出现在以下两种场合:
-
规则的目标、依赖中,make在读取makefile时会将通配符展开
-
规则的命令中,由shell执行此命令时将通配符进行展开
-
2.3通配符的使用
通配符代替了一系列文件,如“*.c”表示所有后缀名为“.c”的文件,另外我们要注意的是,如果中有真实的“*”字符,我们就需要使用转义字符“\”,如“\*”表示真实的“*”字符,而不是任意长度的字符串。
*:表示任意多个字符
?:表示任意一个字符
[ ] :表示符合中括号中的字符
实例:
#当前工作目录下存在1.c ; 2.c ; test.c ; 1.h四个文件
Makefile文件的内容如下:
运行结果:
注意:objects= *.o ,表示通配符可以在变量中使用,但objects的值就是*.o,如果需要通配符在变量中展开,正确的写法是:objects= $( wildcard *.o ),下面学习wildcard方法时,会再次说道
-
文件搜索
在一些大的工程中,有大量的源文件,通常的做法是把这些文件分类,并存放在不同的目录中,所以当make需要去寻找文件的依赖关系时,可以在文件前加上路径,但最好的方法时把一个路径告诉make,让make自动去寻找。
VPATH就是完成这个功能的,如果没有指明这个变量,make只会在当前目录中去寻找依赖文件和目标文件。但如果定义了这个变量,那么make就会在当前目录找不到的情况下,到所指定的目录中去寻找文件。
3.1. 特殊变量“VPATH”可以使make在当前目录找不到时,到其指定的地址去寻找
3.2. 多个地址
VPATH = src : ../headers #多个地址用:号分隔
上面的定义指明了两个目录,“src”和“../headers”,make会按照这个顺序进行搜索
3.3. vpath关键字设置搜索路径(小写的,是makefile的关键字)
另一种设置文件搜索路径的方法是使用vpath关键字,和VPATH变量很类似,但它可以指定不同的文件在不同的目录中,比较灵活。。
使用方法有三种:
vpath <pattern> <directories>#在指定的directories下,搜索符合pattern模式的文件。
vpath <pattern> #清除符合pattern的搜索目录
vpath #清除所有的目录
vpath使用方法中的<pattern>可以使用匹配符%,%.h表示以.h结尾的文件,它指定了要搜索的文件集,<directories>则指定了<pattern>的文件集的搜索的目录,如:
vpath %.h ../headers(为了显示清楚,我中间多打了几个空格)
该语句表示,要求make在../headers目录中搜索所有以“.h”结尾的文件(如果这个文件在当前目录没有找到的话)
我们可以连续地使用vpath语句,以指定不同搜索路径。如果连续的vpath语句中出现了相同的<pattern>,或是被重复了的<patter>,那么make会按照vpath语句的先后顺序来执行搜索。
-
伪目标##特性:总是被执行
4.1. 伪目标不是一个文件,而是一个lable,只有通过显示的指明才能让其生效就是执行下面的command
4.3. .PHONY显示的指明目标是一个伪目标,.PHONY是关键字
例如:
.PHONY clean
clean:
rm *.o
.PHONY为关键字,clean是伪目标,只要有这个声明,要运行clean这个目标,只能显示的指明才能生效,即使用makeclean命令来执行(执行make命令,是不会执行rm.o这个命令的)
-
伪目标可以作为依赖
-
伪目标一般没有依赖条件,但是我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”(如果没有指定的话,就是第一个目标),只要将其放在第一个。
.PHONY : cleanall cleanobjcleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
我们可以使用makecleanall来清除以.o.diff结尾的文件和program文件,也可以用makecleanobj来清除.o结尾的文件或者用makecleandif来清除.diff结尾的文件。
cleanall : cleanobj cleandiff
就把cleanobj和cleandif这两个伪目标作为cleanall伪目标的依赖条件。
-
多目标
bigoutput littleoutput:text.g
generate text.g -$( subst output, ,$@) > $@#subst 截取字符串
#$@表示目标的集合,就相当于一个数组
#$@可以依次取出数组中的数据,并执行命令
上语句相当于:
bigoutput : text.g
generate text.g -big >bigoutput
littleoutput : texts.g
generate text.g -little >littleoutput
其中,-$(subst output, ,$@)中的“$”表示执行一个Makefile的函数,函数名为subset,后面的为参数。关于函数,后面会讲述。这个函数就是截取字符串的意思。
“$@”表示目标的集合,也就是bigoutput和littleoutput就像一个数组。“$@”依次取出目标,并执行命令。
-
静态模式
静态模式可以更加容易地定义多目标的规则,可以让规则变得更加的有弹性和灵活
语法:
<targets..>:<target-pattern>:<prereq-patterns…>
<commands>
….
targets:定义了一系列的目标文件,可以有通配符,是目标的一个集合
target-pattern:指明了targets的模式,也就是目标集模式
prereq-pattern:是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标定义
例如:
objects = foo.o bar.o
all: $(objects)
$(objects):%.o:%.c
gcc –c $< -o $@
上面的例子中,指明了目标从$(objects)中获取,%.o表明要所有以.o结尾的目标,也就是foo.obar.o,而依赖模式“%.c”则取模式“%.o”的“%”部分,也就是foo和bar,并为其加上“.c”的后缀,于是我们的依赖目标就是foo.c和bar.c。
-
自动变量
$@ : 工作目标的文件名
$< : 依赖目标中的第一个目标的文件名
目录中有两个文件,foo.c和bar.c
例:
foo.o bar.o : foo.c bar.c
gcc –c $< -o $@
$@:表示一个文件集,里面有两个元素foo.o和bar.o(从文件中依次取出来使用)
$<:依赖目标中的第一个目标名称,即是foo.c。如果依赖目标是以模式(%.c)来定义的,(即foo.obar.o :%.c)则表示符合模式的一系列的文件集,里面有两个文件foo.c和bar.c(也是依次取出来的)
$% : 档案文件成员结构中的文件名元素
例如:
foo.a(bar.o) :foo.cbar.o
commands
foo.a(bar.o)就是一个档案文件成员结构(函数库文件),$%就代表是bar.o,就是工作目标中小括号里面的文件;$@代表是foo.a;
当工作目标不是一个档案文件成员时,$%则为空
函数库文件,unix下是.a结尾的文件
$^ : 所有依赖的文件名,以空格隔开,重复的已删除
例:
有两个目录,一个目录中有文件:foo.c和bar.c;另一个目录有文件:foo.c
foo.o : %.c
$^:表示所有依赖的文件名,即foo.cbar.c(两个文件名中间有一个空格),两个目录中依赖的文件有重复名字的(foo.c),但只能保留一个文件名
$+ : 如同$^,但包含重复的文件名
$* :工作目标的主文件名(一个文件名称是由两部分组成:主文件名和扩展名)
如果目标名为foo.c,$*就是foo,.c为后缀名
$? :时间戳在工作目标之后的所有必要条件(即依赖文件),并以空格隔开
一个大的项目中,第一次运行make时,会用掉很长的时间,这是因为所有的文件都需要进行编译,而以后修改一些文件后再运行make时,就会发现速度很快。这就是因为make会自动的去检查依赖和目标的时间戳,如果依赖的时间戳在目标之后,则表明依赖的文件被修改过,那么make就会再一次执行该command命令,以生成新的目标文件。否则就会直接跳过该命令。这种机制会在以后的编译中节省很多的时间。
例:
bar.o : foo.c bar.c
command
如果我们应经修改了foo.c文件,在执行make时,make会根据时间戳判断出bar.o需要重新进行编译,即执行该command。
$?就是代表时间戳在目标之后的依赖文件名,多个文件时,会以空格隔开。
-
Make file的函数
Makefile的函数使用,和取变量一样,使用“$”开始,括号中是函数名和参数,用空格隔开,参数用“,”隔开。
字符串处理函数
3.1. subst 字符串替换函数
old:=1
new:=a
foo:=123123
bar:=$( subst $(old),$(new),$(foo))
#输出a23a23
#第一个参数$(old)为被替换字串,第二个参数$(new)为替换字串,
#第三个参数$(foo)为替换操作作用的字串即内容
结果:
3.2. patsubst 模式替换函数
$(patsubst pattern, replacement,text ) :::::::::::: $( patsubst %.c, %.o, $(XXXX) )
功能:搜索“TEXT”以空格、TAB、回车、换行隔开的单词,将符合“pattern”模式内容替换成“replacement”,参数“pattern”中可以使用匹配符“%”。
将所有以.c为结尾的字符换成以.o结尾
结果:
后缀时.c的都换成.o
3.3. strip去空格函数
$(strip string)
功能:去掉string中开头和结尾的空字符
Result:
3.4 findstring 查找字符串函数
$(findstring<find>,<text>)::$(findstring a, a b c) ==>> return a
:: $(findstring a, b c) ===>>return空
功能:在text中查找find字符串,如找到returnfind,否则returnnull
在object内容中查找foo.o,如果查到,返回foo.o,没有查到则返回空
结果:
如果没有这个文件,则返回空。
3.5 filter 过滤函数
$(filter <pattern>,<text>)
功能:以pattern模式过滤text字符串中的单词,保留符合pattern的单词
sources:=a.c b.c c.o d.z
$(filter %.c %.z, $(sources))
===>>return a.c b.c d.z可以看出c.o被过滤掉了
过滤object内容,保留以.c结尾的字符
Result:
输出符合.c的文件名
3.6 filter-out 反过滤函数
$(filter-out <pattern,...>,<text>)
功能:以pattern模式过滤text字符串中的单词,去除符合pattern的单词
Sources:=a.c b.c c.o d.z
$(filter-out %.c %.z, $(sources))
===>>return c.o 可以看出c.o被保留了下来
和filter作用相反,返回不是以.c结尾的字符
Result:
3.7 sort 排序函数
$(sort <list>)
功能:给list中的单词排序(升序),去掉重复的单词
Return 没有重复的、空格分隔的单词
给object的内容进行排序,如果有重复的字符,则只显示一个
Result:
3.8 word 取单词函数
$(word number,text)
功能:取出text中第number个单词,number不能为0,会报错,超过最大值,返回空字符串
$(word 2,”abc”) return : b
返回object内容的第三个字符串
Result:
3.9 wordlist 取字符串函数
$(wordlist start,end,text)
功能:从text中取出从start到end的单词,start,end都是从1开始的
$(worlist 1,3,”abcd”) return:abc
Result:
3.10 words 计算单词数量
$(words , text )
功能:统计text中字符数量
返回object中字符串的个数
Result:
5
返回个数。
3.11 firstword 取首单词的函数
$(firstsword ,text)
功能:取text中的第一个字符
返回object里第一个字符串
Result:
文件处理函数
3.12 dir 取目录函数
$(dir names....)
功能:从names中取出各个文件目录。Return空格隔开的目录。如果没有目录就认为此文件为当前目录“./”下的文件
$(dir src/foo.c hacks.c)===>>return : src/ ./
返回所有文件的路径,如果在本目录,则返回./
Result:
3.13 notdir 取文件名函数
$(notdir names..)
功能:从names中取出各个文件名称。Return空格隔开的文件名
$(notdir src/foo.c hack.c)==>>return : foo.c hack.c
返回文件名,前面的路径全部抛弃
Result;
3.14 suffix 取后缀名函数
$(suffix names)
功能:从names中取出各个文件的后缀名
$(suffix src/foo.c hack.c)==>>return : .c .c
返回所有文件的后缀名
Result:
3.15 basename 取前缀函数
$(basename names...)
功能:从names中取出各个文件的前缀名
$(basename src/foo.c hack.c)====>>return : src/foo hack
返回所有文件名的前缀,和suffix相反,抛弃掉文件后缀名
Result:
3.16 addsuffix 加后缀函数
$(addsuffix suffix,names..)
功能:给names中的各个文件添加后缀suffix
$(addsuffix .c,foo.c hack)====>>return : hack.c
给文件名加上后缀名
Result:
3.17 addprefix 加前缀函数
$(addprefix prefix,names...)
功能:给names中的各个文件添加前缀prefix
$(addprefix src/,foo.c hack)====>>return : src/foo.c src/hack
给文件名加上前缀,和addsuffix相反
Result:
3.18 Join 单词连接函数
$(join name1,name2)
功能:使name1中的第一个单词和name2中的第一单词合并成一个新单词,后面依次类推,返回被空格分隔的序列
$(join src/foo hack,.c .c)===>>return : src/foo.c hack.c
注意:当name1和name2数目不相同时,多余的部分将做为返回值的一部分。
所以这个函数可重建被dir和notdir分解的列表
将object的第一个字符串和name的第一个字符串向Join,第二个和第二个相加,依次类推;当字符串个数不同时,直接显示剩下的字符串。
Result:
-
wildcard获得匹配模式函数
-
$(wildcard pattern…)
功能:列出当前目录下所有符合pattern模式的文件名。还可以判断文件是否存在
例如:sources:=$(wildcard *.c,*.h)
Isexits :=$(wildcard a.c) ##判读a,c是否存在,存在返回文件名,不存在返##回空字符
判断hello.c文件是否存在,存在返回文件名,否则返回空
Result:
流程控制函数
3.20 if
$(ifcondition,then-part[,else-part])
功能:当condition为true,返回then-part;condition为false,返回else-part,
当没有第三个参数时,condition为false,返回空
上图中,判断if后面的条件真假($(object));为真时,获得第一个值,为假时,获得第二个值。因为object没有赋值,所以条件为假。
Result:
-
foreach
-
$(foreach var,list,text)
功能:类似shell中的for,执行时把list中使用空格分隔的单词依次取出复制给var,然后执行text表达式,重复直到list最后一位,为空时结束。
先展开var和list的变量,text中的变量直到被引用或执行时才会展开。因此text中对var的引用,var在每次被展开时会得到不同的值
Return :空格分隔的多次表达式text的计算结果
例:letters:=$(foreach letter,a b c d,$(letter))
Show-words :
Litters has $(words $(letters))words : ‘$(litters)’
#### return : litters has 4words: ‘a b c d’
上图中,先将object的一个值赋值给变量var,然后在执行最后的$(var).l操作,所以第一个值被打印出来是a.c.l;后面的执行同样的操作。。
Result:
-
shell
-
$(shell command)
功能:需要一个shell命令作为他的参数,而返回的结果就是command在shell中的执行结果。Make仅对他的返回值的处理。输出中一系列的换行符号都会被换成单一的空格符号,接在后面的换行符号都会被删除,标准错误以及任何程序的结束状态都不会被返回。
可以直接执行shell后面的command;直接返回执行结果。
Result:
3.23 origin
$(origin var)
功能:查询一个变量的出处,返回var的定义方式
类型:Undefined;default; environment; environment override; file; command line;
override; automatic .
返回变量的出处。比如说例子的file类型
Result:
3.24 call 函数
$(call var,param,param….)
功能:call函数是唯一的一个可以创建定制参数化的引用函数。可以将一个变量定义为一个复杂的表达式,用call函数根据不同的参数来对他展开获得不同的结果。
可以理解为调用方法,后面的参数用逗号隔开,$(shelldate -u)是获得UTC时间。
Result:
3.25 value 函数
$(value var)
功能:不对var进行任何展开操作,直接返回变量var代表的值,var是一个变量名,一般不包含$
返回:变量var所定义文本值
直接返回一个变量的值
Result: