第十四章Makefile的约定
本章讨论书写Makefile时需要遵循的约定。工具“Automake”可以帮助我们创建一个遵循这些约定的Makefile。所有GNU发布的软件包中的Makefile都是按照这些标准的约定来书写的。因此理解本章的内容,可帮助很快的熟悉那些开源代码的结构。而对于我们,在为一个工程书写Makefile时,也尽量遵循这些约定。虽然并没有强求你这么做,但是建议你还是按照已约定的规则来书写自己的Makefile。
14.1 基本的约定
本节讨论书写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默认的可识别后缀列表,第二行重新指定可识别的后缀列表。
3. 小心处理规则中的路径。当需要处理指定目录的的文件时,应该明确给出路径。如“./”代表当前目录,“$(srcdir)”代表源代码目录。没有指定明确路径,那么就意味着是当前目录。
目录“./”(当前目录,GNU的发布软件包中的“build”目录)和“$(srcdir)”的区别和重要,我们可以通过“configure”脚本的选项“--srcdir”指定源代码所在的目录(可参考GNU发布的软件包中的configure脚本)。当源代码目录和build目录不同时,规则:
foo.1 : foo.man sedscript
sed –e sedscript foo.man > $@
将执行失败,是因为“foo.man”和“sedscript”并不在当前目录(当然,处理这种错误的手段可能有很多,诸如使用变量“VPATH”等)。当前目录只是build目录,并不是软件包目录。
4. 使用GNU make的变量“VPATH”指定搜索目录。当规则只有一个依赖文件时。应该使用自动化变量“$<”和“$@”代替出现在命令的依赖文件和目标文件(其它版本的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 > $@
在GNU的发布软件包中,包括了很多非源代码的文件。诸如:“info”文件、“Autoconf”的输出文件、“Automake”、“Bison”或者“Flex”等文件。这些文件在发布时也存在于源代码的目录中。因此Makefile中对它们的重建也应该是在源代码目录下,而不应该在build目录。
相反的,对于那些本来就不存在于源代码目录下的文件,也不应该将它们创建在源代码的目录下。要记住,make的过程不应该以任何方式修改源代码,或者改变源代码目录的结构。
14.2 规则命令行的约定
本节讨论书写规则命令的一些约定,在书写多系统兼容的Makefile时,需要注意不同系统的命令存在不兼容。这里对规则命令行做出了一些书写的基本约定:
1. 书写Makefile时,规则的命令(包括其他的脚本文件,如:configure)应该是“sh”而不是“csh”所支持的。
2. 用于创建和安装的“configure”脚本以及Makefile中的命令,除使用下面所列出的之外,避免使用其它命令:
cat cmp cp diff echo egrep expr false grep install-info
ln ls mkdir mv pwd rm rmdir sed sleep sort tar test touch true
3. 在目标“dist”的命令行中可以使用压缩工具“gzip”。
4. 对于可使用的这些工具命令,尽量使用它的通用选项。不要使用那些只在特定系统上有效的选项。如:“mkdir -p”这个命令在Linux系统上能够很好的工作,但是其它很多系统却并不支持“mkdir”的“-p”选项。
5. 尽量不要在规则的命令行中创建符号连接文件(使用“ln”命令)。因为有些系统不支持符号连接文件(对于类Unix的系统我们基本上没有问题,可能这里所说的是MS-DOS系统的系统。我想大家也没有兴趣或者说没有必要在MS-DOS下写Makefile,所以这个限制基本可以不考虑)。
6. 重建或者安装目标(一般是伪目标)的命令行可使用编译器或者相关工具程序,这些命令使用一个变量来表示。这样做的好处是:当修改一个命令时,只需要更改代表命令的变量的值就可以了。对于以下的这些命令程序:
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”等这些工具时,需要考虑当前的系统是否支持这些工具。当在不支持它的系统中执行包含此命令的规则时,要能够给出提示信息(提示原因是告诉用户系统不支持此命令,但不应该出现错误而退出执行)。
对以上没有列出的其它命令,在规则的命令行使用时也应该都是通过变量的形式。例如如下的这些命令:
chgrp chmod chown mknod
我们可以在Makefile中为这些命令组件定义一个代表它的变量(如:CHRP、CHMOD等,在命令行中就可以使用$(CHMOD)来引用)。
书写多系统兼容Makefile时需要遵循以上的约定。如果只考虑一种系统时,以上的这些约定中可以灵活处理,比如:在命令组件的使用上,我们就可以使用这个系统独具的那些命令组件;系统支持符号链接时,我们就可以在命令行重创建一个符号链接文件。对于上边的第6个约定,强烈建议大家都遵循。
14.3 代表命令变量
在我们书写的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 = -g
ALL_CFLAGS = -I $(CFLAGS)
.c.o:
$(CC) -c $(CPPFLAGS) $(ALL_CFLAGS) $<
例子中,变量“CFLAGS”指定编译选项为“-g”,在本例中它作为缺省的编译选项。对于所有源文件的编译都使用“-g”选项。双后缀规则的命令行中为编译生成“.o”文件指定了另外的选项“-I. -g”
在所有编译命令行中,变量“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
另外,也可以使用变量“DESTDIR”来指定目标需要安装的目录。通常也可以不在Makefile定义变量“DESTDIR”,可通过make命令行参数的形式来指定。例如:“make DESTDIR=exec/ install”。因此上边的命令就可以这样实现:
$(INSTALL_PROGRAM) foo $(DESTDIR)$(bindir)/foo
$(INSTALL_DATA) libfoo.a $(DESTDIR)$(libdir)/libfoo.a
安装命令中使用文件名而不是目录名作为第二个参数。每一个需要安装的文件使用单独的命令(包括安装一个目录)。
14.4 安装目录变量
在Makefile中,安装目录同样需要使用变量来指定,这样就可以很方便的修改文件的安装路径。安装目录的标准命名下边将一一介绍。这些变量基于标准的文件系统结构,这些变量的变种在SVR4、4.4BSD、Linux、Ultrix v4以及其它现代操作系统中都有使用。
Ø 以下所罗列的两个变量是指定安装文件的根目录。所有其它安装目录都是它们的子目录。注意:文件不能直接安装在这两个目录下。
prefix
这个变量(通常作为实际文件安装目录的父目录,可以理解为其它实际文件安装目录的前缀)用于构造下列(除这两个安装根目录以外的其它目录变量)变量的缺省值。变量“prefix”缺省值是“/usr/local”。创建完整的GNU系统时,变量prefix的缺省值是空值,“/usr”是“/”的符号连接符文件。(如果使用“Autoconf”工具,它应该写成“@prefix@”)。注意:当更改了变量“prefix”以后重新执行“make install”,不会导致可执行程序(终极目标)的重建。
exec_prefix
这个前缀用于构造下列变量的缺省值。变量“exec_prefix”缺省值是“$(prefix)”(如果使用“Autoconf”工具,它应该写为“@exec_prefix@”)。通常,“$(exec_prefix)”目录中的子目录下存放和机器相关文件(例如可执行文件和例程库)。“$(prefix)”目录的子目录存放通用的一般文件。同样:改变“exec_prefix”的值之后执行“make install”,不会重建可执行程序(终极目标)。
Ø 文件(包括可执行程序、说明文档等)的安装目录: