命令
make的命令默认是被 “/bin/sh” 即UNIX的标准 Shell 解释执行的。
每条规则中的命令和操作系统Shell的命令行是一致的。make会一按顺序一条一条的执行命令,每条命令的开头必须以[Tab]键开头,除非,命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是空行会被忽略,但是如果该空格或空行是以Tab键开头的,那么make会认为其是一个空命令。
显示命令
- 一般情况,make会打印出各个命令的执行顺序,如果不想打印某条命令执行过程,可以在其前加上符号 @ 。例如:@echo balabala,其输出不会打印echo balabala。
- -n 、–just-print 两个参数即只打印命令执行步骤但不执行。
- -s 、–slient 两个参数即全面禁止打印。
执行命令
当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。例如:
下面两个例子,前者pwd的输出还是makefile所处文件目录,而后者才能在cd命令基础上执行pwd。
exem1 :
cd /home/xxx
pwd
exem2 :
cd /home/xxx;pwd
命令出错
每当命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令,当规则中所有的命令成功返回后,这个规则就算是成功完成了。如果一个规则中的某个命令出错了(命令退出码非零),那么make就会终止执行当前规则,这将有可能终止所有规则的执行。
若想让make忽略错误继续执行(比如mkdir文件夹已存在的错误),有以下几个方法:
- 在需要忽略错误的命令前添加符号 “-” 。
- 全局方法,键入make指令时添加参数 “-i” 或 “ --inogre-errors” 。
- 使用目标 “.IGNORE” ,这个目标下的命令自动忽略错误。
注意:make中的 “-k” 即 “ --keep-going” 参数的作用是如果当前规则有命令出错,则终止当前规则但继续执行其他规则。
嵌套执行
项目较大时,通常我们将不同模块放在不同目录,并为不同模块单独编写一个makefile,然后在总控makefile中调用次级makefile,方便修改维护。
在主makefile中调用下级makefile有以下两种写法:
targeta :
cd adir && $(MAKE)
targetb :
$(MAKE) -C bdir
这里的$(MAKE)用变量主要为了需要给make添加一些参数时便于修改。
最高一级的makefile称为总控makefile,总控Makefile的变量可以传递到下级的Makefile中(如果你显示的声明),但是不会覆盖下层的Makefile中所定义的变量,除非指定了“-e”参数。
想要传递变量到下级makefile,可以使用export参数修饰,不想传递的参数可以用unexport修饰。
需要注意的是,有两个变量,一个是SHELL,一个是MAKEFLAGS,这两个变量不管你是否export,其总是要传递到下层Makefile中。MAKEFLAGS是一个系统级的变量,想不传递它需要在make参数中添加上 “MAKEFLAGS=” ,即将它在下级makefile重新赋空值。
即:
targeta :
cd adir && $(MAKE) MAKEFLAGS=
(但是make命令中的有几个参数并不往下传递,它们是 “-C” ,“-f” ,“-h” ,“-o” 和 “-W” )
make参数中 “-w” 可以让make进入不同目录时将进入的目录打印出来,在嵌套执行中比较实用。“-C”会自动打开这个选项,而当有“-s”或“–no-print-directory”,“-w”不会生效。
命令包
相同的命令序列可以使用define关键字,语法上以define开始,以endef结束,使用时把它当成变量即可,例如:
define myshells
mkdir xxx
touch xxx
...
endef
use :
$(myshells)
make在执行命令包时,命令包中的每个命令会被依次独立执行。
变量
变量在声明时需要赋初始值,使用时要在变量名前加上美元 $ 符号,用()或{}括起来。若要使用真实的 $ 符号,需要用 $$ 来表示。
- makefile中所有的变量本质上是一个字符串替换,它在使用的位置精确地展开。
- 变量的展开总是在使用时才展开,若未执行到则不会主动展开,这与C语言中的预编译要区别开。
- 变量可以使用在任何你想的位置,比如目标、依赖、命令、甚至其他变量之中。
变量中的变量
变量是可以迭代,定义一个变量时可以使用其他变量的值,这使得我们可以在声明一个变量之后再具体确定它的真实值,比如:
CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar
但在使用中我们需要避免递归定义,这会导致make进入无限的展开,例如:
CFLAGS = $(CFLAGS) -O
A = $(B)
B = $(A)
为了避免这种情况,可以使用 := 操作符。
附:
- = 最基本的赋值
- := 覆盖之前的值
- ?= 如果没有被赋值过就赋予等号后面的值
- += 添加等号后面的值
例:
x := foo
y := $(x) bar
x := later
则最终的结果等价于,y = foo bar,x = later。 :=不能使用后面的变量。
一般来说,make的变量声明中会忽略最前面的空格和最后面的空格,但有时我们又需要这些空格,要想保留某些空格,可以用空变量开头和注释符#结尾来解决,例如:
nullstring :=
space := $(nullstring) # end of the line
此处nullstring是一个空变量,space的声明中以nullstring开始,紧接一个空格,而后以#注释符表示变量的结束,于是这样就定义了一个值为一个空格的变量space。
而当我们不需要尾部的空格时,那就必须注意跟在行尾的注释了,它可能会给我们的变量多加几个空格但这也许并不是我们的本意。
一些方便的用法
$(var:a=b)
其作用为将var中所有以a结尾的字符串的a替换为b,例如:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
执行的结果,bar的值就是 “a.c b.c c.c” 了。
另外,替换规则中也可以使用静态模式,将上述例子换为静态模式,就是:
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
变量套娃
变量的值可以继续作为变量,比如:
x = y
y = z
a := $($(x))
$(x)的值是 “y” , $(y)的值是 “z” ,于是 $(a)的值最终为 “z” 。
总之记住变量都是字符串,可以套娃拼拼拼就完事了。
追加变量
即使用 += 赋值符。需要注意几点:
- 追加字符串会自动在两者间加一个空格。
- 当变量是第一次赋值,那么+=相当于=。
- 当变量前面有定义,那么+=会使用上一次操作用的赋值符来进行附加。例如:
variable := value
variable += more
等价于:
variable := value
variable := $(variable) more
但如果是这种情况:
variable = value
variable += more
由于前次的赋值符是“=”,所以“+=”也会以“=”来做为赋值,那么岂不会发生变量的递归定义,这是很不好的,所以make会自动为我们解决这个问题,我们不必担心这个问题。
override 指示符
如果有变量是通过make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:
override < variable > = < value >
override < variable > := < value >
当然,你还可以追加:
override < variable > += < more text >
也可以使用多行定义:
override define foo
bar
endef
环境变量
make运行时可以把系统的环境变量加载到Makefile文件中,但如果Makefile文件中对相应环境变量有重新定义,那么make会使用文件中的值,除非make使用了-e参数。
优先级为: 命令行 > 文件内定义 > 系统设置
默认情况,命令行中设置的变量会自动传递到下层Makefile,环境变量也是一样的,若命令行没有设置又想传递到下层,需要使用export关键字。
目标局部变量
前面提到的变量声明定义操作都是全局变量,Makefile也支持针对某个目标设置局部变量,允许和全局变量重名,只影响指定的规则以及连带规则,不影响规则链之外的全局变量。
语法如下:
< target … > : < variable-assignment >
< target … > : overide < variable-assignment >
- target 指定的目标
- variable-assignment 赋值表达式
- 可以使用前面提到的 override 忽略命令行传入的值
另外,GNU 的 make 支持模式变量,上面提到的<target …>可以使用模式字符串,即包含至少一个 “%” 的字符串,即可为所有符合模式的目标设置一个局部变量。