• Makefile编写 一 *****


      编译:把高级语言书写的代码转换为机器可识别的机器指令。编译高级语言后生成的指令虽然可被机器识别,但是还不能被执行。编译时,编译器检查高级语言的语法、函数与变量的声明是否正确。只有所有的语法正确、相关变量定义正确编译器就可以编译出中间目标文件。通常,一个高级语言的源文件都可对应一个目标文件。目标文件在Linux中默认后缀为“.o”(如“foo.c”的目标文件为“foo.o”)。

      链接:将多.o文件,或者.o文件和库文件链接成为可被操作系统执行的可执行程序(Linux环境下,可执行文件的格式为“ELF”格式)。链接器不检查函数所在的源文件,只检查所有.o文件中的定义的符号。将.o文件中使用的函数和其它.o或者库文件中的相关符号进行合并,对所有文件中的符号进行重新安排(重定位),并链接系统相关文件(程序启动文件等)最终生成可执行程序。链接过程使用GNU 的“ld”工具。

      静态库:又称为文档文件(Archive File)。它是多个.o文件的集合。Linux中静态库文件的后缀为“.a”。静态库中的各个成员(.o文件)没有特殊的存在格式,仅仅是一个.o文件的集合。使用“ar”工具维护和管理静态库。

      共享库:也是多个.o文件的集合,但是这些.o文件时有编译器按照一种特殊的方式生成(Linux中,共享库文件格式通常为“ELF”格式。共享库已经具备了可执行条件)。模块中各个成员的地址(变量引用和函数调用)都是相对地址。使用此共享库的程序在运行时,共享库被动态加载到内存并和主程序在内存中进行连接。多个可执行程序可共享库文件的代码段(多个程序可以共享的使用库中的某一个模块,共享代码,不共享数据)。另外共享库的成员对象可被执行(由libdl.so提供支持)。

    参考 info ld了解更加详细的关于ld的说明和用法。

       Makefile包含内容:

      显式规则:它描述了在何种情况下如何更新一个或者多个被称为目标的文件(Makefile的目标文件)。书写Makefile时需要明确地给出目标文件、目标的依赖文件列表以及更新目标文件所需要的命令(有些规则没有命令,这样的规则只是纯粹的描述了文件之间的依赖关系)。

      隐含规则:它是make根据一类目标文件(典型的是根据文件名的后缀)而自动推导出来的规则。make根据目标文件的名,自动产生目标的依赖文件并使用默认的命令来对目标进行更新(建立一个规则)。

      变量定义:使用一个字符或字符串代表一段文本串,当定义了一个变量以后,Makefile后续在需要使用此文本串的地方,通过引用这个变量来实现对文本串的使用。第一章的例子中,我们就定义了一个变量“objects”来表示一个.o文件列表。

      Makefile指示符:指示符指明在make程序读取makefile文件过程中所要执行的一个动作。其中包括:

      注释:Makefile中“#”字符后的内容被作为是注释内容(和shell脚本一样)处理。如果此行的第一个非空字符为“#”,那么此行为注释行。注释行的结尾如果存在反斜线(),那么下一行也被作为注释行。一般在书写Makefile时推荐将注释作为一个独立的行,而不要和Makefile的有效行放在一行中书写。当在Makefile中需要使用字符“#”时,可以使用反斜线加“#”(#)来实现(对特殊字符“#”的转义),其表示将“#”作为一字符而不是注释的开始标志。

      以将一个较长行使用反斜线()来分解为多行,这样可以使我们的Makefile书写清晰、容易阅读理解。但需要注意:反斜线之后不能有空格

    make的执行:执行过程分为两个阶段。

      第一阶段:读取所有的makefile文件(包括“MAKIFILES”变量指定的、指示符“include”指定的、以及命令行选项“-f(--file)”指定的makefile文件),内建所有的变量、明确规则和隐含规则,并建立所有目标和依赖之间的依赖关系结构链表。

      在第二阶段:根据第一阶段已经建立的依赖关系结构链表决定哪些目标需要更新,并使用对应的规则来重建这些目标。

      理解make执行过程的两个阶段是很重要的。它能帮助我们更深入的了解执行过程中变量以及函数是如何被展开的。变量和函数的展开问题是书写Makefile时容易犯错和引起大家迷惑的地方之一。明确变量和函数的展开阶段,对正确的使用变量非常有帮助。

      首先,明确以下基本的概念;在make执行的第一阶段中如果变量和函数被展开,那么称此展开是“立即”的,此时所有的变量和函数被展开在需要构建的结构链表的对应规则中(此规则在建立链表是需要使用)。其他的展开称之为“延后”的。这些变量和函数不会被“立即”展开,而是直到后续某些规则须要使用时或者在make处理的第二阶段它们才会被展开。

      

    变量取值

    变量定义解析的规则如下:

      IMMEDIATE = DEFERRED

      IMMEDIATE ?= DEFERRED

      IMMEDIATE := IMMEDIATE

      IMMEDIATE += DEFERRED or IMMEDIATE

      define IMMEDIATE

      DEFERRED

      Endef

    当变量使用追加符(+=)时,如果此前这个变量是一个简单变量(使用 :=定义的)则认为它是立即展开的,其它情况时都被认为是“延后”展开的变量

    通配符使用举例:

      1、可以用在规则的目标、依赖中,make在读取Makefile时会自动对其进行匹配处理(通配符展开);

      2、可出现在规则的命令中,通配符的通配处理是在shell在执行此命令时完成的。

    除这两种情况之外的其它上下文中,不能直接使用通配符。而是需要通过函数“wildcard”来实现,即不能在变量中使用通配符。

      print: *.c

      lpr -p $?

      touch print

    通配符缺陷,如下:

      objects = *.o

     

      foo : $(objects)

      cc -o foo $(CFLAGS) $(objects)

    如果在工作目录下已经存在必需的.o文件,那么这些.o文件将成为目标的依赖文件,目标“foo”将根据规则被重建。

    但是如果将工作目录下所有的.o文件删除,重新执行make将会得到一个类似于“没有创建*.o文件的规则” 的错误提示。

    目录搜索:

    1、一般目录搜索:VPATH

    定义变量“VPATH”时,使用空格或者冒号(:)将多个需要搜索的目录分开

    VPATH = src:../headers

    2、选择性目录搜索

      1、vpath PATTERN DIRECTORIES

      为所有符合模式“PATTERN”的文件指定搜索目录“DIRECTORIES”。多个目录使用空格或者冒号(:)分开。类似上一小节的“VPATH”变量。

      2、vpath PATTERN

      清除之前为符合模式“PATTERN”的文件设置的搜索路径。

      3、vpath

      清除所有已被设置的文件搜索路径。

      举例:

      vpath %.h ../headers

      vpath %.c foo

      vpath % blish

      vpath %.c bar

    表示对所有的.c文件,make依次查找目录:“foo”、blish”、“bar”。

    库文件和搜索目录:-lNAME

    1. make在执行规则时会在当前目录下搜索一个名字为“libNAME.so”的文件;

    2. 如果当前工作目录下不存在这样一个文件,则make会继续搜索使用“VPATH”或者“vpath”指定的搜索目录。

    3. 还是不存在,make将搜索系统库文件存在的默认目录,顺序是:“/lib”、“/usr/lib”

    终极目标的确认:

      1、第一个规则中的第一个目标,但此目标不能是为“.”开始

      2、如果Makefile的第一个规则有多个目标,那么默认的终极目标是多个目标中的第一个。

      3、对于模式规则的目标,不会被作为“终极目标”。

      4、通过命令行指定终极目标,如:make clean

    甚至可以指定一个在Makefile中不存在的目标作为终极目标

    “order-only”依赖的使用举例:

        LIBS = libtest.a

    foo : foo.c | $(LIBS)

           $(CC) $(CFLAGS) $< -o $@ $(LIBS)

    make在执行这个规则时,如果目标文件“foo”已经存在。当“foo.c”被修改以后,目标“foo”将会被重建,但是当“libtest.a”被修改以后。将不执行规则的命令来重建目标“foo”。

    伪目标:永远会被执行的目标

    详细请看"Makefile编写 三 伪目标的作用"

    1、Makefile中定义的只执行命令的目标,如果工作目录下有一个同名的实际文件,则此目标永远不会执行。

    2. 提高执行make时的效率

    3、一般情况下,一个伪目标不作为另外一个目标的依赖。这是因为当一个目标文件的依赖包含伪目标时,每一次在执行这个规则时,伪目标所定义的命令都会被执行。

    效率举例:

      SUBDIRS = foo bar baz

      subdirs:

      for dir in $(SUBDIRS); do

      $(MAKE) -C $$dir;

      done

    存在以下几个问题。

      1. 当子目录执行make出现错误时,make不会退出。就是说,在对某一个目录执行make失败以后,会继续对其他的目录进行make。在最终执行失败的情况下,我们很难根据错误提示定位出具体是在那个目录下执行make时发生错误。这样给问题定位造成了很大的困难。

      2. 另外一个问题就是使用这种shell的循环方式时,没有用到make对目录的并行处理功能,由于规则的命令是一条完整的shell命令,不能被并行处理。

     使用伪目标:

      SUBDIRS = foo bar baz

      .PHONY: subdirs $(SUBDIRS)

     

      subdirs: $(SUBDIRS)

      $(SUBDIRS):

      $(MAKE) -C $@

      foo: baz

    上边的实现中有一个没有命令行的规则“foo: baz”,此规则用来限制子目录的make顺序。它的作用是限制同步目录“foo”和“baz”的make过程(在处理“foo”目录之前,需要等待“baz”目录处理完成)。

    强制目标:

    是指一个规则中,只有目标,没有“依赖”与“命令”,这样的规则本身没有什么意义,但这样的规则的目标可以作为其他规则的依赖。

    强制目标,作为其他规则的依赖时,这个规则是总被执行的,强制目标系统总会被认为是最新的

      clean: FORCE

      rm $(objects)

      FORCE:

    空文件目标:

      空目标,是指目标是一个文件,且是一个“空文件”,文件只是用来记录上一次执行此规则命令的时间。“命令部分”都会使用“touch”在完成所有命令之后来更新目标文件的时间戳,记录此规则命令的最后执行时间。make时通过命令行将此目标作为终极目标,当前目录下如果不存在这个文件,“touch”会在第一次执行时创建一个空的文件(命名为空目标文件名)。

      print: foo.c bar.c

      lpr -p $?

      touch print

    执行“make print”,当目标“print”的依赖文件任何一个被修改之后,命令“lpr –p $?”都会被执行,打印这个被修改的文件。

    多规则目标:

      一个目标文件在多个规则中出现,建立了多个依赖关系。执行时,以这个文件为目标的规则的所有依赖文件,将会被合并成此目标一个依赖文件列表,当其中任何一个依赖文件比目标更新时,make将会执行特定的命令来重建这个目标。

      对于一个多规则的目标,命令只能出现在一个规则中。如果多个规则同时给出重建此目标的命令,make将使用最后一个规则中所定义的命令,同时提示错误信息。

      #sample Makefile

      objects = main.o kbd.o command.o display.o

             insert.o search.o files.o utils.o

     

      edit : $(objects)

      cc -o edit $(objects)

     

      $(objects) : defs.h

      kbd.o command.o files.o : command.h

      display.o insert.o search.o files.o : buffer.h

      这样做的好处是:我们可以在源文件增加或者删除了包含的头文件以后不用修改已经存在的Makefile的规则,只需要增加或者删除某一个.o文件依赖的头文件。这种方式很简单也很方便。对于一个大的工程来说,这样做的好处是显而易见的。在一个大的工程中,对于一个单独目录下的.o文件的依赖规则建议使用此方式。当然规则中头文件的依赖描述规则也可以使用gcc自动产生。

     静态模式规则的多目标:

      静态模式规则存在多个目标,并且不同的目标可以根据目标文件的名字来自动构造出依赖文件。静态模式规则中的依赖文件必须是相类似的而不是完全相同的。

      首先,我们来看一下静态模式规则的基本语法:

      TARGETS ...: TARGET-PATTERN: PREREQ-PATTERNS ...

      COMMANDS

      举例:

      objects = foo.o bar.o

      all: $(objects)

      $(objects): %.o: %.c      //根据不同的目标构造出依赖文件的过程

      $(CC) -c $(CFLAGS) $< -o $@

      在使用静态模式规则时,指定的目标必须和目标模式相匹配,否则执行make时将会得到一个错误提示。如果存在一个文件列表,其中一部分符合某一种模式而另外一部分符合另外一种模式,这种情况下我们可以使用“filter”函数来对这个文件列表进行分类,在分类之后对确定的某一类使用模式规则。例如:

      files = foo.elc bar.o lose.o

      $(filter %.o,$(files)): %.o: %.c

      $(CC) -c $(CFLAGS) $< -o $@

      $(filter %.elc,$(files)): %.elc: %.el

      emacs -f batch-byte-compile $<

      我们通过另外一个例子来看一下自动环变量“$*”在静态模式规则中的使用方法:

      bigoutput littleoutput : %output : text.g

      generate text.g -$* > $@

      当执行此规则的命令时,自动环变量“$*”被展开为“茎”。在这里就是“big”和“little”。

    静态模式规则和隐含规则的区别:

      两者相同的地方都是用目标模式和依赖模式来构建目标的规则中的文件依赖关系,两者不同的地方是make在执行时使用它们的时机。

      隐含规则可被用在任何和它相匹配的目标上,在Makefile中没有为这个目标指定具体的规则、存在规则但规则没有命令行或者这个目标的依赖文件可被搜寻到。当存在多个隐含规则和目标模式相匹配时,只执行其中的一个规则。具体执行哪一个规则取决于定义规则的顺序。

      相反的,静态模式规则只能用在规则中明确指出的那些文件的重建过程中。不能用在除此之外的任何文件的重建过程中,并且它对指定的每一个目标来说是唯一的。如果一个目标存在于两个规则,并且这两个规则都定以了命令,make执行时就会提示错误。

      静态模式规则相比隐含模式规则由以下两个优点:

      1、不能根据文件名通过词法分析进行分类的文件,我们可以明确列出这些文件,并使用静态模式规则来重建其隐含规则。

      2、对于无法确定工作目录内容,并且不能确定是否此目录下的无关文件会使用错误的隐含规则而导致make失败的情况。当存在多个适合此文件的隐含规则时,使用哪一个隐含规则取决于其规则的定义顺序。这种情况下我们使用静态模式规则就可以避免这些不确定因素,因为静态模式中,指定的目标文件有明确的规则来描述其依赖关系和重建命令。

      静态模式规则对一个较大工程的管理非常有用。它可以对整个工程的同一类文件的重建规则进行一次定义,而实现对整个工程中此类文件指定相同的重建规则。

    模式规则:

      模式规则类似于普通规则。只是在模式规则中,目标名中需要包含有模式字符“%”(一个)。要注意的是:模式字符“%”的匹配和替换发生在规则中所有变量和函数引用展开之后,变量和函数的展开一般发生在make读取Makefile时

    因此,一个模式规则的格式为:

      %.o : %.c ; COMMAND...    //这个模式规则指定了如何由文件“N.c”来创建文件“N.o”,

      %.o : debug.h      //表示所有的.o文件都依赖于头文件“debug.h”)。

    所有模式匹配规则

      #sample GNUmakefile

      foo:

      frobnicate > foo

    如“make foo”,会执行上面的明确规则

      %: force

      @$(MAKE) -f Makefile $@

      force: ;

    如“make bar”, 没有明确规则的,就会执行上面的“所有模式匹配规则”

    双冒号规则

      双冒号规则就是使用“::”代替普通规则的“:”得到的规则。当同一个文件作为多个规则的目标时,双冒号规则的处理和普通规则的处理过程完全不同,不同之处在于,双冒号规则允许在多个规则中为同一个目标指定不同的重建目标的命令。

      1、双冒号规则中,当依赖文件比目标更新时。规则将会被执行。对于一个没有依赖而只有命令行的双冒号规则,当引用此目标时,规则的命令将会被无条件执行。而普通规则,当规则的目标文件存在时,此规则的命令永远不会被执行(目标文件永远是最新的)。

      2、当同一个文件作为多个双冒号规则的目标时。这些不同的规则会被独立的处理,而不是像普通规则那样合并所有的依赖到一个目标文件。这就意味着对这些规则的处理就像多个不同的普通规则一样。就是说多个双冒号规则中的每一个的依赖文件被改变之后,make只执行此规则定义的命令。

    我们来看一个例子,在我们的Makefile中包含以下两个规则:

      Newprog :: foo.c

             $(CC) $(CFLAGS) $< -o $@

      Newprog :: bar.c

             $(CC) $(CFLAGS) $< -o $@

      如果“foo.c”文件被修改,执行make以后将根据“foo.c”文件重建目标“Newprog”。而如果“bar.c”被修改那么“Newprog”将根据“bar.c”被重建。回想一下,如果以上两个规则为普通规时出现的情况是什么?(make将会出错并提示错误信息)

      当同一个目标出现在多个双冒号规则中时,规则的执行顺序和普通规则的执行顺序一样,按照其在Makefile中的书写顺序执行。

    自动产生依赖

      Makefile中,有时需要书写一些规则来描述一个.o文件和头文件的依赖关系。例如,如果在main.c中使用“#include defs.h”,那么我们可能就需要一个像下边那样的规则来描述当头文件“defs.h”被修改以后再次执行make,目标“main.o”应该被重建。

      main.o: defs.h

      这样,对于一个大型工程。当在源文件中加入或删除头文件后,也需要小心地去修改Makefile。这是一件非常费力的工作。Gcc通过“-M”选项来实现此功能,使用“-M”选项gcc将自动找寻源文件中包含的头文件,并生成文件的依赖关系。例如,如果“main.c”只包含了头文件“defs.h”,那么在Linxu下执行下面的命令:

      gcc -M main.c 

      其输出是:

      main.o : main.c defs.h

      当不需要在依赖关系中考虑标准库头文件时,对于gcc需要使用“-MM”参数。

      在旧版本的make中,使用编译器此项功能通常的做法是:在Makefile中书写一个伪目标“depend”的规则来定义自动产生依赖关系文件的命令。输入“make depend”将生成一个称为“depend”的文件,其中包含了所有源文件的依赖规则描述。Makefile中使用“include”指示符包含这个文件。

      在新版本的make中,推荐的方式是为每一个源文件产生一个描述其依赖关系的makefile文件。对于一个源文件“NAME.c”,对应的这个makefile文件为“NAME.d”。“NAME.d”中描述了文件“NAME.o”所要依赖的所有头文件。采用这种方式,只有源文件在修改之后才会重新使用命令生成新的依赖关系描述文件“NAME.o”

      我们可以使用如下的模式规则来自动生成每一个.c文件对应的.d文件,可以看到.d是依赖.c的,所以只有.c文件比较新时,才会更新.d文件:

      %.d: %.c

      $(CC) -M $(CPPFLAGS) $< > $@.$$$$;

      sed 's,($*).o[ :]*,1.o $@ : ,g' < $@.$$$$ > $@;

      rm -f $@.$$$$

      此规则的含义是:所有的.d文件依赖于同名的.c文件。

      第一行;使用c编译器自自动生成依赖文件($<)的头文件的依赖关系,并输出成为一个临时文件,“$$$$”表示当前进程号。如果$(CC)为GNU的c编译工具,产生的依赖关系的规则中,依赖头文件包括了所有的使用的系统头文件和用户定义的头文件。如果需要生成的依赖描述文件不包含系统头文件,可使用“-MM”代替“-M”。

      第二行;使用sed处理第二行已产生的那个临时文件并生成此规则的目标文件。这里sed完成了如下的转换过程。例如对已一个.c源文件。将编译器产生的依赖关系:

      main.o : main.c defs.h

      转成:

      main.o main.d : main.c defs.h

      这样就将.d加入到了规则的目标中,其和对应的.o文件文件一样依赖于对应的.c源文件和源文件所包含的头文件。当.c源文件或者头文件被改变之后规则将会被执行,相应的.d文件同样会被更新。

      第三行;删除临时文件。

    使用上例的规则就可以建立一个描述目标文件依赖关系的.d文件。我们可以在Makefile中使用include指示符将描述将这个文件包含进来。在执行make时,Makefile所包含的所有.d文件就会被自动创建或者更新。Makefile中对当前目录下.d文件处理可以参考如下:

      sources = foo.c bar.c

      sinclude $(sources:.c=.d)

      例子中,变量“sources”定义了当前目录下的需要编译的源文件。变量引用置换“$(sources : .c=.d)”的功能是根据变量“source”指定的.c文件自动产生对应的.d文件,并在当前Makefile文件中包含这些.d文件。.d文件和其它的makefile文件一样,make在执行时读取并试图重建它们。其实这些.d文件也是一些可被make解析的makefile文件。

      需要注意的是include指示符的书写顺序,因为在这些.d文件中已经存在规则。当一个Makefile使用指示符include这些.d文件时,应该注意它应该出现在终极目标之后,以免.d文件中的规则被是Makefile的终极规则。关于这个前面我们已经有了比较详细的讨论。

    规则的命令

      规则中除了第一条紧跟在依赖列表之后使用分号隔开的命令以外,其它的每一行命令行必须以[Tab]字符开始。多个命令行之间可以有空行和注释行。

      当使用默认的“/bin/sh”时,命令中出现的字符“#”到行末的内容被认为是注释。当然了“#”可以不在此行的行首,此时“#”之前的内容不会被作为注视处理。

    命令回显:加“@”表示,不会回显将要执行的命令

      make在执行命令行之前会把要执行的命令行输出到标准输出设备。我们称之为“回显”。如果规则的命令行以字符“@”开始,则make在执行这个命令时就不会回显这个将要被执行的命令。 如:

      @echo 开始编译XXX模块......

      执行时得到:

      开始编译XXX模块......

      如果在命令行之前没有字符“@”,那么,make的输出将是:

      echo开始编译XXX模块......

      开始编译XXX模块......

    命令的执行:

      规则中,当目标需要被重建时。每一行命令将在一个独立的子shell进程中被执行。因此,多行命令之间的执行是相互独立的。

      因此在一个规则的命令中,命令行“cd”改变目录不会对其后的命令的执行产生影响:

      foo : bar/lose

      cd bar; gobble lose > ../foo

    如果希望把一个完整的shell命令行书写在多行上,需要使用反斜杠()来对处于多行的命令进行连接,表示他们是一个完整的shell命令行:

      foo : bar/lose

      cd bar; 

      gobble lose > ../foo

     命令执行中的错误:

      通常;规则中的命令在运行结束后,make会检测命令执行的返回状态,如果返回成功,那么就启动另外一个子shell来执行下一条命令。

      一些情况下,规则中一个命令的执行失败并不代表规则执行的错误。例如我们使用“mkdir”命令来确保存在一个目录。为了忽略一些无关命令执行失败的情况,我们可以在命令之前加一个减号“-”(在[Tab]字符之后),来告诉make忽略此命令的执行失败。命令中的“-”号会在shell解析并执行此命令之前被去掉,shell所解释的只是纯粹的命令,“-”字符是由make来处理的。例如对于“clean”目标我们就可以这么写:

      clean:

        -rm  *.o

    其含义是:即使执行“rm”删除文件失败,make也继续执行。

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

      当不使用这种方式来通知make忽略命令执行的错误时,那么在错误发生时,由于先决条件不能建立,那么后续的命令将不会被执行。

      在发生这样情况时,我们也可以使用make的命令行选项“-k”或者“--keep-going”来通知make,在出现错误时不立即退出,而是继续后续命令的执行。

    make的递归:

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

      例如,当前目录下存在一个“subdir”子目录:

      subsystem:

      cd subdir && $(MAKE)

      其等价于规则:

      subsystem:

      $(MAKE) -C subdir

    第一个规则命令的意思是:进入子目录,然后在子目录下执行make。第二个规则使用了make的“-C”选项,同样是首先进入子目录而后再执行make。在make的递归调用中,需要了解一下变量“CURDIR”,此变量代表make的工作目录。当使用“-C”选项进入一个子目录后,此变量将被重新赋值。

    变量和递归

      在make的递归执行过程中,上层make可以明确指定将一些变量的定义通过环境变量的方式传递给子make过程。使用环境变量传递上层所定义的变量时,上层所传递给子make过程的变量定义不会覆盖子make过程所执行makefile文件中的同名变量定义。

      在GNU make中,实现此功能的指示符是“export”。当一个变量使用“export”进行声明后,变量和它的值将被加入到当前工作的环境变量中,如果使用命令行指定的变量(如命令“make CFLAGS +=-g”或者“make –e CFLAGS +=-g”)也会传递给子make程序。

      指示符“export”对此变量进行声明。格式如下:

      export VARIABLE ...

      当不希望将一个变量传递给子make时,可以使用指示符“unexport”来声明这个变量。格式如下:

      unexport VARIABLE ...

      一个不带任何参数的指示符“export”指示符:

      export    //含义是将此Makefile中定义的所有变量传递给子make过程。

      如果不需要传递其中的某一个变量,可以单独使用指示符“unexport”来声明这个变量。

      在新版本中,需要使用特殊目标“.EXPORT_ALL_VARIABLES”来代替“export”。它会被老版本的make忽略,只有新版本的make能够识别这个特殊目标。

      格式如下:

      .EXPORT_ALL_VARIABLES:

      VARIABLE1=var1

      VARIABLE2=var2      //其含义是将特殊目标“.EXPORT_ALL_VARIABLES”依赖中的所有变量全部传递给子make。

      当然也可以使用单独的“unexport”指示符来禁止一个变量的向下传递。在多级的make递归调用中,可以在中间的Makefile中使用它来限制上层传递来的变量再向下传递。

    调用深度"MAKELEVEL":

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

    Main目录下的Makefile清单如下:

      #maindir Makefile

      .PHONY :test

      test:

             @echo “main makelevel = $(MAKELEVEL)”

             @$(MAKE) –C subdir dislevel

      #subdir Makefile

      .PHONY : test

      test :

             @echo “subdir makelevel = $(MAKELEVEL)”

      在maindir 目录下执行“make test”。将显式如下信息:

      main makelevel = 0

      make[1]: Entering directory `/…../ subdir '

      subdir makelevel = 1

      make[1]: Leaving directory `/…../ subdir '

    在主控的Makefile中MAKELEVEL 为“0”,而在subdir的Makefile中,MAKELEVEL为“1”。

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

    命令行选项和递归

      在make的递归执行过程中。最上层(可以称之为主控)make的命令行选项“-k”、“-s”等会被自动的通过环境变量“MAKEFLAGS”传递给子make进程。传递过程中变量“MAKEFLAGS”的值会被主控make自动的设置为包含执行make时的命令行选项的字符串。如果在执行make时通过命令行指定了“-k”和“-s”选项,那么“MAKEFLAGS”的值会被自动设置为“ks”。子make进程在处理时,会把此环境变量的值作为执行的命令行参数,因此子make过程同样也会有“-k”和“-s”这两个命令行选项。

      同样,执行make时命令行中给定的一个变量定义(如“make CFLAGS+=-g”),此变量和它的值(CFLAGS+=-g)也会借助环境变量“MAKEFLAGS”传递给子make进程。可以借助make的环境变量“MAKEFLAGS” 传递我们在主控make所使用的命令行选项给子make进程。需要注意的是有几个特殊的命令行选项例外,他们是:“-C”、“-f”、“-o”和“-W”。这些命令行选项是不会被赋值给变量“MAKEFLAGS”的。

    Make命令行选项中一个比较特殊的是“-j”选项。在支持这个选项的操作系统上,如果给它指定了一个数值“N”(多任务的系统unix、Linux支持,MS-DOS不支持),那么主控make和子make进程会在执行过程中使用通信机制来限制系统在同一时刻(包括所有的递归调用的make进程,否则,将会导致make任务的数目数目无法控制而使别的任务无法到的执行)所执行任务的数目不大于“N”。另外,当使用的操作系统不能支持make执行过程中的父子间通信,那么无论在执行主控make时指定的任务数目“N”是多少,变量“MAKEFLAGS”中选项“-j”的数目会都被设置为“1”,通过这样来确保系统的正常运转。

    执行多级的make调用时,当不希望传递“MAKEFLAGS”的给子make时,需要在调用子make是对这个变量进行赋空。例如:

    subsystem:

    cd subdir && $(MAKE) MAKEFLAGS=

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

    执行make时可以通过命令行来定义一个变量,像上例中的那样;前边已经提到过,这种变量是借助环境“MAKEFLAGS”来传递给多级调用的子make进程的。其实真正的命令行中的 变量定义 是通过另外一个变量“MAKEOVRRIDES”记录的,在变量“MAKEFLAGS”的定义中引用了此变量,所以命令行中的变量定义被记录在环境变量“MAKEFLAGS”中被传递下去。当不希望上层make在命令行中定义的变量传递给子make时,可以在上层Makefile中把“MAKEOVERRIDES”赋空(MAKEOVERRIDES=)。但是这种方式通常很少使用,建议非万不得已您还是最好不使用这种方式(为了和POSIX2.0兼容,当Makefile中出现“.POSIX”这个特殊的目标时,在上层Makefile中修改变量“MAKEOVERRIDES”对子make不会产生任何影响)。另外,在一些系统中环境变量值的长度存在一个上限,一次当“MAKEFLAGS”的值超过一定长度时,执行过程可能会出现类似“Arg list too long”的错误提示。

    历史原因,在make中也存在另外一个和“MAKEFLAGS”相类似的变量“MFLAGS”。现行版本中保留此变量的原因是为了和老版本兼容。和“MAKEFLAGS”不同点是:1. 此变量在make的递归调用时不包含命令行选项中的变量定义部分(就是说此变量的定义没有包含对“MAKEOVERRIDES”的引用);2. 此变量的值(除为空的情况)是以“-”开始的,而“MAKEFLAGS”的值只有在长命令选项格式(如:“--warn-undefined-variables”)时才以“-”开头。传统的此变量一般被明确的使用在make递归调用时的命令中。像下边那样:

      subsystem:

      cd subdir && $(MAKE) $(MFLAGS)

    在现行的make版本中,变量“MFLAGS”已经成为一个多余部分。在书写和老版本make兼容的Makefile时可能需要这个变量。当然它在目前的版本上也能够正常的工作。

    在某些特殊的场合,可能需要为所有的make进程指定一个统一的命令行选项。比如说需要给所有的运行的make指定“-k”选项。实现这个目的,我们可以在执行make之前设置一个系统环境变量(存在于当前系统的环境中)“MAKEFLAGS=k”,或者在主控Makefile中将它的值赋为“k”。注意:不能通过变量“MFLAGS”来实现。

    make在执行时,首先将会对变量“MAKEFLAGS”的值(系统环境中或者在Makefile中设置的)进行分析。当变量的值不是以连字符(“-”)开始时,将变量的值按字分开,字之间使用空格分开。将这些字作为命令行的选项对待(除了选项“-C”、“-f”、“-h”、“-o”和“-W”以及他们的长格式,如果其中包含无效的选项不会提示错误)。

    最后需要说明的是:将“MAKEFLAGS”设置为系统环境变量的做法是不可取的!因为这样一旦将一些调试选项或者特殊选项作为此变量值的一部分,在执行make时,会对make的正常执行产生潜在的影响。例如如果变量“MAKEFLAGS”中包含选项“t”、“n”、“q”这三个的任何一个,当执行make的结果可能就不是你所要的。建议大家最好不要随便更改“MAKEFLAGS”的值,更不要把它设置为系统的环境变量来使用。否则可能会产生一些奇怪甚至让你感到不解的现象。

    make  -w选项

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

    例如,在目录“/u/gnu/make”目录下执行“make -w”,将会看到如下的一些信息:

      在开始执行之前我们将看到:

      make: Entering directory `/u/gnu/make'.

      而在完成之后我们同样将会看到:

      make: Leaving directory `/u/gnu/make'.

      通常,选项“-w”会被自动打开。在主控Makefile中当如果使用“-C”参数来为make指定一个目录或者使用“cd”进入一个目录时,“-w”选项会被自动打开。主控make可以使用选项“-s”(“--slient”)来禁止此选项。另外,make的命令行选项“--no-print-directory”,将禁止所有关于目录信息的打印。

    定义命令包

    使用“define”定义的一组命令称为一个命令包。定义一个命令包的语法以“define”开始,以“endef”结束,例如:

      define run-yacc

      yacc $(firstword $^)

      mv y.tab.c $@

      endef

      需要说明的是:使用“define”定义的命令包中,命令体中变量和函数的引用不会展开。命令体中所有的内容包括“$”、“(”、“)”等都是变量“run-yacc”的定义。它和c语言中宏的使用方式一样。

    例子中,命令包中第一个命令是对引用它所在规则中的第一个依赖文件运行yacc程序。yacc程序总是生成一个命名为“y.tab.c”的文件。第二行的命令就是把这个文件名改为规则目标的名字。

    命令包是使用一个变量来表示的,因此我们就可以按使用变量的方式来使用它。当在规则的命令行中使用这个变量时,命令包所定义的命令体就会对它进行替代。由于使用“define”定义的变量属于递归展开式变量,因此,命令包中所有命令中对其它变量的引用,在规则被执行时会被完全展开。例如这样一个规则:

      foo.c : foo.y

      $(run-yacc)

    此规则在执行时,我们来看一下命令包中的变量的替换过程:1. 命令包中的“$^”会被“foo.y”替换;2. “$@”被“foo.c”替换。大家应该对“$<”和“$@”不陌生吧。

    当在一个规则中引用一个已定义的命令包时,命令包中的命令体会被原封不动的展开在引用它的地方(和 c语言中的宏一样)。这些命令就成为规则的命令。因此我们也可在定义命令包时使用前缀来控制单独的一个命令行(例如“@”,“-”和“+”)。例如:

      define frobnicate

           @echo "frobnicating target $@"

           frob-step-1 $< -o $@-step-1

           frob-step-2 $@-step-1 -o $@

      endef

    此命令包的第一行命令执行前不会被回显,其它的命令在执行前都被回显。

    另一方面,如果一个规则在引用此命令包之前使用了控制命令的前缀字符。那么这个前缀字符将会被添加到命令包定义的每一个命令行之中。例如:

      frob.out: frob.in

      @$(frobnicate)

    这个规则执行时不会回显任何要执行的命令。

    空命令

    有时可能存在这样的一个需求,需要定义一个什么也不做的规则(不需要任何执行的命令)。前面已经有过这样的用法。这样的规则,只有目标文件(可以存在依赖文件)而没有命令行。像这样定义: 

      target: ;

      这就是一个空命令的规则,为目标“target”定义了一个空命令。也可以使用独立的命令行格式来定义,需要注意的是独立的命令行必须以[Tab]字符开始。一般在定义空命令时,建议不使用命令行的方式,因为看起来空命令行和空行在感觉上没有区别。

    大家会奇怪为什么要定义一个没有命令的规则。其唯一的原因是,空命令行可以防止make在执行时试图为重建这个目标去查找隐含命令。这一点它和伪目标有相同之处。使用空命令的目标时,需要注意:如果需要实现一个不是实际文件的目标,我们只是需要通过使用这个目标来完成对它所依赖的文件的重建动作。首先应该想到伪目标而不是空命令目标。因为一个实际不存在的目标文件的依赖文件,可能不会被正确重建。

  • 相关阅读:
    [Clr via C#读书笔记]Cp4类型基础
    [Clr via C#读书笔记]Cp3共享程序集和强命名程
    [Clr via C#读书笔记]Cp2生成打包部署和管理应用程序和类型
    [Clr via C#读书笔记]Cp1CLR执行模型
    试用Markdown来写东西
    字符编码的总结
    常去的网站
    Click Once使用总结
    【LevelDB源码阅读】Slice
    【程序员面试金典】面试题 01.05. 一次编辑
  • 原文地址:https://www.cnblogs.com/jiangzhaowei/p/4273160.html
Copyright © 2020-2023  润新知