《GNU_Make 中文手册》笔记之二 ing


第5章  规则的命令


命令回显


  通常,make在执行命令行之前,会把要执行的命令行输出到标准输出设备。我们称之为“回显”,就好像我们在shell环境下输入命令执行时一样。

  如果规则的命令行字符“@”开始,则不会回显命令。另外,如果使用make的命令行参数-n--just-print,那么make执行时只显示所要执行的命令包括“@”字符开始的命令),但会真正的去执行这些命令。该选项对于我们调试Makefile非常有用,可以按执行顺序打印出Makefile中所有需要执行的命令。make参数“-s”或“--slient”则是禁止所有执行命令的显示,就好像所有的命令行均使用“@”开始一样。


命令执行


  规则中,当目标需要被重建时,此规则所定义的命令将会被执行。如果是多行命令,那么每一行命令将在一个独立的子shell进程中被执行多行命令之间的执行是相互独立的。在Makefile中书写在同一行中的多个命令属于一个完整的shell命令行,书写在独立行的一条命令是一个独立的shell命令行。

  把多条命令写在一行上,用分号“;分隔。这样才是一个完整的shell命令行。如果希望把一个完整的shell命令行写在多行上,需要使用反斜杠(\来对处于多行的命令进行连接,表示他们是一个完整的shell命令行。例如:

    foo : bar/lose
      cd bar;gobble lose > ../foo

或者:

    foo : bar/lose
      cd bar; \
      gobble lose > ../foo

上例是在同一行中书写两个命令,两者同属于一个完整的shell命令行。后者使用反斜杠将一个完整的shell命令行书写在两行。


并发执行命令


  通过make的命令行选项-j或者--job,告诉make在同一时刻可以允许多条命令同时被执行并行执行命令所带来的问题如下:

  a. 多个同时执的命令的输出信息,将同时被输出到终端。当出现错误时很难根据一大堆凌乱的信息来区分哪条命令执行错误

  b. 在同一时刻可能会存在多个命令执行进程同时读取标准输入,但是对于标准输入设备来说,在同一时刻只能存在一个进程访问它。为了解决这个问题。我们可以修改Makefile规则的命令,使之在执行过程中避免使用标准输入。当然也可以只存在一个命令在执行时会访问标准输入流的Makefile;

  c. 会导致make的递归调用出现问题。

  如果make在执行时,由某种原因(包括信号)被中止,此时它的子进程(那些执行规则命令行的shell子进程)正在运行,那么make将等到所有子进程结束之后才真正退出


命令执行的错误


  通常:规则中的命令在运行结束后,make会检测命令执行的返回状态,如果返回成功,那么就启动另外一个子shell来执行下一条命令。规则中的所有命令执行完成之后,这个规则就执行完成了。如果一个规则中的某一个命令出错(返回非0状态),make就会放弃对当前规则后续命令的执行,也有可能会终止所有规则的执行。

  一些情况下,规则中一个命令的执行失败,并不代表规则执行的错误。为了忽略一些无关命令执行失败的情况,我们可以在命令之前加一个减号“-(在[Tab]字符之后)。命令中的“-”号会在shell解析并执行此命令之前被去掉,shell所解释的只是纯粹的命令,“-”字符是由make来处理的。

  在执行make时,如果使用命令行选项“-i”或者“—ignore-errors”, make将忽略所有规则中命令执行的错误

  当使用make的“-i”选项或者使用“-”字符忽略命令执行的错误时,make始终命令的执行结果作为成功来对待。但提示错误信息,同时提示这个错误被忽略。

  make的命令行选项-k或者--keep-going来通知make,在出现错误时不立即退出,而是继续后续命令的执行,直到无法继续执行命令时才异常退出。一般make的“-k”参数在实际应用中,主要用途是:当同时修改了工程中的多个文件,“-k”参数可以帮助我们确认对哪些文件的修改是正确的(可以被编译),那些文件的修改是不正确的(不能正确编译)。例如我们修改了工程中的20个源文件,修改完成之后使用带“-k”参数的make,它可以一次性找出修改的20个文件中哪些是不能被编译。

  通常情况下,执行失败的命令一旦改变了它所在规则的目标文件,则这个改变了的目标可能就不是一个被正确重建的文件,但是这个文件的时间戳已经被更新过了(包括使用信号强制中止命令执行的情况)。因此下一次执行make时,由于时间戳更新它将不会被重建,将最终导致终极目标不能被正确重建。推荐的做法是:在make执行失败时,修改错误之后执行make之前,使用“make clean明确地删除第一次错误重建的所有目标


make的递归执行


  make的递归过程:在Makefile中使用“make”作为一个命令,来执行本身或者其它makefile文件的过程。递归调用在一个存在多级子目录的项目中非常有用。

  例如,当前目录下存在一个“subdir”子目录,在这个子目录中有描述此目录编译规则的makefile文件,在执行make时需要从上层目录(当前目录)开始并完成它所有子目录的编译。那么在当前目录下可以使用这样一个规则来实现对这个子目录的编译:

    subsystem:
      cd subdir && $(MAKE)

其等价于规则:

    subsystem:
      $(MAKE) -C subdir

规则中“$(MAKE)”是对变量“MAKE”的引用,规则命令的意思是:进入子目录,然后在子目录下执行make。

  在make的递归调用中,需要了解一下变量“CURDIR”,此变量代表make的工作目录。当使用-C”选项进入一个子目录后,此变量将被重新赋值。如果没有对此变量进行显式的赋值,那么它代表make的工作目录。


变量MAKE

  当规则命令行中变量MAKE时,可以改变make-t”(“--touch”)-n”(“--just-print”)-q”(“--question”)命令行选项的效果。它所实现的功能,和在规则中命令行首使用字符“+”的效果相同。t”(“-t”选项用来更新所有目标的时间戳,而不执行任何规则的命令。如果使“cd subdir && $(MAKE)”作为规则的命令行,执行“make -t”就可以实现我们的初衷。

  变量“MAKE”的特点是:在规则的命令行中使用变量“MAKE”标志“-t”、“-n”和“-q”这个命令的执行中不起作用。尽管这些选项是告诉make不执行规则的命令行,但包含变量“MAKE”的命令行除外,它们会被正常执行。同时,执行make的命令行选项参数通过变量“MAKEFLAGS传递给子目录下的make程序


变量和递归

  在make的递归执行过程中,上层make可以明确指定将一些变量的定义通过环境变量的方式传递子make过程。没有明确指定需要传递的变量,上层make不会将其所执行的Makefile中定义的变量传递给子make过程。使用环境变量传递上层所定义的变量时,上层所传递给子make过程的变量定义,不会覆盖子make过程所执行makefile文件中的同名变量定义除非使用make的-e”选项

  当一个变量使用“export”进行声明后,变量和它的值将被加入到当前工作的环境变量中。上层make将环境变量明确声明执行make之前已经存在的环境变量)和使用命令行指定的变量(如命令“make CFLAGS +=-g”或者“make –e CFLAGS +=-g”)传递给子make程序,通常这些变量由字符、数字和下划线组成。

  存在两个特殊的变量,“SHELL”和“MAKEFLAGS”,除非使用指示符“unexport”对进行声明,否则在整个make的执行过程中,始终被自动的传递给所有的子make。另外一个变量“MAKEFILES”,如果此变量有值(不为空),那么同样被自动的传递给子make。

  指示符“export”或者“unexport”的参数(变量部分),如果它是对一个变量或者函数引用,会被立即展开,并赋值给export或者unexport的变量。

  一个不带任何参数的指示符“export”指示符:
    export
含义是将此Makefile中定义的所有变量传递给子make过程。需要说明的是:单独使用“export”来导出所有变量的行为,是老版本GNU make所默认的。但是在新版本的GNU make中取消了这一默认的行为。因此在编写和老版本GNU make兼容的Makefile时,需要使用特殊目标“.EXPORT_ALL_VARIABLES”来代替“export”,此特殊目标的功和不带参数的“export”相同。

  在多级递归调用的make执行过程中,变量“MAKELEVEL代表调用的深度。在make一级级的执行过程中变量“MAKELEVEL”的值不断的发生变化,通过它的值我们可以了解当前make递归调用的深度。最上一级时“MAKELEVEL”的值为“0”、下一级时为“1”、再下一级为“2”。

  “MAKELEVEL”变量,主要用在条件测试指令中。例如:我们可以通过测试此变量的值,来决定是否执行递归方式的make调用或者其他方式的make调用。


命令行选项和递归

  在make的递归执行过程中,最上层(可以称之为主控)make的命令行选项“-k”、“-s”等,会被自动的通过环境变量“MAKEFLAGS”传递给子make进程。需要注意的是有几个特殊的命令行选项例外,他们是:“-C”、“-f”、“-o”和“-W”,这些命令行选项是不会被赋值给变量“MAKEFLAGS”的。

  执行多级的make调用时,当不希望传递“MAKEFLAGS”的给子make时,需要在调用子make时,对这个变量进行赋空。例如:
    subsystem:
      cd subdir && $(MAKE) MAKEFLAGS=

此规则取消了子make执行时,对父make命令行选项的继承(将变量“MAKEFLAGS”的值赋为空)。


-w选项

  在多级make的递归调用过程中,选项“-w”或者“--print-directory”可以让make在开始编译一个目录之前完成此目录的编译之后给出相应的提示信息,方便开发人员跟踪make的执行过程。

  通常,选项“-w”会被自动打开。在主控Makefile中当如果使用“-C”参数来为make指定一个目录或者使用“cd”进入一个目录时,“-w”选项会被自动打开。主控make可以使用选项“-s”(“--slient”)来禁止此选项。


定义命令包


  将一组命令进行类似c语言函数一样的封装通过名字对这一组命令进行引用。在GNU make中,可以使用指示符“define”来完成这个功能。

  通常,我们把使用“define”定义的一组命令称为一个命令包。定义一个命令包的语法以“define”开始以“endef”结束。需要说明的是:使用“define”定义的命令包中,命令体中变量函数的引用不会展开。命令包是使用一个变量来表示的,因此我们就可以按使用变量的方式使用它。命令包中所有命令中对其它变量的引用,在规则被执行时会被完全展开。例如:

    define run-yacc
    yacc $(firstword $^)
    mv y.tab.c $@
    endef

这里,“run-yacc”是这个命令包的名字。在“define”和“endef”之间的命令就是命令包的主体。例如这样一个规则:
    foo.c : foo.y
      $(run-yacc)

此规则在执行时,我们来看一下命令包中的变量的替换过程:1. 命令包中的“$^”会被“foo.y”替换;2. “$@”被“foo.c”替换。

  当在一个规则中引用一个已定义的命令包时,命令包中的命令体会被原封不动地展开在引用它的地方(和 c语言中的宏一样)。这些命令就成为规则的命令。



第6章  Makfile中的变量


  在Makefile中变量有以下几个特征

  a. Makefile中变量和函数的展开规则命令行中的变量和函数以外),是在make读取makefile文件时进行的,这里的变量包括了使用“=”定义和使用指示符“define”定义的。

  b. 变量可以是所有我们能够想到的事物

  c. 变量名是不包括“:”、“#”、“=”、前置空白和尾空白的任何字符串。定义一个变量最好只包含字母数字下划线。因为其它字符可能会在make的后续版本中被赋予特殊含义,并且这样命名的变量对于一些shell来说是不能被作为环境变量来使用的。

  d. 变量名是大小写敏感的。推荐的做法:内部定义的一般变量使用小写方式,而对于一些参数列表(例如:编译选项CFLAGS)采用大写方式。

  e. 另外有一些变量名只包含了一个或者很少的几个特殊的字符(符号),称它们为自动化变量,像“$<”、“$@”、“$?”、“$*”等。


