GNU Make 中文手册v3.8 学习 ( 3/3 )

来源:互联网 发布:window phone 8软件 编辑:程序博客网 时间:2024/05/22 07:55

GNU Make 中文手册v3.8 学习 ( 3/3 )


后缀规则是一种古老定义隐含规则的方式,在新版本的make中使用模式规则作为对它的替代,模式规则相比后缀规则更加清晰明了。在现在版本中保留它的原因是为了能够兼容旧的makefile文件。
判断一个后缀规则是单后缀还是双后缀的过程:判断后缀规则的目标,如果其中只存在一个可被make识别的后缀,则规则是一个“单后缀”规则;当规则目标中存在两个可被make识别的后缀时,这个规则就是一个“双后缀”规则。(尽管单后缀很少出现)
例如:“.c”和“.o”都是make可识别的后缀。因此当定义了一个目标是“.c.o”的规则时。make会将它作为一个双后缀规则来处理,它的含义是所有“.o”文件的依赖文件是对应的“.c”文件。下边是使用后追规则定义的编译.c源文件的规则:
.c.o:

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

注意:一个后缀规则中不存在任何依赖文件。否则,此规则将被作为一个普通规则对待。因此规则:

.c.o: foo.h

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

就不是一个后缀规则。它是一个目标文件为“.c.o”、依赖文件是“foo.h”的普通规则。它也不等价于规则:

%.o: %.c foo.h

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


可识别的后缀指的是特殊目标“.SUFFIXES”所有依赖的名字。通过给特殊目标“SUFFIXES”添加依赖来增加一个可被识别的后缀。像下边这样:
.SUFFIXES: .hack .win
它所实现的功能是把后缀“.hack”和“.win”加入到可识别后缀列表的末尾。
如果需要重设默认的可识别后缀,因该这样来实现:
.SUFFIXES:            #删除所有已定义的可识别后缀
.SUFFIXES: .c .o .h     #重新定义

静态库文件需要使用“ar”来创建和维护。当给静态库增建一个成员时(加入一个.o文件到静态库中),“ar”可直接将需要增加的.o文件简单的追加到静态库的末尾。之后当我们使用这个库进行连接生成可执行文件时,链接程序“ld”却提示错误,这可能是:主程序使用了之前加入到库中的.o文件中定义的一个函数或者全局变量,但连接程序无法找到这个函数或者变量。
这个问题的原因是:之前我们将编译完成的.o文件直接加入到了库的末尾,却并没有更新库的有效符号表。连接程序进行连接时,在静态库的符号索引表中无法定位刚才加入的.o文件中定义的函数或者变量。这就需要在完成库成员追加以后让加入的所有.o文件中定义的函数(变量)有效,完成这个工作需要使用另外一个工具“ranlib”来对静态库的符号索引表进行更新。

我们所使用到的静态库(文档文件)中,存在这样一个特殊的成员,它的名字是“__.SYMDEF”。它包含了静态库中所有成员所定义的有效符号(函数名、变量名)。因此,当为库增加了一个成员时,相应的就需要更新成员“__.SYMDEF”,否则所增加的成员中定义的所有的符号将无法被连接程序定位。完成更新的命令是:
ranlib ARCHIVEFILE
如果我们使用GNU ar工具来维护、管理静态库,我们就不需要考虑这一步。GNU ar本身已经提供了在更新库的同时更新符号索引表的功能(这是默认行为,也可以通过命令行选项控制ar的具体行为。可参考 GNU ar工具的man手册)。


静态库的后缀规则属于后缀规则的特殊应用,后缀规则在目前版本的GNU make中已经被模式规则替代。但目前版本make同样支持旧风格的后缀规则,主要是为了兼容老版本的make。对于静态库的重建也可以使用后缀规则。目标后缀需要是“.a”(在Linux(Unix)中、静态库的后缀为.a)。例如这样一个后缀规则:
.c.a:
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
$(AR) r $@ $*.o
$(RM) $*.o

它相当于模式规则:
%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
$(AR) r $@ $*.o
$(RM) $*.o


基本的约定
本节讨论书写Makefile时应遵循的一些基本约定,由于不同版本make之间的差异。可能在GNU make环境中正常工作的Makefile,换成其它版本的make执行时会发生错误。为了最大可能的兼容不同版本的make,这里给出了一些基本的约定。

