Makefile使用(6)-隐含规则

在使用makefile过程中,会有很多隐含规则,这是一种惯例,不需要我们显示写出来,make会按照这种惯例来运行。例如把.c文件编译成.o文件,这种规则你根本就不用写出来,make会自动推导。隐含规则会使用一些系统变量,我们可以通过改变系统变量来定制隐含规则的运行参数。比如CFLAGS可以控制编译时的编译器参数。

使用隐含规则

foo : foo.o bar.o
    cc -o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)

这个makefile中并没有写下如何生成foo.obar.o这两个目标的规则和命令。因为make的隐含规则功能会自动推导这两个目标的依赖和生成命令。

make会在自己的隐含规则库中搜索可以用的规则,如果找到就是用,找不到就报错。上面的例子中make调用的隐含规则是,把.o目标的依赖文件置为.c文件,并使用C的编译命令cc -c $(CFLAGS) .c来生成.o目标。也就是说没有必要写成如下:

foo.o : foo.c
    cc -c foo.c $(CFLAGS)
bar.o : bar.c
    cc -c bar.c $(CFLAGS)

隐含规则在规则库中是有其顺序的,越靠前的越经常使用。foo.o : foo.p这条规则,依赖文件foo.p(Pascal程序源文件)有可能变得没有意义,如果目录下存在foo.c,那么隐含规则会一样生效,通过foo.c调用C的编译器生成foo.o文件。因为在隐含规则中Pascal的规则出现在C的规则之后。如果确实不希望任何隐含推导,就不要只写出依赖规则而不写命令。

隐含规则速览:make内建了很多隐含规则,如果不明确写出规则,make就会在这些规则中搜索需要的规则和命令。当然,我们可以使用make的参数-r--no-builtin-rules选项来取消所有的预置的隐含规则。但是即使使用-r参数,某些隐含规则还是会生效,因为很多隐含规则使用了后缀规则来定义的,所以只要隐含规则中有后缀列表,那么隐含规则就会生效。默认的后缀列表:.out.a.ln.o.c.cc.C.p.f.F.r.y.l.s.S.mod.sym.def.h.info.dvi.tex.teinfo.w.ch.web.sh.elc.el

一些常用的隐含规则:

  • 编译C程序的隐含规则:<n>.o的目标的依赖会自动推导为<n>.c,并且其生成命令是$(CC) -c $(CPPFLAGS) $(CFLAGS)
  • 编译C++程序的隐含规则:<n>.o的目标的依赖目标会自动推导为<n>.cc<n.C>,且生成命令为$(CXX) -c $(CPPFLAGS) $(CFLAGS)
  • 链接Object文件的隐含规则:<n>目标依赖于<n>.o,通过运行C的编译器来运行链接程序生成(一般是ld),其生产命令是$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)。这个规则只有一个源文件的工程有效,同时也对多个Object文件有效。
x : y.o z.o
# 如果 x.c y.c z.c 都在,隐含规则将执行如下命令
cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -f z.o

如果没有一个源文件(如x.c)和你的目标名字(x)相关联,最好写出自己的生产规则,不然隐含规则会报错。

隐含规则使用的变量:隐含规则中,基本上都是使用了一些预置的变量。可以在makefile中改变这些变量,或在make的命令行中传入这些值,或在环境变量中设置这些变量的值。例如$(CC) -c $(CFLAGS) $(CPPFLAGS),make默认使用编译命令cc,如果把变量$(CC)重定义为gcc,把变量$(CFLAGS)重定义为-g,那么隐含规则的命令会以gcc -c -g $(CPPFLAGS)来执行。隐含规则中使用的变量可以分为两类,一种是命令相关的,如CC;一种是参数相关的,如CFLAGS