变量的引用


  变量的引用方式:“$(VARIABLE_NAME)”或者“${VARIABLE_NAME}”来引用一个变量的定义。例如:“$(foo) ”或者“${foo}”。变量的展开过程和c语言中的宏展开的过程相同,是一个严格的文本替换过程。注:美元符号“$”在Makefile中有特殊的含义,所有在命令或者文件名中使用“$”时需要用两个美元符号“$$”来表示

  Makefile中在单字符变量的引用,可以直接使用“$x”的格式来实现,自动化变量也使用这种格式。例如,“$PATH”在Makefile中实际上是“$(P)ATH”。这一点shell中变量的引用方式不同,shell中变量的引用可以是“${xx}”或者“$xx”格式。但在Makefile中多字符变量名的引用只能是$(xx)”或者“${xx}”格式


两种变量定义(赋值)


  在GNU make中,变量的定义有两种方式(或者称为风格),区别在于:a. 定义方式;b. 展开时机。


递归展开式变量

  递归方式扩展的变量的定义,是通过“=或者使用指示符“define”定义的。在变量定义时,变量值中对其他变量的引用不会被替换展开;而是变量引用处替换展开的同时,它所引用的其它变量才会被一同替换展开

  “递归展开”式变量优点是:在定义时,可以引用之前未定义的变量(可能在后续部分定义,或者是通过make的命令行选项传递的变量)。其缺点是:可能导致make陷入到无限的变量展开过程中,最终使make执行失败。例如:

    CFLAGS = $(include_dirs) -O

    include_dirs = -Ifoo -Ibar

