makefile 隐含规则

Posted 林散

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了makefile 隐含规则相关的知识,希望对你有一定的参考价值。

  隐含规则
————

在我们使用Makefile时,有一些我们会经常使用,而且使用频率非常高的东西,比如,我们编译C/C++的源程序为中间目标文件(Unix下是[.o]文件,Windows下是[.obj]文件)。本章讲述的就是一些在Makefile中的“隐含的”,早先约定了的,不需要我们再写出来的规则。

“隐含规则”也就是一种惯例,make会按照这种“惯例”心照不喧地来运行,那怕我们的Makefile中没有书写这样的规则。例如,把[.c]文件编译成[.o]文件这一规则,你根本就不用写出来,make会自动推导出这种规则,并生成我们需要的[.o]文件。

“隐含规则”会使用一些我们系统变量,我们可以改变这些系统变量的值来定制隐含规则的运行时的参数。如系统变量“CFLAGS”可以控制编译时的编译器参数。

我们还可以通过“模式规则”的方式写下自己的隐含规则。用“后缀规则”来定义隐含规则会有许多的限制。使用“模式规则”会更回得智能和清楚,但“后缀规则”可以用来保证我们Makefile的兼容性。
我们了解了“隐含规则”,可以让其为我们更好的服务,也会让我们知道一些“约定俗成”了的东西,而不至于使得我们在运行Makefile时出现一些我们觉得莫名其妙的东西。当然,任何事物都是矛盾的,水能载舟,亦可覆舟,所以,有时候“隐含规则”也会给我们造成不小的麻烦。只有了解了它,我们才能更好地使用它。


一、使用隐含规则

如果要使用隐含规则生成你需要的目标,你所需要做的就是不要写出这个目标的规则。那么,make会试图去自动推导产生这个目标的规则和命令,如果make可以自动推导生成这个目标的规则和命令,那么这个行为就是隐含规则的自动推导。当然,隐含规则是make事先约定好的一些东西。例如,我们有下面的一个Makefile:

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

我们可以注意到,这个Makefile中并没有写下如何生成foo.o和bar.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)

因为,这已经是“约定”好了的事了,make和我们约定好了用C编译器“cc”生成[.o]文件的规则,这就是隐含规则。

当然,如果我们为[.o]文件书写了自己的规则,那么make就不会自动推导并调用隐含规则,它会按照我们写好的规则忠实地执行。

还有,在make的“隐含规则库”中,每一条隐含规则都在库中有其顺序,越靠前的则是越被经常使用的,所以,这会导致我们有些时候即使我们显示地指定了目标依赖,make也不会管。如下面这条规则(没有命令):

   foo.o : foo.p

依赖文件“foo.p”(Pascal程序的源文件)有可能变得没有意义。如果目录下存在了“foo.c”文件,那么我们的隐含规则一样会生效,并会通过“foo.c”调用C的编译器生成foo.o文件。因为,在隐含规则中,Pascal的规则出现在C的规则之后,所以,make找到可以生成foo.o的C的规则就不再寻找下一条规则了。如果你确实不希望任何隐含规则推导,那么,你就不要只写出“依赖规则”,而不写命令。

二、隐含规则一览

这里我们将讲述所有预先设置(也就是make内建)的隐含规则,如果我们不明确地写下规则,那么,make就会在这些规则中寻找所需要规则和命令。当然,我们也可以使用make的参数“-r”或“–no-builtin-rules”选项来取消所有的预设置的隐含规则。

当然,即使是我们指定了“-r”参数,某些隐含规则还是会生效,因为有许多的隐含规则都是使用了“后缀规则”来定义的,所以,只要隐含规则中有“后缀列表”(也就一系统定义在目标.SUFFIXES的依赖目标),那么隐含规则就会生效。默认的后缀列表是:.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S,.mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo,.w, .ch .web, .sh, .elc, .el。具体的细节,我们会在后面讲述。

还是先来看一看常用的隐含规则吧。

1、编译C程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.c”,并且其生成命令是“$(CC)–c $(CPPFLAGS) $(CFLAGS)”