1. 所有的Makefile中应该包含这样一行:

SHELL = /bin/sh

其目的是为了避免变量“SHELL”在有些系统上可能继承同名的系统环境变量而导致错误。虽然在GNU版本的make中不会出现这种问题(GNU make中变量“SHELL”的默认值是“/bin/sh”,它不同于系统环境变量“SHELL”)。

2. 小心处理后缀和隐含规则。不同make可识别后缀和隐含规则可能不同,它可能会导致混乱或者错误。因此在特定Makefile中明确限定可识别的后缀是一个不错的主意。在Makefile中应该这样做:

.SUFFIXES:

.SUFFIXES: .c .o

第一行首先取消掉make默认的可识别后缀列表,第二行重新指定可识别的后缀列表。

当规则只有一个依赖文件时。应该使用自动化变量“$<”和“$@”代替出现在命令的依赖文件和目标文件(其它版本的make,只在隐含规则中设置自动化变量“$<”)。对于一个这样的规则:

foo.o : bar.c

    $(CC) –I. –I$(srcdir) $(CFLAGS) –c bar.o –o foo.o

我们在Makefile中应该以这种方式来书写:

foo.o : bar.c

    $(CC) –I. –I$(srcdir) $(CFLAGS) –c $< –o $@


另外,对于有多个依赖的规则,为了规则能被正确执行。应该在规则的命令行中明确的指定文件的完整路径名。例如第一个例子就可以这样写(需要在规则之前使用“VPATH”指定搜索目录):

foo.1 : foo.man sedscript

    sed –e $(srcdir)/sedscript $(srcdir)/foo.man > $@


重建或者安装目标(一般是伪目标)的命令行可使用编译器或者相关工具程序,这些命令使用一个变量来表示。这样做的好处是:当修改一个命令时,只需要更改代表命令的变量的值就可以了。对于以下的这些命令程序:

ar    bison    cc    flex    install    ld    ldconfig    lex

make    makeinfo    ranlib    texi2dvi    yacc

在规则中的命令中,使用以下这些变量来代表它们:

$(AR) $(BISON) $(CC) $(FLEX) $(INSTALL) $(LD) $(LDCONFIG)$(LEX)

$(MAKE) $(MAKEINFO) $(RANLIB) $(TEXI2DVI) $(YACC)

如果规则的命令行需要使用“ranlib”或者“ldconfig”等这些工具时,需要考虑当前的系统是否支持这些工具。当在不支持它的系统中执行包含此命令的规则时,要能够给出提示信息(提示原因是告诉用户系统不支持此命令,但不应该出现错误而退出执行)。


在我们书写的Makefile中应该讲所有的命令、选项作为变量定义,方便后期对命令的修改和对选项的修改。就是说用户可以通过修改一个变量值来重新指定所要执行的命令,或者来控制命令执行的选项、参数等。

当使用变量来表示命令时,如果规则中需要使用此命令时,可通过引用代表此命令的变量来实现。例如:定义变量“CC = gcc”,规则中就可使用“$(CC)”来引用“gcc”。对于一些件管理器工具如“ln”,“rm”“mv”等,可以不为它们定义变量,而直接使用。

所有命令执行的参数选项也应该定义一个变量(可称为命令的选项变量)。在命令变量(代表一个命令的变量)后添加“FLAGS”来命名这个选项变量。例如:变量“CFLAGS”是c编译器(命令变量为“CC”)的命令行选项变量;变量YFLAGS时命令“yacc”(命令变量为“YACC”)选项变量;变量“LDFLAGS”是命令“ld”(命令变量为“LD”)的选项变量等。在所有需要执行预处理的命令行应该使用变量“CCFLAGS”作为gcc的执行参数;同样任何需要执行链接的命令行中使用“LDFLAGS”作为命令的执行参数。

c编译器的编译选项变量“CFLAGS”在Makefile中通常是为编译所有的源文件提供选项变量。为编译一个特定文件增加的选项,不应包含在变量“CFLAGS”中。编译特定文件(或者一类特定文件)时,如果需要使用特定的选项参数,可以将这些选项写在编译它所执行规则的命令行中(也可以使用目标指定变量或者模式指定变量)。