“CFLAGS”会在命令中被展开为“-Ifoo -Ibar -O”,定义中使用了其后定义的变量“include_dirs”。给这个变量追加值:

    CFLAGS = $(CFLAGS) –O

会导致make对变量“CFLAGS”的无限展过程中去(这种定义就是变量的递归定义)。

  第二个缺点:如果使用了函数,那么包含在变量值中的函数总会在变量被引用的地方执行(变量被展开时)。因为在这种风格中,对函数引用的替换展开发生在变量展开的过程中,而不是在定义这个变量的时候。这样所带来的问题是:使make的执行效率降低(每一次在变量被展开时,都要展开他所引用的函数);另外在某些时候会出现一些变量和函数的引用出现非预期的结果。


直接展开式变量

  “直接展开”式变量,使用“:=”定义,变量值中对其他量或者函数的引用定义变量时展开(对变量进行替换)。和递归展开式变量不同:此风格变量在定义时,就完成了对所引用变量和函数的展开,因此不能实现对其后定义变量的引用

  在复杂的Makefile中,推荐使用直接展开式变量,尽量避免和减少递归式变量的使用


操作符“?=”

  GNU make中,还有一个被称为条件赋值的赋值操作符?=。被称为条件赋值是因为:只有此变量在之前没有赋值的情况下才会对这个变量进行赋值。例如:
    FOO ?= bar