关于命令的变量:

  • AR:函数库打包程序,默认命令是ar
  • AS:汇编语言程序,默认命令是as
  • CC:C语言编译程序,默认命令是cc
  • CXX:C++语言编译程序,默认命令是g++
  • CO:从RCS文件中扩展文件程序,默认命令是co
  • CPP:C程序的预处理器(输出是标准输出设备),默认命令是$(CC) -E
  • FC:Fortran和Ratfor的编译器和预处理器,默认命令是f77
  • GET:从SCCS文件中扩展文件的程序,默认命令是get
  • LEX:Lex方法分析其程序(针对于C或Ratfor),默认命令是lex
  • PC:Pascal语言编译程序,默认命令是pc
  • YACC:Yacc文法分析器(针对于C程序),默认命令是yacc
  • YACCR:Yacc文法分析器(针对于Ratfor程序),默认命令是yacc -r
  • MAKEINFO:转换Texinfo源文件(.texi)到Info文件程序,默认命令是makeinfo
  • TEX:从TEX源文件穿件 Tex DVI文件的程序,默认命令是tex
  • TEXI2DVI:从Texinfo源文件穿件Tex DVI文件的程序,默认命令是texi2dvi
  • WEAVE:转换Web到Tex的程序,默认命令是weave
  • CWEAVE:转换C Web 到Tex的程序,默认命令是cweave
  • TANGLE:转换C Web到C,默认命令是ctangle
  • RM:删除文件命令,默认命令是rm -f