在所有编译命令行中,变量“CFLAGS”应该放在编译选项列表的最后。这样可以保证当命令行参数出现重复时,“CFLAGS”始终效的。另外,在任何调用c编译器的命令行中都应该使用选项变量“CFLAGS”,无论是进行编译还是连接。

如果需要在Makefile中实现文件安装的规则,那么就需要在Makefile中定义变量“INSTALL”。此变量代表安装命令(install)。同时在Makefile中也需要定义变量“INSTALL_PROGRAM”和“INSTALL_DATA”(“INSTALL_PROGRAM”的缺省值都是“$(INSTALL)”;“INSTALL_DATA”的缺省值是“${INSTALL} –m 644”)。可以使用这些变量,来安装可执行程序或者非可执行程序到指定位置。例如:

$(INSTALL_PROGRAM) foo $(bindir)/foo

$(INSTALL_DATA) libfoo.a $(libdir)/libfoo.a


所有GNU发布的软件包的Makefile中,必须包含以下这些目标:

all

此目标的动作是编译整个软件包。“all”应该为Makefile的终极目标。该目标的动作不重建任何文档(只编译所有的源代码,生成可执行程序);Info文件应该作为发布文件的一部分,DVI文件只在明确指定的时候才应该被重建。

缺省情况下,对所有源程序的编译和连接应该使用选项“-g”,是最终的可执行程序中包含调试信息。当最终的可执行程序不需要包含调试信息时,可使用“strip”去掉可执行程序中的调试符号以减小最终的程序大小。

install

此目标的动作是完成程序的编译并将最终的可执行程序、库文件等拷贝到安装的目录。如果只是验证这些程序是否可被正确安装,它的动作应该是一个测试安装动作。

安装时一般不要对可执行程序进行strip(去掉可执行程序内部的调试信息)。存在另外一个目标“install-strip”,它实现安装的同时完成对可执行程序strip。

保证目标“install”的动作不更改程序创建目录(builid目录)下的任何文件,对这个目录下文件的修改(重建或者更新)是目标“all”所要定义的动作。

uninstall

删除所有已安装文件——由install创建的文件拷贝。规则所定义的命令不能修改编译目录下的文件,仅仅是删除安装目录下的文件。像install目标的命令一样,uninstall目标的命令也分为三类。

install-strip

和目标install的动作类似,但是install-strip指定的命令在安装时对可执行文件进行strip(去掉程序内部的调试信息)。它的定义如下:

install-strip:

$(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s'  install

如果软件包的存在安装脚本时,目标install-strip所定义的命令就不能是对目标“install”的引用,它仅仅完成对可执行文件的strip。

“install-strip”不应该直接在build目录下对可执行文件进行strip,应该是对安装目录下的可执行文件进行strip。就是说“install-strip”所定义的命令不能对build目录下的文件产生影响。

一般不建议安装时对可执行文件进行strip,因为去掉可执行文件的调试信息后,如果在程序中存在bug,就不能通过gdb对程序进行调试。

clean

清除当前目录下编译生成的所有文件,这些文件在make过程中产生。注意,clean动作不能删除软件包的配置文件,同时也不能删除build时创建的那些文件(诸如:目录、build生成的信息记录文件等)。因为这些文件都是发布版本的一部分。

对于.dvi文件,当它不作为发布版本的一部分时,可以删除。

distclean

类似于目标clean,但增加删除当前目录下的的配置文件、build过程产生的文件。目标“distclean”指定的删除命令应该删除软件包中所有非发布文件。

dist

此目标指定的命令创建发布程序的tar文件。创建的tar文件应该是这个软件包的目录,文件名中也可以包含版本号(就是说创建的tar文件在解包之后应该是一个目录)。例如,发布的gcc 1.40版的tar文件解包的目录为“gcc-1.40”。

通常的做法是是创建一个空目录,如使用ln或cp将所需要的文件加入到这个目录中,之后对这个目录使用tar进行打包。打包之后的tar文件使用gzip压缩。例如,实际的gcc 1.40版的发布文件叫“gcc-1.40.tar.gz”。

目标“dist”的依赖文件为软件包中所有的非源代码的文件,因此在使用目标进行发布软件打包压缩之前必须保证这些文件是最新的。