Makefile系列之规则

规则

  • 语法

    TARGETS:PREREQUISITES (; COMMAND)
    COMMAND

    TARGETS : 目标,可以是空格分开的多个文件,也可使用通配符;
    PREREQUITITES : 依赖,根据依赖更新后是否需要重建目标,将依赖分为两种,用“|”进行分割,“需要|不需要”,把更新后不需要重建目标的依赖称为“order-only”依赖;
    COMMAND : 有两种风格,可以与目标和依赖在同一行,用分号分割,或者在下一行,并必须以【Tab】字符开始;

  • 目录搜索
    VPATH:是个变量,指定Makefile中所有文件的搜索路径,定义时通过“:”将多个目录分开;
    vpath:是个关键字,可以为不同类型的文件指定不同的搜索路径,使用时通过模式字符“%”进行匹配,但是模式匹配所指定的文件仅仅指在Makefile中出现的相应类型的文件,(如:%.h 并不包含源文件中包含的头文件所在的路径,其需要在编译时通过 gcc 的 -I 选项进行指定);
    GPATH:变量,如果目标是通过目录搜索得到的完整路径名,且该目标所在的目录出现在GPATH所定义的目录列表中,那么重建目标时将在该目录下进行,而不是在Makefile所在的目录下进行;
    -Iname : 若在依赖文件列表中存在这种格式的依赖文件,make将根据name在当前系统中依次搜索可提供的共享库、静态库。
    搜索时的搜寻顺序是: Makefile文件所在的目录 => VPATH、vpath指定搜索目录 => 对于用-I方式引入的库文件依赖,还会搜索库文件的默认目录/lib、/usr/lib、PREFIX/lib;
    通过搜索得到的文件名可能是包含路径信息的完整文件名,可能并不是规则中列出的文件名,为了保证规则中能正确的使用依赖文件,规则的命令行中必须使用自动化变量来代表依赖文件;

  • 特殊目标
    (1). 强制目标
    它的目标不是一个存在的文件,并且规则没有命令或依赖。
    clean:
    rm *.o temp
    特点:
    i). 规则的目标不是一个真正的文件;
    ii). clean可能与当前Makefile目录中的实际文件名产生冲突;
    iii). 此时目标总被认为被更新过,是最新的,因此该规则的命令永远也不会被执行;
    iv). 当该目标作为某个规则的依赖时,由于依赖总被认为时最新的,则作为目标的规则中所定义的命令总会被执行;
    v). “FORCE” 是一个强制目标,但是在GNU make中剑灵避免使用,尽量通过 .PHONY 的方式来代替;
    (2). 伪目标
    伪目标不是一个真正的文件名,可以没有命令或依赖,也可以都有,通过指定规则的目标来完成某一功能的一系列命令,通过 .PHONY 进行定义,
    .PHONY:clean
    clean:
    rm *.o temp
    特点:
    i). 避免Makefile中的目标和工作目录下的实际文件名冲突;
    ii). 提高make执行效率,对于声明为伪目标的规则,make执行时不会试图去为它查找隐含规则来创建它;
    iii). 无论目标文件是否存在,其规则中的命令总会被无条件执行。一般不将伪目标作为一个目标的依赖,因为该规则每次在检查时,伪目标的命令都会被执行;
    iv). 伪目标可以有自己的依赖(可以是一个或者多个文件或伪目标)。如果一个伪目标作为另外一个伪目标的依赖时,作为依赖的伪目标会被认为时目标伪目标的子例程来处理,即其时目标伪目标必须要执行的部分;
    v). 伪目标可以实现并行处理,而替代shell语句中的for语句(它是顺序执行);
    (3). 空目标文件
    是伪目标的一个变种,这个目标可以使一个存在的文件,但文件的内容我们并不关心,它只是用来记录上一次执行此命令的时间,通常是在规则的所有命令行的最后使用touch命令来更新目标文件的时间戳,以记录此规则命令的最后执行时间;

  • 多规则目标 与 多目标规则
    (1). 一个文件可以作为多个规则的目标(多个规则中只能有一个规则定义命令,即重建此目标的规则只能出现一个规则中)否则使用最后一个规则中定义的命令,并同时提示错误。但若目标的任何一个规则都没有定义重建此目标的命令,make会寻找合适的隐含规则来重建此目标;
    (2). 普通的多目标规则,是指在一个规则中有多个目标,该规则在处理时,将每一个目标作为一个独立的规则来处理,所以多个目标就对应多个独立的规则,且各个规则都有各自的命令行,只不过各个规则的命令行可能相同(此处区别于模式规则中的多目标规则);

  • 模式规则
    (1). 模式规则类似于普通规则,只是在模式规则中,目标名中需要包含有模式字符“%”,包含有模式字符“%”的目标被用来匹配一个文件名,“%”可以匹配任何非空字符串。模式规则中依赖文件中可以不包含模式字符“%”,其含义是所有符合目标模式的目标文件都依赖于指定的文件;
    (2). 一个模式规则可以存在多个目标,多目标的模式规则和普通的模式规则不同:普通的多目标规则处理是将每一个目标作为一个独立的规则来处理,所以多个目标就对应多个独立的规则(且各个规则有自己的命令行);对于多目标模式规则来说,所有的目标共同拥有依赖文件和规则的命令行,即当文件符合多个目标模式中的任何一个时,规则定义的命令行就有可能会被执行,且执行后,规则不会再去检查是否需要重建符合其它模式的目标,表明多目标的模式规则在make时是被作为一个整体来处理的;如: (该规则是一个隐含模式规则)

    %.o:%.c
        $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
    


    i). 模式字符“%”的匹配和替换发生在规则中所有变量和函数引用展开之后,变量和函数的展开一般发生在make读取Makefile时,模式规则中的“%”的匹配和替换则发生在make执行时;
    ii). 当一个目标文件同时符合多个不同的目标模式时,make将会把第一个目标匹配的模式规则作为重建它的规则;
    iii). Makefile中指定的模式规则会覆盖隐含模式规则,即使用时明确规则永远优先于隐含规则;
    iv). 当目标模式中包含斜杠(目录部分),在进行目标文件匹配时:文件名中包含的目录字符串在匹配之前被移除只进行基本文件名的匹配;匹配成功后,再将目录部分加入到匹配之后的字符串之前形成“茎”; 依赖文件的产生: 首先使用“茎”中的非目录部分替代依赖文件中的模式字符“%”,之后再将目录部分加入到形成的依赖文件名之前,构成依赖文件的全路径名。如模式规则: e%t: c%r,则目标 src/eat 对应的茎为 “src/a”,对应的依赖应是 “src/car” ;

  • 静态模式规则
    静态模式规则: 规则存在多个目标,并且不同的目标文件可以根据目标的名字来自动构造出依赖文件。使用从目标模式中抽取的部分字符串来替代依赖模式中的相应部分来产生对应目标的依赖文件。
    TARGETS( …):TARGET-PATTERN:PREREQ-PATTERNS (…)
    TARGETS : 目标,可以是多个
    TARGET-PATTERN : 目标模式
    PREREQ-PATTERNS: 依赖模式
    使用静态模式规则时,指定的目标必须和目标模式相匹配,否则执行make时将产生错误提示,此时可以使用filter函数来提前对文件进行分类过滤。如:

    objects = foo.o bar.o
    all: $(objects)
    $(objects):%.o:%.c
        $(CC) -c $(CFLAGS) $< -o $@
    
  • 隐含规则
    (1). 隐含规则为make提供了重建一类目标文件的通用方法,隐含规则被用在任何可以和他匹配的目标上,如Makefile中没有为这个目标指定具体的规则,或者存在规则但没有命令行来指定重建操作,或者目标的依赖文件可以被搜索到,均可使用隐含规则来重建目标,当存在多个隐含规则和目标模式相匹配时,只执行其中的一个规则(取决于规则的定义顺序);
    i). 隐含规则和静态模式规则都是用目标模式和依赖模式来构建目标的规则中的文件依赖关系,区别在于make在执行时使用他们的时机不同,隐含规则被用在任何可以和他匹配的目标上,而静态模式规则只能用在规则中明确指出的那些目标文件的重建过程;
    ii). 给目标文件指定明确的依赖文件并不会影响隐含规则的搜索(只要规则中不包含命令,依然会被使用隐含规则来重建);
    iii). 当不想让make为一个没有命令行的规则中的目标搜索隐含规则时,我们需要使用空命令来实现【见“命令”文档】;
    iv). 可以使用“-r”或“–no-builtin-rules”命令行参数来取消内嵌的隐含规则,或者使用“-R”或“–no-builtin-variables”命令行参数来取消make的隐含变量,“-R”同时打开“-r”,因为没有了隐含变量隐含规则也将失去意义;
    v). 在gcc编译源文件时,如果没有指定“-c”选项,gcc会在编译完成之后调用“ld”链接称为可执行文件;
    vi). 隐含规则中所使用的(隐含)变量都是预定义的,分为两类:代表一个程序的名字,代表执行这个程序使用的参数;
    vii). 如果一个目标需要一系列隐含规则才能完成它的创建,我们就把这个系列称为一个链。在make过程中,会自动将重建目标过程中需要依赖的中间过程文件加入到依赖关系列表中,且中间过程文件和那些明确指定的文件在规则中的地位完全相同,但是中间过程文件在make执行结束后会被删除;
    viii). 默认的,在Makefile中明确提及的所有文件都不被作为中间过程文件来处理,但是在Makefile中可以使用特殊目标 “.INTERMEDIATE” 来指定将哪些文件作为中间过程文件来处理,即使这些文件在Makefile中被明确提及。此外,也可以明确指出希望保留的中间过程文件,方法时使用特殊目标“.SECONDARY”来指出这些文件,或者将这些文件作为特标“.PRECIOUS”的依赖,来取消在make重建目标终止时,对该目标执行删除动作;
    ix). 同一个隐含规则在一个链中只能出现一次,否则将会导致无限循环的问题;
    x). make的隐含规则表中,所有可用的优化规则处于首选地位;
    (2). 修改内嵌隐含规则
    i). 重建内嵌隐含规则时,需要使用相同的目标和依赖模式,命令可以不同,这样具有相同目标和依赖模式的内嵌规则将被新定义的规则替代;
    ii). 取消内嵌隐含规则时,需要定义一个和隐含规则具有相同目标和依赖的规则,但这个规则没有命令行;
    (3). 隐含规则搜索算法
    这里写图片描述

  • 万用规则
    当模式规则的目标只是一个模式字符“%”时(他可以匹配任何文件名),称为万用规则,万用规则在书写Makefile时非常有用,但他会影响make的执行效率,因为make在执行时将会使用万用规则来试图重建其他规则的目标和依赖文件。若存在万用规则,make会对Makefile中提及的文件通过所有可能的隐含规则来创建它,即使这个文件已经是源文件,虽然最后这些文件不可能被创建,但该过程依然会进行,导致make执行效率会很低。
    为了避免万用规则带来的效率低下的问题,对万用规则加以限制,有两种方式,在定义时要么将规则定义为最终规则,否则若不进行指定,那么该万用规则就是非最终的万用规则,在定义万用规则时,必须选择其中一种方式,如下:
    (1). 将万用规则时将其设置为最终规则,定义时使用双冒号规则。作为最终规则,此规则只有在它的依赖文件存在时才能被应用,即使它的依赖可以由隐含规则创建也不行;
    如何设置为最终规则呢?双冒号规则都是最终规则吗?(待解释)
    最终的万用规则优先级高于明确的模式规则吗?(待解释)
    (2). 如果万用规则没有被定义为最终规则,那么它就是一个非最终规则,需要定义一个特殊的内嵌哑模式规则给出如何重建某一类文件,避免使用非最终的万用规则通过隐含规则进行无意义的尝试创建操作。内嵌哑模式规则格式:没有依赖,也没有命令行,在make的其它场合被忽略。我们可以为所有的 make 可识别的后缀创建一个哑模式规则。此外,非最终的万用规则不会被用来创建那些符合某一明确模式规则的目标和依赖文件。即是说如果在Makefile中对一个文件存在匹配的模式规则(非万用规则)和非最终的万用规则,那么对这个文件其重建时只会是它所匹配的这个模式规则,而非这个非最终的万用规则。这样做是为了避免make执行时使用非最终的万用规则来重建源文件情况的发生;
    注:
    i). 非最终的万用规则是使用“:”定义的普通规则;
    ii). 对于内嵌哑模式规则,其只在非最终的万用规则下才有意义,对最终的万用规则,由于对诸如源文件的文件,其本身就不会被调用隐含规则进行重建,因此内嵌哑模式规则定义了也没有意义;
    iii). 非最终的万用规则也可以用来实现Makefile的重载;

  • 缺省规则
    当make在执行过程中无法为一个文件找到合适的重建规则时(即Makefile中没有给出重建它的明确规则,同时也没有合适可用的隐含规则),那么make就使用这个规则来重建它。就是说,当需要创建的目标文件没有可用的命令时,就执行这个缺省规则;
    (1). 定义缺省规则,可以使用最终的万用规则,如下所示。例如当我们调试Makefile时(可能一些源文件还没有完成),我们关心的是Makefile中所有的规则是否可以正确执行,而源文件的内容却不需要关心,基于这一点我们可以使用(和源文件同名的)空文件在Makefile中定义如下规则:

    %::
        touch $@
    

    则在执行make过程中,对所有不存在的.c文件,将会使用 “touch” 命令创建一个空的源文件;
    (2). 定义缺省规则的方式也可以使用伪目标 “.DEFAULT”,对如上的情况可以使用如下方式来代替:

    .DEFAULT:
        touch $@
    

    对于没有指定命令行的伪目标 “.DEFAULT”,其含义是取消前面所有使用“.DEFAULT” 指定的缺省执行命令同样也可以通过给 .DEFAULT 定义空命令的方式,让这个缺省规则不执行任何命令;
    (3). 缺省规则也可以用来实现在一个Makefile中重载另一个Makefile文件
    : 没有指定命令行的规则和空命令规则功能是不同的,对于没有指定命令行的规则,make执行时会为其目标文件查找可用的隐含规则来进行重建;而空命令规则,是含有命令行的,只不过命令行为空,make执行时,不会为其调用隐含规则来进行重建;

  • 双冒号规则
    普通规则使用单冒号“:”定义,而双冒号规则使用双冒号“::”定义,它允许在多个规则中为同一个目标指定不同的重建目标的命令(即多规则目标)。
    特性:
    i). 对于同一个目标其规则只能是一种,要么全是单冒号的普通规则,要么全是双冒号规则,不予许同时存在两种规则;
    ii). 对于没有依赖的只有命令行的双冒号规则,当引用此目标时,该规则的命令行将会被无条件执行(与伪目标类似)但是普通规则下(与强制目标类似),目标永远被认为时最新的,命令永远不会被执行;
    iii). 一个目标有多个双冒号规则时,其多个规则的依赖并不会合并,每个依赖文件被改变后,make仅会执行此依赖所在规则中命令,其它的规则并不会被执行;
    iv). 当同一个目标出现在多个双冒号规则中时,规则的执行顺序与普通规则一样,按照其在Makefile中的书写顺序执行;
    v). 一般双冒号规则都要定义命令,如果一个双冒号规则没有定义命令,在执行规则时将为其目标自动查找隐含规则;

  • 后缀规则
    后缀规则是一种古老的定义隐含规则的方式,在新版本中使用模式规则作为对它的替代,模式规则相比后缀规则更加清晰明了,在当前版本中保留它的原因是为了能够兼容旧的Makefile文件。后缀规则有两种类型:双后缀和单后缀
    i). 双后缀规则定义一对后缀,目标文件的后缀和依赖的后缀;单后缀规则之定义一个后缀,此后缀是源文件名的后缀它可以匹配任何文件;
    ii). 判断一个后缀规则时单后缀还是双后缀,是通过判断规则的目标中可被识别的后缀的数量,若是仅有一个就是单后缀,两个的话就是双后缀;
    iii). 一个后缀规则中不存在任何依赖文件,否则将被作为一个普通规则来对待;
    iv). 一个没有命令行的后缀规则是没有任何意义的,达不到取消后缀规则的目的,后缀规则的作用仅仅是将规则加入数据库中;
    v). 可识别的后缀是指特殊目标 “.SUFFIXES” 所有依赖的名字,通过给特殊目标 .SUFFIXES 添加依赖来增加一个可被识别的后缀,如” .SUFFIXES: .hack .win”,其作用是将后缀 .hack 和 .win加入可识别后缀列表。此外可识别后缀列表支持被重置,方法是通过一个没有依赖的 .SUFFIXES 特殊目标来实现,然后再通过该特殊目标依次添加想要定义的可识别后缀即可;

  • 自动产生依赖
    (1). 在Makefile中需要书写.o文件与头文件间的依赖关系,而现代的C编译器提供了通过查找源文件中的 “#include”来自动产生这种依赖关系的功能,通过使用GCC的 “-M”选项来实现,而此时 “gcc -M” 的选项其输出结果中也包含对标准头文件的依赖关系描述,可以通过 “-MM” 来实现去掉依赖关系中的标准库头文件。
    (2). 在旧版本的Makefile中,使用编译器此功能的通常做法是在Makefile中定义一个伪目标“depend”,来定义自动产生依赖关系文件的命令,先使用 make depend 来生成该depend文件,然后在 Makefile 中使用 include指示符包含这个文件。
    (3). 在新版本中的 make 中,推荐的方式是为每一个源文件产生一个描述其依赖关系的 makefile 文件,对于一个源文件 NAME.c 其对应的描述依赖关系的 makefile 文件为 NAME.d,即 NAME.d 中描述了 NAME.o 所要依赖的所有头文件。

  • makefile 文件的重建
    (1). include (关键字)告诉 make 暂停读取当前的 Makefile,转去读取 include 指定的一个或多个Makefile,完成以后在返回继续读取当前的 Makefile
    (2). makefile 文件的相互包含使用关键字 “include”,“-”符号作用来忽略操作的错误,让make继续执行,考虑与其它make的兼容,可以使用 “sinclude” 来代替 “-include”
    (3). 当环境中定义了 “MAKEFILES” 环境变量,make执行时首先将此变量的值作为需要读入的Makefile文件,作用与使用 include 类似,但该环境变量中的目标不会作为make执行的中级目标,且如果其中定义的文件无法找到也不会产生错误,该变量主要用在 make 的递归调用中的通信,实际使用中很少定义该变量
    (4). make在执行时,要读取的多个Makefile文件时,在对文件进行解析执行之前都会将变量追加到“MAKEFILE_LIST”中,即该变量中的最后一个子即是当前正在处理的Makefile文件名
    (5). 在实际应用中,我们会明确给出Makefile文件,而并不需要make自动重建它们,但是make每次执行时总会自动的尝试重建那些已经存在的Makefile文件,如果需要处于效率考虑,可以为Makefile文件定义一个以Makefile文件为目标的空命令规则,来避免make查找重建Makefile的隐含规则。
    (6). 如果使用一个没有依赖的只有命令的双冒号规则去更新Makefile文件(Makefile文件作为规则的目标),那么执行make时,这个Makefile文件总会被无条件更新,从而使得make的执行陷入一个死循环,为了防止这种情况的发生make在遇到一个目标时Makefile文件的双冒号规则时,将忽略对这个规则的执行。
    (7). 对于Makefile文件中存在以Makefile文件为目标的规则,如果在使用 -t, -n, -q等命令来仅仅更新时间戳时,对于重建Makefile文件的规则是无效的,即这些规则任然会被执行,即Makefile文件任然会被重建,如果想要避免这种结果,不重建Makefile文件,可以在执行make的命令行中,将Makefile文件作为最终目标,则Makefile中更新Makefile的规则也不会被执行。
    (8). 对于需要互相包含的两个Makefile文件,若两个文件中存在相同的目标但规则命令不同的规则时,将会产生错误,那么解决方法是通过万用规则来实现重载,即在万用规则的命令行中去执行需要包含的另一个Makefile文件。
    (9). make如何解析Makefile文件,分两个阶段
    i). 读取所有的Makefile文件,内建所有的变量,明确规则和隐含规则,建立所有的目标和依赖之间的依赖关系结构链表
    ii). 根据第一阶段建立的依赖关系结构链表来决定哪些目标需要更新,并使用对应规则进行重建目标对于变量和函数,如果在make执行的第一阶段被展开,那么称展开时“立即”的,其它的展开被称为时“延后”的,即这些变量和函数时直到后续某些规则需要使用时,或者在make处理的第二阶段他们才会被展开

  • 通配符使用问题
    在规则中,通配符会被自动展开,但在变量的定义和函数引用时将失效,此时需要使用函数“wildcard”函数,
    通配符何时由make解析,何时由shell解析 ?(待补充)

猜你喜欢

转载自blog.csdn.net/GuoSenZQ/article/details/81637645