关于参数的变量,下面这些变量都是和上面命令相关的参数,如果没有指明默认值,其默认值都是空。

  • ARFLAGS:函数库打包程序AR命令的参数,默认值是rv
  • ASFLAGS:汇编语言编译器参数
  • CFLAGS:C语言编译器参数
  • CXXFLAGS:C++语言编译器参数
  • CPPFLAGS:C预处理器参数
  • LDFLAGS:链接器参数(如ld
  • LFLAGS:Lex文法分析器参数
  • YFLAGS:Yacc文法分析器参数

隐含规则链:有的时候一个目标可能引起一系列的隐含规则,比如.o文件的生成,如果.c文件存在,那么就直接调用C的编译器的隐含规则,如果.c文件不存在,但有.y文件存在,那么Yacc的隐含规则会被调用生成.c文件,再接着调用C编译的隐含规则生成.o文件。我们称.c文件叫做中间目标。对于中间目标,它和一般目标有两个不同点:一是除非中间目标不存在,才会引发中间规则,二是只要目标成功生成,所产生的所有中间目标文件都会被rm -f删除。

通常一个被makefile指定称目标或依赖目标的文件不能被当做中间目标,除非显示的使用伪目标.INTERMEDIATE来强制声明,如.INTERMEDIATE: mid。也可以阻止make自动删除中间文件,使用伪目标.SECONDARY来强制声明,如.SECONDARY : mid。在隐含规则链中,进制同一个目标出现两次或以上,防止make自动推导时出现无线递归问题。

定义模式规则:可以使用模式规则来定义一个隐含规则,一个模式规则就好像一个一般的规则,只是在模式规则中,目标的定义需要%字符。%表示一个或多个任意字符。在依赖目标中同样可以使用%,只是依赖目标中%取值取决于其目标。需要注意的是,%的展开发生在变量和函数展开之后,变量和函数的展开发生在make载入makefile时,而模式规则中的%则发生在运行时。

  • 规则介绍:模式规则中,至少在规则的目标定义中要包含%,否则就是一般的规则。目标中的%定义表示对文件名的匹配,%表示任意长度的非空字符串。

    %.o : %.c; <command ...>;
    

    其含义是指出了怎么从所有的.c文件生成相应的.o文件的规则。

  • 规则模式示例:下面这个例子把所有的.c文件都编译成.o文件:

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

    其中$@表示所有的目标的值,$<表示所有依赖目标的值。

  • 自动化变量,在上述的规则中,目标和依赖文件都是一系列的文件。自动变量可以在每一次对模式规则解析时,可以是不同的目标和依赖文件。所谓自动化变量,就是这种变量会把模式中所定义的文件挨个取出,直到所有的符合模式的文件都取完。这种自动化变量只出现在规则的命令中。

    • $@:表示规则中的目标文件集,在模式规则中,如果有多个目标,那么$@就是匹配于目标中模式定义的集合。
    • @%:仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是foo.a(bar.o),那么$%就是bar.o$@就是foo.a。如果目标不是函数库文件(unix下是.a,windows下是.lib),那么其值为空。
    • $<:依赖目标中的第一个目标的名字。如果依赖目标以模式%定义的,那么%<将是符合模式的一系列的文件集。注意,是一个一个取出来的。
    • $?:所以比目标新的依赖目标的集合,以空格分隔。
    • $^:所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那这个变量会去除重复的依赖目标。
    • $+:这个变量很像$^,也是所有依赖目标的集合,只是它不去除重复的依赖目标。
    • $*:这个变量表示目标模式中%及之前的部分,如果目标是dir/a.foo.b,并且目标的模式是a.%.b,那么$*的值就是dir/a.foo。这个变量对于构造有关联的文件名是比较有用的。如果目标中没有模式的定义,那么$*也不能被推导出来,但是如果目标文件的后缀是make所识别的,那么$*就是除了后缀的那一部分。例如:如果目标是foo.c,因为.c是make所能识别的,所以$*的值就是foo。这个特性是GNU make的,很有可能不兼容其它版本的make,所以应该尽量避免使用$*,除非在隐含规则或静态模式中。如果目标后缀不能被make识别,那么其值为空。

    当你希望只对更新过的依赖文件进行操作时,$?在显示规则中很有用,例如,假设一个函数库文件叫lib,其由其它几个object文件更新,那么把object文件打包的比较有效的makefile规则是:

    lib : foo.o bar.o lose.o win.o
        ar r lib $?
    

    在上述所列出来的自动变量中,四个变量$@$<$%$*在扩展时只会有一个文件,而另三个是文件列表。这七个自动化变量还可以取得文件的目录名和当前目录下符合模式的文件名,只需要配上D或F。这是GNU make老版本的特性,在新版本中直接使用函数dir或者notdir就可以做到。如果$@的值时dir/foo.o,那么$(@D)就是dir,如果$@中没有包含斜杠,其值就是.表示当前目录;$(@F)的值就是foo.o,相当于函数$(notdir $@)

  • 模式的匹配: 一般来说一个目标的模式有一个前缀或者后缀的%,或者没有前缀,直接就是一个%。把%匹配的内容叫做,比如%.c匹配的文件test.c中的test就是茎。

    当一个模式匹配有斜杠的文件时,那么在进行模式匹配时,目录部分首先被移开,然后进行匹配,成功后,再把目标加回去。在进行茎传递时,需要知道这个步骤。例如e%t,匹配src/eat文件,于是src/a就是其茎,如果这个模式定义在依赖目标中,而被依赖于这个模式的目标中有有个模式c%r,那么目标就是src/car(茎被传递)。

  • 重载内建隐含规则:可以重载或者定义一个全新的隐含规则:

    %.o : %.c
        $(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)
    

隐含规则搜索算法

比如我们有一个目标叫 T。下面是搜索目标T的规则的算法。请注意,在下面,我们没有提到后缀规则,原因是,所有的后缀规则在Makefile被载入内存时,会被转换成模式规则。如果目标是archive(member)的函数库文件模式,那么这个算法会被运行两次,第一次是找目标T,如果没有找到的话,那么进入第二次,第二次会把member当作T来搜索。

  1. T的目录部分分离出来。叫D,而剩余部分叫N。(如:如果Tsrc/foo.o,那么,D就是src/N就是foo.o

  2. 创建所有匹配于T或是N的模式规则列表。

  3. 如果在模式规则列表中有匹配所有文件的模式,如%,那么从列表中移除其它的模式。

  4. 移除列表中没有命令的规则。

  5. 对于第一个在列表中的模式规则:

    1)推导其"茎"SS应该是T或是N匹配于模式中%非空的部分。

    2)计算依赖文件。把依赖文件中的%都替换成"茎"S。如果目标模式中没有包含斜框字符,而把D加在第一个依赖文件的开头。

    3)测试是否所有的依赖文件都存在或是理当存在。(如果有一个文件被定义成另外一个规则的目标文件,或者是一个显式规则的依赖文件,那么这个文件就叫"理当存在")

    4)如果所有的依赖文件存在或是理当存在,或是就没有依赖文件。那么这条规则将被采用,退出该算法。

  6. 如果经过第5步,没有模式规则被找到,那么就做更进一步的搜索。对于存在于列表中的第一个模式规则:

    1)如果规则是终止规则,那就忽略它,继续下一条模式规则。

    2)计算依赖文件。(同第5步)

    3)测试所有的依赖文件是否存在或是理当存在。

    4)对于不存在的依赖文件,递归调用这个算法查找他是否可以被隐含规则找到。

    5)如果所有的依赖文件存在或是理当存在,或是就根本没有依赖文件。那么这条规则被采用,退出该算法。

  7. 如果没有隐含规则可以使用,查看.DEFAULT规则,如果有,采用,把.DEFAULT的命令给T使用。

一旦规则被找到,就会执行其相当的命令,而此时,我们的自动化变量的值才会生成。

results matching ""

    No results matching ""