1 前言
在前篇Makefile规则中,描述了Makefile是为了生成一个文件,该文件称为目标,而用什么命令生成这个目标,就是规则
1.1 shell命令
所以在Makefile中,规则一般都由一些shell命令行组成。规则中除了第一条命令可以紧着着依赖列表之后(使用分隔符隔开),其他的都是一行一条命令,以[Tab]字符开头,多行命令之间可以添加空行和注释
1.2 注释
执行所使用的shell决定了规则中命令和语法的处理机制,使用默认的”/bin/sh“命令行中出现的”#“是注释
2 命令回显
在make执行命令行之前,会把执行的命令行输出到标准输出设备上,即"回显",但是如果规则的命令以"@"符号开始,则make在执行命令时不会回显此命令,常见用法是使用"echo"命令
@echo 输出日志信息......
- 如果没有"@",那么输出的信息是
echo编译XXX模块......
编译XXX模块......
由于使用了"@",所以只会输出"编译XXX模块......"
- 如果使用参数"-n"或"-just print",那么执行时只会回显,而不执行命令(包括使用"@"开头的命令)
- 使用参数"-s"或"--slient"则会禁止所有执行命令的显示(等于所有命令使用"@")开头
3 命令的执行
在Makefile中,当目标需要重建时,对应规则所定义的命令将会被执行,如果是多行命令,那么每一行都在一个独立的子shell进程中执行,即多行命令相互独立,相互不存在依赖
- 由于每一行shell独立,所以命令行"cd"不会影响后面的命令执行,所以如果需要使用"cd"功能,那么命令就不能分行写,需要写在一行,用分号隔开,例如
foo : bar/lose
cd bar; gobble lose > ../foo
- 如果实在要分行,那么用反斜线连接,例如
foo : bar/lose cd bar; \
gobble lose > ../foo
- make对所有的规则命令解析使用环境变量"shell"所指定的那个程序,默认程序是"/bin/sh"
4 并发执行命令
make支持同时多命令的执行,但是通常同一时刻只有一条命令执行,不过可以通过"-j"或者"-job"来实现多命令同时执行。如果"-j"之后存在一个整数,那么表示同一时刻允许执行的命令数目(即"job slots"),如果没有数字,那么表示使用默认的"job slots",默认值为1(即串行)
并行的缺点
- 多命令同时执行和输出信息,错误不好定位
- 对应标准输入设备,同一时刻只能有一个进程访问,所以其他进程的标准输入流将无效,这话导致错误
- 导致make的递归调用出现问题(见7.递归执行)
在make执行过程中,如果某条命令执行失败且产生的错误不可忽略,那么其他重建同一目标的命令将终止。如果make没有使用"-k"或"--keep-going",make将停止执行且退出
make在执行时,如果由于某原因被中止,如果它的子进程正在运行,那么make将等所有子进程结束之后才退出
4.1 负荷限制
在执行make时,如果系统处于重负荷状态下,则需要减轻执行make时的负荷。通过使用"-l"可以限制make当前任务的数量。"-l"或"--max-load"选项一般跟一个浮点数
例如
-l 2.5
是指系统平均负荷高于2.5时,不再启动任何执行命令的子任务,不带浮点的"-l"用于取消前面"-l"给定的负荷限制
在每次make执行一项任务之前,make会检查当前系统的负荷,如果当前系统的负荷高于"-l"设定的值,那么make就不会在其他任务完成前启动任务
5 命令执行的错误
通常命令执行结束后,make会检查命令执行的返回状态,如果返回成功,就启动另外一个子shell来执行命令。如果某一个规则命令出错(返回非0),make会放弃当前规则的后续执行,也可能终止所有规则执行
一般情况,某一个命令的失败不代表规则执行错误,对于一些失败并不影响的命令,可以通过在命令前加"-"减号告诉make忽略执行失败的情况。
减号会在shell解析并执行之前被去掉,它是有make处理的,例如常见的
clean:
-rm *.o
即使执行"rm"失败,也需要后续执行
当使用"-"来忽略命令执行的错误时,make会始终当做命令成功,但是会给出错误提示。使用"-k"或者"--keep-going"时,make错误不会立刻退出,这一般用在编译时确定被修改后的文件都是否能被编译通过
6 中断make的执行
如果在make执行命令时收到一个致命信号,那么make将会删除此过程中已经重建的那些规则目标。删除的原因是为了保证下一次make时目标文件能够被正确重建(因为那些已经重建的文件时间戳会更新,但是不能保证一定正确)
不过,如果目标文件是特殊目标“.PRECIOUS”的依赖,那么可以不被删除,不被删除的原因有
- 目标的重建动作是一个原子的不可被中断的过程;
- 目标文件的存在仅仅为了记录其重建时间(不关心其内容无)
- 这个目标文件必须一直存在来防止其它麻烦
7 递归执行
make的递归执行是:在Makefile中使用"make“命令执行本身或其他Makefile文件的过程,例如递归一个目录
subsystem:
cd subdir && $(MAKE)
$(MAKE)是对变量"MAKE"的引用,改命令的意思是:进入子目录,然后执行make
7.1 CURDIR变量
在make递归调用时,变量"CURDIR"表示make的工作目录,当使用"-C"选项进入一个子目录后,此变量会被重新赋值,如果Makefile中没有对此变量显示赋值,那么它代表make的工作目录
7.2 MAKE变量
变量"make"的值是"make”,如果是"/bin/make",那么之前的递归命令等于"cd subdir&&/bin/make",如果使用其他版本的make程序,那么"MAKE"的值会改变
注意:当使用变量"MAKE"时,标志"-t","-n",-q"都不起作用
7.3 变量和递归
在make的递归过程中,上层make可以指明一些变量通过环境变量传递给子make,没有指明的变量将不会传递。使用环境变量传递上层定义的变量时,上层传递的变量不会覆盖子make过程中Makefile文件中的同名变量(同名变量以子Makefile变量为准)
- 传递使用的指示符是"export"
- 当一个变量用"export"声明后,变量和它的值都被加入当前工作的环境变量中,之后的make都可以使用这个变量
- 如果没有"export"声明,那么make只讲已经初始化的环境变量和使用命令行指定的变量传递给子make
- 这些变量由字符、数字和下划线组成(有些shell不能处理那些名字中包含除字母、数字、下划线以外的其他字符的变量)
- 特殊的变量“SHELL”和“MAKEFLAGS”,这两个变量除非使用指示符“unexport”对它们进行声明,否则默认会自动传递给所有的子make。变量“MAKEFILES”如果有值(不为空)也会被自动的传递给子make
- 如果"export"不带任何参数,那么此Makefile中定义的所有变量都传递给子make(新版本GNU make使用“.EXPORT_ALL_VARIABLES”代替)
8 定义命令包
在Makefile中,对于重复的命令,可以类似c语言函数一样封装,使用指示符"define"
8.1 定义
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
- run-yacc就是命令包主体
- “define”和“endef”之间的命令就是命令包的主体
- “define”定义的命令包中,命令体中变量和函数的引用不会展开
- 命令体中所有的内容包括“$”、“(”、“)”等都是变量“run-yacc”的定义(类似c语言中的宏)
8.2 使用
命令包中所有命令中对其它变量的引用,在规则被执行时会被完全展开
foo.c : foo.y
$(run-yacc)
这里的具体含义在后续的变量相关章节中说明
9 空命令
对于只有目标文件,没有命令行的定义
target: ;
如果空命令使用独立命令行,那么必须使用[Tab]开始,所以一般不使用独立命令行
使用空命令的原因
空命令行可以防止make在执行时试图为重建这个目标去查找隐含命令(包括了使用隐含规则中的命令和“.DEFAULT”指定的命令
对于空命令最好不要指定依赖文件