2、编译C++程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.cc”或是“<n>.C”,并且其生成命令是“$(CXX)–c $(CPPFLAGS) $(CFLAGS)”。(建议使用“.cc”作为C++源文件的后缀,而不是“.C”)

3、编译Pascal程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.p”,并且其生成命令是“$(PC)–c  $(PFLAGS)”。

4、编译Fortran/Ratfor程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.r”或“<n>.F”或“<n>.f”,并且其生成命令是:
   “.f”  “$(FC) –c  $(FFLAGS)”
   “.F”  “$(FC) –c  $(FFLAGS)$(CPPFLAGS)”
   “.f”  “$(FC) –c  $(FFLAGS)$(RFLAGS)”

5、预处理Fortran/Ratfor程序的隐含规则。
“<n>.f”的目标的依赖目标会自动推导为“<n>.r”或“<n>.F”。这个规则只是转换Ratfor或有预处理的Fortran程序到一个标准的Fortran程序。其使用的命令是:
   “.F”  “$(FC) –F $(CPPFLAGS) $(FFLAGS)”
   “.r”  “$(FC) –F $(FFLAGS) $(RFLAGS)”

6、编译Modula-2程序的隐含规则。
“<n>.sym”的目标的依赖目标会自动推导为“<n>.def”,并且其生成命令是:“$(M2C)$(M2FLAGS) $(DEFFLAGS)”。“<n.o>”的目标的依赖目标会自动推导为“<n>.mod”,并且其生成命令是:“$(M2C)$(M2FLAGS) $(MODFLAGS)”。

7、汇编和汇编预处理的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.s”,默认使用编译品“as”,并且其生成命令是:“$(AS)$(ASFLAGS)”。“<n>.s”的目标的依赖目标会自动推导为“<n>.S”,默认使用C预编译器“cpp”,并且其生成命令是:“$(AS)$(ASFLAGS)”。

8、链接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 -oy.o
    cc -c z.c -oz.o
    cc x.o y.oz.o -o x
    rm -fx.o
    rm -fy.o
    rm -fz.o

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

9、YaccC程序时的隐含规则。
“<n>.c”的依赖文件被自动推导为“n.y”(Yacc生成的文件),其生成命令是:“$(YACC)$(YFALGS)”。(“Yacc”是一个语法分析器,关于其细节请查看相关资料)

10、Lex C程序时的隐含规则。
“<n>.c”的依赖文件被自动推导为“n.l”(Lex生成的文件),其生成命令是:“$(LEX)$(LFALGS)”。(关于“Lex”的细节请查看相关资料)

11、Lex Ratfor程序时的隐含规则。
“<n>.r”的依赖文件被自动推导为“n.l”(Lex生成的文件),其生成命令是:“$(LEX)$(LFALGS)”。

12、从C程序、Yacc文件或Lex文件创建Lint库的隐含规则。
“<n>.ln”(lint生成的文件)的依赖文件被自动推导为“n.c”,其生成命令是:“$(LINT) $(LINTFALGS)$(CPPFLAGS)-i”。对于“<n>.y”和“<n>.l”也是同样的规则。

三、隐含规则使用的变量

在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。你可以在你的makefile中改变这些变量的值,或是在make的命令行中传入这些值,或是在你的环境变量中设置这些值,无论怎么样,只要设置了这些特定的变量,那么其就会对隐含规则起作用。当然,你也可以利用make的“-R”或“–no–builtin-variables”参数来取消你所定义的变量对隐含规则的作用。

例如,第一条隐含规则——编译C程序的隐含规则的命令是“$(CC) –c $(CFLAGS)$(CPPFLAGS)”。Make默认的编译命令是“cc”,如果你把变量“$(CC)”重定义成“gcc”,把变量“$(CFLAGS)”重定义成“-g”,那么,隐含规则中的命令全部会以“gcc –c -g $(CPPFLAGS)”的样子来执行了。

我们可以把隐含规则中使用的变量分成两种:一种是命令相关的,如“CC”;一种是参数相的关,如“CFLAGS”。下面是所有隐含规则中会用到的变量:

