Make语言详解

Make

赋值操作

=:赋值,将右值赋给左值

?=:条件赋值,如果变量没有初始化,就用右边初始化左边

:=:直接赋值,不过展开方式不同

::=:与:=一致

+=:追加赋值

!=:右值为一条shell命令,shell命令的输出赋给左值

!=例子

1
var != echo "hello"  #此处var的值就是hello

等价于

1
var := ${shell echo "hello"} #这里的shell是Make的内建函数,表示后面接的参数是shell命令,并且返回该命令的结果

在Makefile中使用$,要用$$表示,shell中用\$

变量

变量的扩展方式

简单扩展

赋值时就确定了变量的值,不管它是否引用了其他变量,:=::=!=都是简单扩展

循环递归扩展

在赋值时不确定,如果引用了其他变量,make会先确定其他变量,然后再确定该变量,=?=属于循环变量

直接扩展

+=属于直接扩展,原变量是啥,左边就是啥

创建私有变量

默认是全局变量,若要使用私有变量,使用private

删除变量

删除变量使用unset

内置变量

$@:表示所有目标

$<:依赖中的第一个依赖

$^:依赖列表中的所有文件

$?:依赖列表中所有更新的文件

$*:在模式中,该变量代表茎,也就是%的部分

~:用户家目录

./:Make的当前目录

@:禁止命令回显

-:出错则忽略,而不是报错退出

+:当make -n时,只有带+号的会执行

@::占位符,防止make出现Nothing to be done

变量可以定义在命令行中,会覆盖掉make自带的变量

override:让make采用Makefile的赋值

make启动时,所有来自环境的变量都成为make变量

make会在执行一个规则的命令脚本之前,立刻创建自动变量

常用Make变量

VPATH:搜文件的路径

MAKEFLAGS:Make的参数

MAKELEVEL:Make的嵌套层数

MAKECMDGOALS:工作目标的列表

CURDIR:当前工作目录

MAKEFILE_LIST:读取的Makefile列表,最后一个是当前的Makefile

MAKE_VERSION:make的版本

VARIABLES:make从各个Makefile读进的变量列表,不含工作目标

变量扩展规则

  1. 对于变量赋值,make会立即扩展其左边
  2. 对于=?=,其右边会被延后到变量使用时进行扩展,在分析依存图时进行
  3. :=的右边会立即被扩展
  4. 如果+=的左边是一个简单变量,+=的右边会被立即扩展,否则,其求值会被延后
  5. 对于宏定义,其宏名会被立即扩展,宏体会被延后扩展
  6. 对于规则,工作目标和依赖总是被立即扩展,命令则总是被延时扩展

工作目标的专属变量

1
target... : variable =, :=, ?=, += value

专属变量的定义会附加在工作目标之上,且只在该工作目标以及相应的任何必要条件被处理时才会发生作用

此类变量的赋值动作会在处理工作目标时进行

make参数

-f:指定一个文件作为Makefile

-k:遇到错误不停止,一次发现所有的编译错误

-n:输出将要执行的步骤,而不真的执行

-c:切换到指定目录,执行该目录下的Makefile

-l:指定make去寻找的链接库libName.solibName.a

-s:静默输出

伪目标

.PHONY:指示不要将一个目标当作文件来处理

伪目标总是最新的

通配符

*

*匹配任何东西,包括空

?

通常在依赖中,匹配所有更新的目标

使用通配符本身时,需要使用转义字符\

如果使用OBJ = *.o,一般情况下,会匹配通配符,但是若当前目录下没有可匹配的文件,就会将*.o这个字符串赋给OBJ

通配符函数

1
${wildcard *.o}

其好处是,如果没有匹配的文件,那么赋给左边的将是一个空

模式规则

模式规则类似于普通规则,模式规则中包含模式字符%,包含有模式字符%的目标被用来匹配一个文件名,%可以匹配任何非空字符串,依赖中的%取值依赖于目标的%

静态模式规则

1
2
3
目标...:目标模式:依赖模式
命令
...

另一种常用语法

1
2
3
4
${OBJ : per-pattern=pattern}

e.g:
${OBJ:%.c = %.o}

将OBJ中所有.c后缀文件替换为.o后缀

当模式出现在目标和依赖时,由make进行扩展

当模式出现在命令中时,由shell进行扩展

Makefile的执行顺序

  1. 读入所有的Makefile
  2. 初始化变量
  3. 分析规则,若有显式规则,则加入依赖库,若没有,则分析隐式规则
  4. 根据依赖关系,决定哪些目标需要更新
  5. 执行命令

命令脚本初始化的顺序

  1. 读取程序代码
  2. 扩展变量
  3. 对make表达式求值
  4. 执行命令
  1. 命令脚本的求值会被延后的执行的时候
  2. ifdef的处理会在读入的时候
  3. 在执行之前,首先会看是否有可扩展的变量和可求值的表达式
  4. 宏被扩展时,会为每一行增加tab

Make内置函数

调用方式:${<function_name> <arguments>},参数之间以逗号comma分割,函数名和参数之间以空格分割

字符串操作函数

subst——字符串替换

1
$(subst <from>, <to>, <text>)

返回替换过后的字符串

patsubst——模式字符串替换

1
$(patsubst <pattern>, <replacement>, <text>)

查找text中的单词(以空白符分割)是否符合pattern,如匹配则以replacement替换,如果replacement中含有%,则与pattern中的%含义一致