其等价于:
    ifeq ($(origin FOO), undefined)
    FOO = bar
    endif

含义是:如果变量“FOO”在之前没有定义,就给它赋值“bar”。否则不改变它的值。


变量的高级用法


变量的替换引用

  对于一个已经定义的变量,可以使用替换引用将其值中的后缀字符(串)使用指定的字符(字符串)替换。格式为$(VAR:A=B)(或者${VAR:A=B}),意思是,替换变量“VAR”中所有“A”字符结尾的字为“B”结尾的字。例如:

    foo := a.o b.o c.o
    bar := $(foo:.o=.c)

在这个定义中,变量“bar”的值就为“a.c b.c c.c”。

  变量的替换引用其实是函数“patsubst”的一个简化实现。另外一种引用替换的技术使用功能更强大的“patsubst”函数,它的格式和上面“$(VAR:A=B)”的格式相类似,不过需要在“A”和“B”中需要包含模式字符“%。例如:
    foo := a.o b.o c.o
    bar := $(foo:%.o=%.c)

这个例子同样使变量“bar”的值为“a.c b.c c.c”。这种格式的替换引用方式比第一种方式更通用


变量取值


一个变量可以通过以下几种方式来获得值:

  a. 运行make时,通过命令行选项来取代一个已定义的变量值;

  b. 在makefile文件中,通过赋值或者使用“define”来为一个变量赋值;

  c. 将变量设为系统环境变量。所有系统环境变量都可以被make使用。

  d. 自动化变量,在不同的规则中自动化变量会被赋予不同的值。它们每一个都有单一的习惯性用法。

  e. 一些变量具有固定的值(隐含变量)。


变量定义


  Makefile中变量的定义,通过=”(递归方式或者:=”(静态方式来实现的。静态方式或者说直接展开方式实现时,如果存其他变量或者函数的引用,在定义时被替换展开。定义一个变量时需要明确以下几点:

a. 变量名之中可以包含函数或者其它变量引用,make在读入此行时,根据已定义情况,进行替换展开而产生实际的变量名;

b. 变量的定义值在长度上没有限制。变量定义较长时,一个好的做法就是将比较长的行分多个行来书写,行与行之间使用反斜杠(\)连接,表示一个完整的行;

c. 当引用一个没有定义的变量时,默认值为

d. 一些特殊的变量在make中有内嵌固定的值(隐含变量),允许显式地重新赋值

e. 自动环变量的值,不能在Makefile中进行显式的修改

f. 如果你希望仅对一个之前未定义过的变量进行赋值,那么可以使用速记符?=”(条件方式)来代替“=”或者“:=”来实现。


追加变量值


  通常,一个通用变量在定义之后的其他一个地方,可以对其值进行追加。在Makefile中使用“+=”(追加方式来实现对一个变量值的追加操作,使用空格和原有值分开






你可能感兴趣的:(Autotools)