1、makefile 中文手册 第五章 规则的命令第五章:规则的命令规则的命令由一些 shell 命令行组成,它们被一条一条的执行。规则中除了第一条紧跟在依赖列表之后使用 分号隔开的命令以外,其它的每一行命令行必须以 Tab字符开始。多个命令行之间可以有空行和注释行(所谓 空行,就是不包含任何字符的一行。如果以 Tab键开始而其后没有命令的行,此行不是空行。是空命令行, 在执行规则时空行被忽略。通常系统中可能存在多个不同的 shell 。但在 make 处理 Makefile 过程时,如果没有明确指定,那么对所有规 则中命令行的解析使用“ /bin/sh”来完成。执行过程所使用的 shell 决定
2、了规则中的命令的语法和处理机制。当使用默认的“ /bin/sh”时,命令中出现 的字符“ #”到行末的内容被认为是注释。当然了“ #”可以不在此行的行首,此时“ #”之前的内容不会被作 为注视处理。另外在 make 解析 makefile 文件时,对待注释也是采用同样的处理方式。我们的 shell 脚本也一样。5.1命令回显通常, make 在执行命令行之前会把要执行的命令行输出到标准输出设备。我们称之为“回显”,就好像 我们在 shell 环境下输入命令执行时一样。但是,如果规则的命令行以字符“ ”开始,则 make 在执行这个命令时就不会回显这个将要被执行的命 令。典型的用法是在使用“ e
3、cho ”命令输出一些信息时。如:echo 开始编译 XXX 模块 .执行时,将会得到“开始编译 XXX 模块 . ”这条输出信息。如果在命令行之前没有字符“ ”,那么, make 的输出将是:echo 编译 XXX 模块 .编译 XXX 模块 .另外,如果使用 make 的命令行参数“ -n ”或“ -just-print ”,那么 make 执行时只显示所要执行的命令, 但不会真正的去执行这些命令。只有在这种情况下 make 才会打印出所有 make 需要执行的命令,其中也包括了 使用“ ”字符开始的命令。这个选项对于我们调试 Makefile 非常有用,使用这个选项我们可以按执行顺序打
4、印出 Makefile 中所有需要执行的所有命令。而 make 参数“ -s ”或“ -slient ”则是禁止所有执行命令的显示,就好像所有的命令行均使用“ ”开始 一样。在 Makefile 中使用没有依赖的特殊目标“ .SILENT ”也可以禁止命令的回显,但是它不如使用“ ”来 的灵活。因此在书写 Makefile 时,我们推荐使用“ ”来控制命令的回显。5.2命令的执行规则中,当目标需要被重建时。此规则所定义的命令将会被执行,如果是多行命令,那么每一行命令将在 一个独立的子 shell 进程中被执行(就是说,每一行命令的执行是在一个独立的 shell 进城中完成。因此,多行 命令之间
5、的执行是相互独立的,相互之间不存在依赖(多条命令行的执行为多个相互独立的进程。在 Makefile 中书写在同一行中的多个命令属于一个完整的 shell 命令行,书写在独立行的一条命令是一个独 立的 shell 命令行。因此:在一个规则的命令中,命令行“ cd ”改变目录不会对其后的命令的执行产生影响。就是说其后的命令执行的工作目录不会是之前使用“ cd ”进入的那个目录。如果要实现这个目的,就不能把“ cd ”和其后的命令放在两行来书写。而应该把这两条命令写在一行上,用分号分隔。这样它们才是一个完 整的 shell 命令行。如:foo : bar/losecd bar; gobble los
6、e ./foo如果希望把一个完整的 shell 命令行书写在多行上,需要使用反斜杠(来对处于多行的命令进行连接, 表示他们是一个完整的 shell 命令行。例如上例我们以也可以这样书写:foo : bar/losecd bar; gobble lose ./foomake 对所有规则命令的解析使用环境变量“ SHELL ”所指定的那个程序,在 GNU make中,默认的程序 是“ /bin/sh”。不像其他绝大多数变量,它们的值可以直接从同名的系统环境变量那里获得。 make 的环境变量“ SHELL ”没有使用系统环境变量的定义。因为系统环境变量“ SHELL ”指定那个程序被用来作为用户和
7、 系统交互的接口程序,它对于不存在直接交互过程的 make 显然不合适。在 make 的环境变量中“ SHELL ”会被 重新赋值;它作为一个变量我们也可以在 Makefile 中明确地给它赋值(指出解释程序的名字,当明确指定时需 要使用完整的路径名。如“ /bin/sh”,变量“ SHELL ”的默认值是“ /bin/sh”。(在 MS-DOS 下有些不同, MS-DOS不存在 SHELL 环境变量。这里不对 MS-DOS 下 make 进行介绍,有兴 趣地可以自行参考 info make关于此部分的描述5.3并发执行命令GNU make支持同时执行多条命令。通常情况下,同一时刻只有一个命令
8、在执行,下一个命令只有在当前 命令执行完成之后才能够开始执行。不过可以通过 make 的命令行选项“ -j ”或者“ -job ”来告诉 make 在同一 时刻可以允许多条命令同时被执行(注意,在 MS-DOS 中此选项无效,因为它是单任务操作系统。如果选项“ -j ”之后存在一个整数,其含义是告诉 make 在同一时刻可允许同时执行命令的数目。这个数字 被称为“ job slots”。当“ -j ”选项之后没有出现一个数字时,那么同一时刻执行的命令数目没有要求。使用默 认的“ job slots”,值为 1。表示 make 将串行的执行规则的命令(同一时刻只能有一条命令被执行。并行执行命令所
9、带来的问题是显而易见地:1. 多个同时执的命令的输出信息将同时被输出到终端。当出现错误时很难根据一大堆凌乱的信息来区分 是哪条命令执行错误。2. 在同一时刻可能会存在多个命令执行进程同时读取标准输入,但是对于标准输入设备来说,在同一时 刻只能存在一个进程访问它。就是说在某个时间点, make 只能保证此刻正在执行的进程中的一个进程 读取标准输入流,而其它进程的标准输入流将置无效。因此在一时刻多个执行命令的进程中只能有一个 进程获得标准输入,而其它需要读取标准输入流的进程由于输入流无效而导致致命错误(通常此进程会 得到操作系统的管道破裂信号而被终止。这是因为:执行中的命令在什么时候会读取标准输入
10、流(终端输入或重定向的标准输入是不可预测 的。而得到标准输入的顺序总是按照先来先获得的原则。那个命令首先被执行,那么它就可以首先得到标准输入设备。而其它后续需要获取标准输入设备的命令执行进程,由于不能得到标准输入而产生 致命错误。在 Makefile 规则中如果存在很多命令需要读取标准输入设备,而它们又被允许并行执行时, 就会出现这样的错误。为了解决这个问题。我们可以修改 Makefile 规则的命令使之在执行过程中避免使用标准输入。当然也可 以只存在一个命令在执行时会访问标准输入流的 Makefile 。3. 会导致 make 的递归调用出现问题。当 make 在执行命令时,如果某一条命令执
11、行失败(被一个信号中止,或非零退出,且该条命令产生的 错误不可忽略,那么其它的用于重建同一目标的命令执行也将会被终止。此种情况下,如果 make 没有使用“ -k ”或“ -keep-going ”选项, make 将停止执行而退出。另外:如果 make 在执行时,由某种原因(包括信号 被中止,此时它的子进程(那些执行规则命令行的 shell 子进程正在运行,那么 make 将等到所有这些子进程 结束之后才真正退出。执行 make 时,如果系统运行于重负荷状态下,我们需要控制(减轻系统在执行 make 时的负荷。可以使 用“ -l ”选项告诉 make 限制当前运行的任务的数量(make 所限
12、制的只是它本身所需要占用的系统负载,而不 能通过它去控制其它的任务所占用的系统负载。“ -l ”或“ -max-load ”选项一般后边需要跟一个浮点数。 如:-l 2.5它的意思是告诉 make 当系统平均负荷高于 2.5时,不再启动任何执行命令的子任务。不带浮点数的“ -l ”选项 用于取消前面通“ -l ”给定的负荷限制。更为准确一点就是:每一次, make 在启动一项任务之前(当前系统至少存在 make 的子任务正在运行。 首先 make 会检查当前系统的负荷;如果当前系统的负荷高于通过“ -l ”选项指定的值,那么 make 就不会在其 他任务完成之前启动任何任务。缺省情况下没有负荷
13、限制。5.4命令执行的错误通常;规则中的命令在运行结束后, make 会检测命令执行的返回状态,如果返回成功,那么就启动另外 一个子 shell 来执行下一条命令。规则中的所有命令执行完成之后,这个规则就执行完成了。如果一个规则中的 某一个命令出错(返回非 0状态, make 就会放弃对当前规则后续命令的执行,也有可能会终止所有规则的执 行。一些情况下,规则中一个命令的执行失败并不代表规则执行的错误。例如我们使用“ mkdir ”命令来确保 存在一个目录。当此目录不存在使我们就建立这个目录,当目录存在时那么“ mkdir ”就会执行失败。其实我 们并不希望 mkdir 在执行失败后终止规则的执
14、行。为了忽略一些无关命令执行失败的情况,我们可以在命令之 前加一个减号“ -”(在 Tab字符之后,来告诉 make 忽略此命令的执行失败。命令中的“ -”号会在 shell 解 析并执行此命令之前被去掉, shell 所解释的只是纯粹的命令,“ -”字符是由 make 来处理的。例如对于“ clean ”目标我们就可以这么写:clean:-rm *.o其含义是:即使执行“ rm ”删除文件失败, make 也继续执行。在执行 make 时,如果使用命令行选项“ -i ”或者“ ignore-errors ”, make 将忽略所有规则中命令执行的错误。没有依赖的特殊目标“ .IGNORE ”
15、在 Makefile 中有同样的效果。但是“ .IGNORE ”的方式已经很少使 用,因为它没有在命令行之前使用“ -”的方式灵活。当使用 make 的“ -i ”选项或者使用“ -”字符来忽略命令执行的错误时, make 始终把命令的执行结果作为 成功来对待。但会提示错误信息,同时提示这个错误被忽略。当不使用这种方式来通知 make 忽略命令执行的错误时,那么在错误发生时,就意味着定义这个命令规则 的目标不能被正确重建,同样,和此目标相关的其它目标也不会被正确重建。由于先决条件不能建立,那么后 续的命令将不会被执行。在发生这样情况时,通常 make 会立刻退出并返回一个非 0状态,表示执行失
16、败。像对待命令执行的错误一 样,我们可以使用 make 的命令行选项“ -k ”或者“ -keep-going ”来通知 make ,在出现错误时不立即退出, 而是继续后续命令的执行。直到无法继续执行命令时才异常退出。例如:使用“ -k ”参数,在重建一个 .o 文件 目标时出现错误, make 不会立即退出。虽然 make 已经知道因为这个错误而无法完成终极目标的重建,但还是 继续完成其它后续的依赖文件的重建。直到执行最后链接时才错误退出。一般 make 的“ -k ”参数在实际应用中,主要用途是:当同时修改了工程中的多个文件后,“ -k ”参数可以 帮助我们确认对那些文件的修改是正确的(可
17、以被编译,那些文件的修改是不正确的(不能正确编译。例 如我们修改了工程中的 20个源文件,修改完成之后使用带“ -k ”参数的 make ,它可以一次性找出修改的 20个 文件中哪些是不能被编译。通常情况下,执行失败的命令一旦改变了它所在规则的目标文件,则这个改变了的目标可能就不是一个被 正确重建的文件。但是这个文件的时间戳已经被更新过了(这种情况也会发生在使用一个信号来强制中止命令 执行的时候。因此下一次执行 make 时,由于时间戳更新它将不会被重建,将最终导致终极目标不能被正确 重建。为了避免这种错误的出现,应该在一次 make 执行失败之后使用“ make clean”来清除已经重建的
18、所有 目标,之后再执行 make 。我们也可以让 make 自动完成这个动作,我们只需要在 Makefile 中定义一个特殊的目 标“ .DELETE_ON_ERROR”。但是这个做法存在不兼容。推荐的做法是:在 make 执行失败时,修改错误之 后执行 make 之前,使用“ make clean”明确的删除第一次错误重建的所有目标。本节的最后,需要说明的是:虽然 make 提供了命令行选项来忽略命令执行的错误。建议对于此选项谨慎 使用。因为在一个大型的工程中,可能需要对成千个源文件进行编译。编译过程中的任何一个文件编译的错误 都是不能被忽略的没,否则可能最后完成的终极目标是一个让你感到非常
19、迷惑的东西,它在运行时可能会产生 一些莫名奇妙的现象。这需要我们保证书写的 Makefile 中规则的命令在执行时不会发生错误。特别需要注意哪 些有特殊目的的规则中的命令。当所有命令都可以被正确执行时,我们就没有必要为了避免一些讨厌的错误而 使用“ -i ”选项,为了实现同样的目的,我们可以使用其它的一些方式。例如删除命令可以这样写:“ $(RM”或者“ rm -f”,创建目录的命令可以这样写:“ mkdir p ”等等。5.5中断 make 的执行make 在执行命令时如果收到一个致命信号(终止 make ,那么 make 将会删除此过程中已经重建的那些 规则的目标文件。其依据是此目标文件的
20、当前时间戳和 make 开始执行时此文件的时间戳是否相同。删除这个目标文件的目的是为了确保下一次 make 时目标文件能够被正确重建。其原因我们上一节已经有 所讨论。假设正在编译时键入“ Ctrl-c ”,此时编译器已经开始写文件“ foo.o ”,但是“ Ctrl-c ”产生的信号关 闭了编译器。这种情况下文件“ foo.o ”可能是不完整的,但这个内容不完整的“ foo.o ”文件的时间戳比源程 序 foo.c 的时间戳新。如果在 make 收到终止信号后不删除文件“ foo.o ”而直接退出,那么下次执行 make 时此文件被认为已是最新的而不会去重建它。最后在链接生成终极目标时由于某一
21、个 .o 文件的不完 整,可能出现一堆令人难以理解的错误信息,或者产生了一个不正确的终极目标。相反,可以在 Makefile 中将一个目标文件作为特殊目标“ .PRECIOUS ”的依赖,来取消 make 在重建这个 目标时,在异常终止的情况下对这个目标文件的删除动作。每一次在 make 在重建一个目标之前,都将首先判 断该目标文件是否出现在特殊目标“ .PRECIOUS ”的依赖列表中,决定在终止信号发生时是否要删除这个目 标文件。不删除这种目标文件的原因可能是:1. 目标的重建动作是一个原子的不可被中断的过程; 2. 目标文件 的存在仅仅为了记录其重建时间(不关心其内容无; 3. 这个目标
22、文件必须一直存在来防止其它麻烦。5.6make 的递归执行make 的递归过程指的是:在 Makefile 中使用“ make ”作为一个命令来执行本身或者其它 makefile 文件的 过程。递归调用在一个存在有多级子目录的项目中非常有用。例如,当前目录下存在一个“ subdir ”子目录, 在这个子目录中有描述此目录编译规则的 makefile 文件,在执行 make 时需要从上层目录(当前目录开始并完 成它所有子目录的编译。那么在当前目录下可以使用这样一个规则来实现对这个子目录的编译:subsystem:cd subdir & $(MAKE其等价于规则:subsystem:$(MAKE
23、-C subdir对这两个规则的命令进行简单说明,规则中“ $(MAKE”是对变量“ MAKE ”(下一小节将详细讨论的 引用(关于变量可参考 第六章 Makefile中的变量 。第一个规则命令的意思是:进入子目录,然后在子目录 下执行 make 。第二个规则使用了 make 的“ -C ”选项,同样是首先进入子目录而后再执行 make 。书写这样的规则对于我们来说应该不是什么大问题,但是其中有一些需要我们深入了解的东西。首先需要 了解它如何工作、上层 make (在当前目录下运行的 make 进程和下层 make (subdir 目录下运行的 make 进 程之间存在的联系。也许会发现这两个
24、规则的实现,使用伪目标更能提高效率。在 make 的递归调用中,需要了解一下变量“ CURDIR ”,此变量代表 make 的工作目录。当使用“ -C ”选 项进入一个子目录后,此变量将被重新赋值。总之,如果在 Makefile 中没有对此变量进行显式的赋值操作,那 么它代表 make 的工作目录。我们也可以在 Makefile 为这个变量赋一个新的值。此时这变量将不再代表 make 的 工作目录。5.6.1变量 MAKE在使用 make 的递归调用时,在 Makefile 规则的命令行中应该使用变量“ MAKE ”来代替直接使用“ make ”。上一小节的例子应该这样来书写:subsyste
25、m:cd subdir & $(MAKE变量“ MAKE ”的值是“ make ”。如果其值为“ /bin/make”那么上边规则的命令就为“ cd subdir & /bin/make”。这样做的好处是:当我们使用一个其它版本的 make 程序时,可以保证最上层使用的 make 程序 和其子目录下执行的 make 程序保持一致。另外使用此变量的另外一个特点是:当规则命令行中变量 MAKE 时,可以改变 make 的“ -t ”(“ -touch ”,“ -n ”(“ -just-print ”和“ -q ”(“ -question ”命令行选项的效果。它所实现的功能和在规则中命令行首使用字符
26、“ +”的效果相同。在规则的命令行中使用“ make ”代替了“ $(MAKE”以后,上例子规则的命令行为:“ cd subdir & make ”。在我们执行“ make -t”(“ -t ”选项用来更新所有目标的时间戳,而不执行任何规则的命令,结 果是仅仅创建一个名为“ subsystem ”的文件,而不会进入到目录“ subdir ”去更新此目录下文件的时间戳。 我们使用“ -t ”命令行参数的初衷是对规则中的目标文件的时间戳进行更新。而如果使“ cd subdir & $(MAKE”作为规则的命令行,执行“ make -t”就可以实现我们的初衷。变量“ MAKE ”的这个特点是:在规则
27、的命令行中如果使用变量“ MAKE ”,标志“ -t ”、“ -n ”和“ -q ”在这个命令的执行中不起作用。尽管这些选项是告诉 make 不执行规则的命令行,但包含变量“ MAKE ”的 命令行除外,它们会被正常执行。同时,执行 make 的命令行选项参数被通过一个变量“ MAKEFLAGS ”传递 给子目录下的 make 程序。例如,当使用 make 的命令行选项“ -t ”来更新目标的时间戳或者“ -n ”选项打印命令时,这些选项将会被 赋值给变量“ MAKEFLAGS ”被传递到下一级的 make 程序中。在下一级子目录中执行的 make ,这些选项会被 附加作为 make 的命令行
28、参数来执行,和在此目录下使用“ make -t”或者“ make -n”有相同的效果。5.6.2变量和递归在 make 的递归执行过程中,上层 make 可以明确指定将一些变量的定义通过环境变量的方式传递给子 make 过程。没有明确指定需要传递的变量,上层 make 不会将其所执行的 Makefile 中定义的变量传递给 子 make 过程。使用环境变量传递上层所定义的变量时,上层所传递给子 make 过程的变量定义不会覆盖子 make 过程所执行 makefile 文件中的同名变量定义。如果子 make 过程所执行 Makefile 中存在同名变量定义,则上层传递的变量定义不会覆盖子 Ma
29、kefile 中定义 的值。就是说如果上层 make 传递的变量和子 make 所执行的 Makefile 中存在重复的变量定义,则以子 Makefile 中的变量定义为准。除非使用 make 的“ -e ”选项。我们在本节第一段中提到,上层 make 过程要将所执行的 Makefile 中的变量传递给子 make 过程,需要明确 地指出。在 GNU make中,实现此功能的指示符是“ export ”。当一个变量使用“ export ”进行声明后,变量 和它的值将被加入到当前工作的环境变量中,以后在 make 执行的所有规则的命令都可以使用这个变量。而当 没有使用指示符“ export ”对
30、任何变量进行声明的情况下,上层 make 只将那些已经初始化的环境变量(在执 行 make 之前已经存在的环境变量和使用命令行指定的变量(如命令“ make CFLAGS +=-g”或者“ make e CFLAGS +=-g”传递给子 make 程序,通常这些变量由字符、数字和下划线组成。需要注意的是:有 些 shell 不能处理那些名字中包含除字母、数字、下划线以外的其他字符的变量。存在两个特殊的变量“ SHELL ”和“ MAKEFLAGS ”,对于这两个变量除非使用指示符“ unexport ”对它 们进行声明,它们在整个 make 的执行过程中始终被自动的传递给所有的子 make 。
31、另外一个变量“ MAKEFILES ”,如果此变量有值(不为空那么同样它会被自动的传递给子 make 。在没有使用关键 字“ export ”声明的变量, make 执行时它们不会被自动传递给子 make ,因此下层 Makefile 中可以定义和上层 同名的变量,不会引起变量定义冲突。需要将一个在上层定义的变量传递给子 make ,应该在上层 Makefile 中使用指示符“ export ”对此变量进行 声明。格式如下:export VARIABLE .当不希望将一个变量传递给子 make 时,可以使用指示符“ unexport ”来声明这个变量。格式如下:unexport VARIABL
32、E .以上两种格式,指示符“ export ”或者“ unexport ”的参数(变量部分,如果它是对一个变量或者函数的 引用,这些变量或者函数将会被立即展开。并赋值给 export 或者 unexport 的变量(关于变量展开的过程可参 考 第六章 Makefile中的变量。例如:Y = Zexport X=$(Y其实就是“ export X=Z”。 export 时对变量进行展开,是为了保证传递给子 make 的变量值有效(使用当 前 Makefile 中定义的变量值。“ export ”更方便的用法是在定义变量的同时对它进行声明。看下边的几个例子:1.export VARIABLE = value等效于:VARIABLE = valueexport VARIABLE2.export VARIABLE := value等效于:VARIABLE := valueexport VARIABLE
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1