返回被替换后的字符串

strip——去空格函数

1
$(strip <string>)

去掉string中开头和结尾的空格

返回去掉首位空格后的字符串

findstring——查找字符串

1
$(findstring <find>,<in>)

in中查找find子串

如果找到,返回find,否则返回空

filter——过滤函数

1
$(filter <pattern...>, <text>)

pattern模式过滤text字符串中的单词,保留符合pattern模式的单词,可以有多个模式

返回符合pattern模式的子串

filter-out——反过滤函数

1
$(filter-out <pattern>, <text>)

去掉符合pattern模式的子串,可以有多个模式

返回不符合pattern模式的子串

sort——排序函数

1
$(sort <list>)

list中的单词进行排序(升序),

返回排序后的字符串,会去掉重复单词

word——取单词函数

1
$(word <n>, <text>)

取字串text中的第n个单词,从1开始

返回text中的第n个单词,如果ntext 中的单词数大,则返回空

wordlist——取单词串函数

1
$(wordlist <ss>, <e>, <text>)

text中取从sse的单词

返回子串,若ss比末尾大,返回空

words——单词个数统计函数

1
$(words <text>)

统计text中单词个数

返回数量

firstword——首单词函数

1
$(firstword <text>)

text中第一个单词

返回单词字串

文件名操作函数

dir——取目录函数

1
$(dir <names...>)

从文件名序列中取出目录部分,目录部分是指最后一个/之前的部分,如果没有/,则返回./

返回目录名

notdir——取文件名函数

1
$(notdir <names...>)

从字符串序列中取出文件名,指最后一个/之后的内容

返回文件名

suffix——取后缀函数

1
$(suffix <names...>)

返回后缀,若无后缀返回空

basename——取前缀函数

1
$(basename <names...>)

返回前缀,若无则返回空

addsuffix——加后缀函数

1
$(addsuffix <suffix>, <names...>)

把后缀suffix加到names的每个单词后面

返回加了后缀的字符串

addprefix——加前缀函数

1
$(addprefix <prefix>, <names...>)

把前缀prefix加到names的每个单词前面

返回加了前缀的字符串

join——连接函数

1
$(join <list1>, <list2>)

list2对应连接到list1后面,若list1长,list1中多出来的保持原样,list2长,则复制到list1后面

返回连接后的字符串

realpath——取真实路径函数

1
$(realpath names...)

names中的每个文件名,反会规范的绝对路径,规范名指的是不含...,也不包含任何重复路径,分隔符,符号链接

返回路径,失败返回空

abspath——取绝对路径

1
$(abspath names...)

names中的每个文件名,返回一个不含...的绝对路径,也不含重复路径,与realpath相比,abspath不解析符号链接,也不要求文件名names引用现有的文件或目录,使用通配符函数来测试函数是否存在,如果目标不存在,也返回绝对地址

返回绝对地址

功能函数

foreach——循环遍历

1
$(foreach <var>, <list>, <text>)

list中的单词逐个放入var指定的变量中,然后执行text中的表达式,结果放入返回值中

var是个临时变量,作用域只在foreach

if——条件判断

1
2
$(if <condition>, <then-part>)
$(if <condition>, <then-part>, <else-part>)

判断condition,如果为真,执行then-part,否则,执行else-part(若存在)

call——调用某函数

1
$(call <expression>, <param1>, <param2>, ..., <paramn>)

唯一一个可以调用创建的新函数,param*会取代expression中的变量

call处理参数时,第二个及之后的参数会保留空格

origin——参数来源

1
$(origin <variable>)

判断这个变量来自哪里

可能的返回值

1
2
3
4
5
6
7
undefined   	#未定义
default #默认
environment #环境变量,且-e没有打开
file #该变量被定义在makefile中
command line #命令行定义
override #该变量被重写
automatic #该变量是一个自动化变量

shell——运行shell命令

1
$(shell <func> ...)

与’`’相同,其会启动一个子shell,然后运行命令,将结果赋给左边的变量

error——报错函数

1
$(error <text...>)

产生一个致命错误,text是其输出信息

warn——警告函数

1
$(warn <text...>)

产生一个警告,text是其输出信息

eval——二次命令

1
$(eval sources:=foo.c bar.c)

将文本直接放入make的解析器中

简单而言,eval会将后面的求值结果,当作make命令再执行一次

条件指令

1
2
ifdef variable-name
ifndef variable-name

执行上述指令时,variable-name不需要用$()包裹

条件指令可以用于宏定义和命令脚本中

1
2
ifeq test
ifneq test

test可以表示为"a" "b"(a, b)

这里的test有点微妙,如果采用()的形式,逗号comma后的空格会被忽略,逗号comma之前的会被保留

引入指令

也就是include

引入指令流程:

  1. 当make看到include时,首先对通配符及变量进行扩展,然后试着引入该文件
  2. 如果该文件存在,则流程继续,如果该文件不存在,make产生报告,并继续读取其余Makefile
  3. 当所有读取完成,make会从规则库中找出任何可用来更新引入文件的规则,如果找到了一个相符的规则,就更新工作目标,如果任何一个引入文件规则被更新,make会清除其内部数据,并重新引入该Makefile
  4. 如重复以上流程后,仍有引入文件不存在,则make报错

Make语言详解
https://yill-z.github.io/2025/01/01/Make/
作者
Yill Zhang
发布于
2025年1月1日
许可协议