讲真,在公司做代码开发,对于一个大项目,能有机会让你去写第一版Makefile的并不多见,需要添加新模块的时候,也只需要我们在其中修修补补的似乎就行了,以至于对Makefile的内容,一直都觉的梳理的不够系统。最近得了点小空,于是乎就找来了Makefile的中文手册前前后后的读了两遍,感觉对Makefile的理解变的清晰了许多,按照我梳理的大概流程,就形成了本Makefile系列的总结,欢迎诸位勘误。
一. 变量
变量定义
(1) 定义变量时,推荐的做法是,对于内部定义的一般变量使用小写方式,而对于一些参数列表采用全大写方式;
(2) 变量在引用时,对于多字符名的变量,必须使用括号进行标记,即 “ {}”, 变量在展开时时使用严格的文本替换过程,与C语言中的宏定义一样。对于Makefile中的各部分变量的引用建议如下:
i). 对于make变量的引用使用 make 格式 “$(VAR)”,包括Makefile中定义的或make的环境变量;
ii). 出现在规则命令行中的shell变量,引用时使用shell的格式 “$tmp”;
iii). 对于出现在命令行中的make变量,引用时同样使用make格式 “$(CMDVAR)”;
(3) 递归展开时变量
i). 定义方式使用 “=” 或指示符 “define”;
ii). 变量在定义时,对其它变量的引用不会被立即展开,而是变量在应用它的地方替换展开的同时,它所引用的变量才会被一同替换展开,所以递归展开变量在定义时可以引用之前还没有定义的变量;
iii). 递归变量不支持递归定义,否则会使变量陷入到无限的变量展开过程中,最终导致make执行失败;
iv). 如果变量定义中使用了函数,纳闷包含在变量定义中的函数总会在变量被引用的地方被调用,使make的执行效率降低;
(4) 直接展开式变量
i). 定义方式”:=”;
ii). 变量在定义时对其它变量和函数的引用会被立即展开,因此不能实现对变量先时候后定义,否则引用的其它未定义的变量,得到的会是空值;
iii). 在复杂的Makefile中推荐使用直接展开式变量,可以使一个比较复杂的Makefile在一定程度上具有可预测性;
(5) 变量定义中的空格
i). 一般变量值中的前导空格字符在变量引用和函数引用时被丢弃,而尾空格是不被忽略的
ii). 明确定义空格的方式,需要使用直接展开式定义变量的方式,如下nullstring:= space:=$(nullstring) # end of the line
space 就表示一个空格,将变量定义和注释书写在同一行并使用一个(若干个)空格分开,在变量引用”$(nullstring)”和注释标识符”#”之间存在一个空格,通过这种方式就明确定义了一个空格,此时空格以”#”来结束。因此,当定义不包含空格的变量时,就不能采用这种方式,推荐将注释书写放在独立的一行或多行,防止出现定义的变量结尾出现不期望的空格字符;
(6) 条件赋值 “?=”
只有此变量在之前没有赋值 (或没有定义) 的情况下才会对这个变量赋值FOO ?= bar 即如果FOO变量之前没有赋值(或定义),那么给它赋值”bar”;
(7) define定义变量
i). 定义格式如下,以指示符”define”开始,以”endef”结束。其属于递归展开式变量,定义时也可以使用override指示符进行声明:[override] define var_name ...... ...... endef
ii). define用来定义一个包含多行字符串的变量,利用这个特点实现了一个完整命令包的定义【见命令部分】;
(8) 系统环境变量
i). 系统环境变量对系统的所有有用户所共享,而make的环境变量只对当前make执行的这一次有效,当前make命令行或者Makefile中定义的变量都将覆盖同名的系统环境变量,但它仅仅是在执行当前make时有效,并不能改变系统环境变量的值,而当make时使用”-e”参数时,make命令行和Makefile中定义的变量都不会覆盖同名的系统环境变量;
ii). 默认情况下只有系统环境变量和通过命令行方式定义的变量才会被传递给子make进程,在Makefile中定义的普通变量需要传递给子make时,使用”export”指示符来完成;
iii). 不推荐使用系统环境变量的方式来完成普通变量的工作,特别是在make的地柜调用中,任何一个环境变量的错误都是毁灭性的,所以尽量不要污染环境变量;变量的高级用法
(1) 变量的替换引用
格式为: $(VAR:A=B),含义:替换变量VAR中所有以”A”结尾的字为以”B”结尾,”A”和”B”中可以使用模式字符串”%”foo:=a.ob.o c.o bar:=$(foo:.o=.c) 或 bar:=$(foo:%.o=%.c)
(2) 变量的嵌套引用
对嵌套的变量引用的唯一限制是,在嵌套引用中包含函数时,不能通过仅仅指定部分需要调用的函数名称来实现对这个函数的调用(调用的函数包括了函数名本身和执行的参数),因为嵌套引用在展开之前已经完成了对函数名的识别测试。在Makefile中,应该尽量避免嵌套的变量引用,在一些必须的地方,也最好不要使用高于两级的嵌套引用。可以使用多个两层嵌套引用来替代一个多层的嵌套引用;
(3) 追加变量值
使用 “+=” 来实现对一个变量值的追加操作。如果被追加值的变量已经存在,那么”+=”就继承之前定义时的变量风格,但若是被追加的变量之前没有定义”+=”就会自动变成”=”,即变量被定义成一个递归展开式的变量;
(4) override指示符
i). 在执行make时,如果通过命令行制定了一个变量,那么它将替代在Makefile中出现的同名变量的定义。如果不希望命令行指定的变量替代在Makefile中定义的变量,那么在Makefile中使用override对这个变量进行声明;
ii). 变量定义时使用override,则后续对它进行值追加时,也需要使用带有”override”指示符的方式进行追加,否则追加无效;
iii). 如果在Makefile中变量未定义,而只是使用override指示符以追加方式进行赋值,则在make命令行中仍然可以有效的定义该变量;
iv). 指示符不是用来调整Makefile和执行时命令参数的冲突,其存在目的是为了时用户可以改变或者追加那些使用make的命令行指定的变量的定义;
(5) 目标指定变量
i). 格式如下,它将作用到由这个目标所引发的所有规则中去:TARGET ...: VARIABLE-ASSIGNMENT 或者 TARGET ...: override VARIABLE-ASSIGNMENT
ii). 该类型的变量只在它的目标的上下文有效,对其它的目标没有影响,且如果该目标指定标量与当前Makefile中的全局的变量重名,那么该目标指定标量对其它目标的全局变量也没有影响;
iii). 目标指定变量和普通全局变量有相同的优先级,即该变量和普通变量一样,可以被make命令行中定义的变量所覆盖,同样也可以在定义时使用override来防止被覆盖;
(6)模式指定变量
格式如下,此变量将作用到所有符合此模式的目标上去,与目标指定变量的区别时,这里的目标是一个或者多个”模式”目标,此时目标文件需要包含模式字符”%”及文件名字的特征字符,否则只有”%”,将对所有文件有效;PATTERN ...: VARIABLE-ASSIGNMENT 或者 PATTERN ...: override VARIABLE-ASSIGNMENT
自动化变量
(1) 常用自动化变量
$@ : 规则的目标文件名;
$< : 规则中通过目录搜索得到的依赖文件列表的第一个依赖文件;
$% : 当规则的目标是一个静态库文件时,代表静态库的一个成员名,如果目标不是静态库文件,其值为空;
$* : 在模式规则和静态模式规则中,代表茎(即膜表模式中的“%”代表的部分),茎也包含目录部分;
$^ : 通过目录搜索得到的依赖文件列表,空格分隔;
$+ : 同“$^”,区别是它保留了依赖文件列表中重复出现的名字;
$? : 所有比目标文件更新的依赖文件列表,空格分隔;
注 : 对于一个明确指定的规则来说不存在茎,在这种情况下“ *”代表文件名中除后缀以外的部分,否则若包含的时不可识别的后缀则该变量为空。通常在除模式规则和静态模式规则以外,明确指定目标文件的规则中应该避免使用这个变量;
在GNU make 中还可以通过这七个自动化变量,来获取一个完整文件名中的目录部分和具体文件名部分,在这些变量中加入“D”或者“F”字符就形成了一系列变种的自动化变量,这些变量都是在以前的旧版本的Makefile中,在当前版本中可以使用“dir”或“nodir”来实现同样的功能 : $(@D),$(@F),$(*D),$(*F),$(%D),$(%F),$(<D),$(<D),$(+D),$(+F),$(^D),$(^F),$(?D), $(?F);
(2) 自动化变量只能在规则的命令行中被引用, 但是为了支持“Sysv”特性,允许在规则的依赖列表中使用特殊的变量引用:$$@, $$(@D), $$(@F),这三个特殊的变量只能用在明确指定目标文件名的规则中或者时静态模式规则中,不用于隐含规则中;
(3) Sysv make 对规则的依赖进行两次替换展开,而GNU make对依赖列表的处理只有一次,对其中的变量和函数引用直接进行展开;
二. 函数
- 定义
GNU make 的函数提供了处理文件名、变量、文本、命令的方法,其调用语法如下:
$(FUNCTION ARGUMENTS) 或 \ ${FUNCTION ARGUMENTS}
其中:
FUNCTION : make 内嵌的函数名,对于用户自定的函数需要通过 make 的 “call” 函数来调用;
ARGUMENTS: 函数的参数,参数与函数名之间用若干个空格分割,多个参数间使用”,”来分割,参数的前导空格会被忽略,到后置空格不会,需要注意;且当有逗号或者空格作为函数的参数时需要把它们赋值给一个变量,在函数的参数中引用这个变量来实现; - 文本处理函数
. subst : 字符串替换
. patsubst : 模式字符串替换
. strip : 去字符串的开头和结尾的空格,并将多个连续空格合并为一个
. findstring : 查找字符串
. filter : 字符串过滤,按照模式进行过滤
. filter-out : 字符串反向过滤,按照模式进行过滤
. sort : 字符串排序,去重
. word : 取指定位置的单词
. wordlist : 取子串,从一些列字符串中,取出指定位置区间内的子串
. words : 统计单词数目
. firstword : 取收个单词 - 文件名处理函数
这些函数主要用来对一系列用空格分割的文件名进行转换
. dir : 取出各个文件的文件名的目录部分
. notdir : 取出各个文件的文件名的非目录部分
. suffix : 取出各个文件的文件名的后缀(点分割)
. basename : 取出各个文件的文件名的前缀部分(点分割)
. addsuffix : 给各个文件添加后缀
. addprefix : 给各个文件添加前缀
. join : 单词拼接(可以是两个单词序列的逐个拼接)
. wildcard : 通配符,获取左右符合匹配模式的文件名 - foreach 函数 : $(foreach VAR,LIST,TEXT)
循环函数,执行时把LIST中使用空格分割的单词依次取出赋值给变量VAR,然后执行TEXT,重复直到LIST中的最后一个单词。参数 VAR 是一个局部的临时变量,只在foreach函数的上下文有效; - if 函数 : $(if CONDITION,THEN-PART[,ELSE-PART])
执行时,忽略 CONDITION 的前导和结尾空字符,如果 CONDITION 的展开结果非空,则条件为真,执行计算表达式 THEN-PART,否则执行 ELSE-PART; - call 函数 : $(call VARIABLE,PARAM,PARAM,…)
实现对用户自定义函数的引用【注意该变量与通过define定义的命令包的区别】
VARIABLE : 用户定义的变量,通常是包含一系列运算操作的命令的返回值
PARAM : 传递给在 VARIABLE 中定义的参数 $(1)、$(2)…等; - value 函数 : $(value VARIABLE)
在不对变量进行展开的情况下获取变量的值。若是直接展开式,则并不会取消变量在定义时已经发生了的替换动作;若是递归展开式,返回的是不对这些引用进行展开,包含引用值的值,即该函数返回的是这个变量定以后的文本值; - origin 函数 : $(origin VARIABLE)
获取变量的相关信息,告诉我们这个变量的出处,并不操作变量返回值类型,
标识变量 VARIABLE 的状态或类型 :
. undefined : 还未定义;
. default : 内嵌变量,默认定义;
. environment : 系统环境变量;
. environment override : 系统环境变量,并且Makefile中存在一个同名的环境变量,make 使用了 “-e” ,系统环境变量取代了Makefile中定义的环境变量;
. file : 在某一个Makefile中定义;
. command line : 在命令行中定义;
. override : 在Makefile中定义,并且定义时使用 override 指示符声明;
. automatic : 自动化变量; - shell 函数 : make 可以用它来和外部通信
函数需要一个 shell 命令作为参数,函数的返回结果是此命令在 shell 中执行的结果。make仅对它的返回结果进行处理: make 将函数返回结果中的所有”\n”或者一对”\n\r”替换为空格,并去掉末尾的”\n”、”\n\r”;
函数本身的返回值是其参数的执行结果,没有进行任何处理,对结果的处理是由make进行的; - make 的控制函数
在Makefile中,当make执行过程中检测到某些错误时为用户提供信息,并且可以控制make过程是否继续;
i). $(error TEXT…)
产生致命错误,并提示”TEXT…”信息给用户,退出make执行,该函数一般不会出现在直接展开式的变量定义中,否则在make读取Makefile时将会提示致命错误;
ii). $(warning TEXT…)
功能类似于error,但它只提示信息不退出,不产生致命错误; - eval 函数 : (这个没理解太透,后续补充吧)