1、关于命令的变量。

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源文件创建TeXDVI文件的程序。默认命令是“tex”。
TEXI2DVI
   从Texinfo源文件创建军TeX DVI 文件的程序。默认命令是“texi2dvi”。
WEAVE
   转换Web到TeX的程序。默认命令是“weave”。
CWEAVE
    转换C Web 到TeX的程序。默认命令是“cweave”。
TANGLE
   转换Web到Pascal语言的程序。默认命令是“tangle”。
CTANGLE
    转换C Web 到C。默认命令是“ctangle”。
RM
   删除文件命令。默认命令是“rm –f”。

2、关于命令参数的变量

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

ARFLAGS
   函数库打包程序AR命令的参数。默认值是“rv”。
ASFLAGS
   汇编语言编译器参数。(当明显地调用“.s”或“.S”文件时)。
CFLAGS
   C语言编译器参数。
CXXFLAGS
   C++语言编译器参数。
COFLAGS
   RCS命令参数。
CPPFLAGS
    C预处理器参数。( C和 Fortran 编译器也会用到)。
FFLAGS
   Fortran语言编译器参数。
GFLAGS
    SCCS“get”程序参数。
LDFLAGS
   链接器参数。(如:“ld”)
LFLAGS
   Lex文法分析器参数。
PFLAGS
   Pascal语言编译器参数。
RFLAGS
    Ratfor程序的Fortran 编译器参数。
YFLAGS
   Yacc文法分析器参数。


四、隐含规则链

有些时候,一个目标可能被一系列的隐含规则所作用。例如,一个[.o]的文件生成,可能会是先被Yacc的[.y]文件先成[.c],然后再被C的编译器生成。我们把这一系列的隐含规则叫做“隐含规则链”。

在上面的例子中,如果文件[.c]存在,那么就直接调用C的编译器的隐含规则,如果没有[.c]文件,但有一个[.y]文件,那么Yacc的隐含规则会被调用,生成[.c]文件,然后,再调用C编译的隐含规则最终由[.c]生成[.o]文件,达到目标。

我们把这种[.c]的文件(或是目标),叫做中间目标。不管怎么样,make会努力自动推导生成目标的一切方法,不管中间目标有多少,其都会执着地把所有的隐含规则和你书写的规则全部合起来分析,努力达到目标,所以,有些时候,可能会让你觉得奇怪,怎么我的目标会这样生成?怎么我的makefile发疯了?

在默认情况下,对于中间目标,它和一般的目标有两个地方所不同:第一个不同是除非中间的目标不存在,才会引发中间规则。第二个不同的是,只要目标成功产生,那么,产生最终目标过程中,所产生的中间目标文件会被以“rm-f”删除。

通常,一个被makefile指定成目标或是依赖目标的文件不能被当作中介。然而,你可以明显地说明一个文件或是目标是中介目标,你可以使用伪目标“.INTERMEDIATE”来强制声明。(如:.INTERMEDIATE: mid )

你也可以阻止make自动删除中间目标,要做到这一点,你可以使用伪目标“.SECONDARY”来强制声明(如:.SECONDARY :sec)。你还可以把你的目标,以模式的方式来指定(如:%.o)成伪目标“.PRECIOUS”的依赖目标,以保存被隐含规则所生成的中间文件。

在“隐含规则链”中,禁止同一个目标出现两次或两次以上,这样一来,就可防止在make自动推导时出现无限递归的情况。

Make会优化一些特殊的隐含规则,而不生成中间文件。如,从文件“foo.c”生成目标程序“foo”,按道理,make会编译生成中间文件“foo.o”,然后链接成“foo”,但在实际情况下,这一动作可以被一条“cc”的命令完成(cc–o foo foo.c),于是优化过的规则就不会生成中间文件。

(版权所有,转载时请注明作者和出处)

以上是关于makefile 隐含规则的主要内容,如果未能解决你的问题,请参考以下文章

9.Makefile隐含规则

9.Makefile隐含规则

Makefile详解--隐含规则

make打印隐含变量和隐含规则

Makefile隐晦规则

Makefile总述②