一、使用make更新静态库

静态库文件是一些.o文件的集合,在Linux中使用ar工具对它进行维护管理。一个静态库通常由多个.o文件组成,这些.o文件可独立的被作为一个规则的目标,库成员作为目标时需要按照如下格式来书写:

ARCHIVE(MEMBER)

注:这种格式只能出现在规则的目标依赖中,不能出现在命令行中。含有这种表达式的规则的命令行只能是ar命令或者其它可以对库成员进行操作的命令。如下规则用于创建库“foolib”,并将“hack.o”成员加入库中:

foolib(hack.o) : hack.o
  ar cr foolib hack.o

如果在规则中同时需要指定库的多个成员,可以将多个成员放在括号中,如下:

foolib(hack.o kludge.o)等价于foolib(hack.o) foolib(kludge.o)

也可以使用shell通配符,例如foolib(*.o)代表库foolib中所有的.o成员。

静态库的更新

模式(%)用来更新目标A(M),其先将M拷贝到A中,如果之前A不存在,则首先创建A。例如:目标为foo.a(bar.o),执行时将完成:首先使用隐含规则创建bar.o,之后将bar.o加入到foo.a中。如此,bar.o就作为foo.a中的一个成员了。

静态库中,其所有的成员名是不包含目录描述的,也就是说对于静态库,当使用nm命令查看其成员时,能够获得的信息只是静态库所包含的成员名字而已。

当给一个静态库增加一个成员时,ar将.o文件简单地追加到静态库的末尾,并没有及时更新库的符号表。如果此时我们使用这个库进行链接生成可执行文件,链接程序ld却提示出错,可能原因是:程序使用了刚加入的.o文件中定义的函数或者全局变量,但链接程序无法找到。此时需要使用ranlib来对符号表进行更新。

静态库中存在一个特殊的成员__.SYMDEF,其记录了库中所有成员的符号(函数名、变量名)。使用方法为:

ranlib ARCHIVEFILE

通常在makefile中我们可以这样来实现:

libfoo.a : libfoo.a(x.o) libfoo.a(y.o)
  ranlib libfoo.a

二、make隐含规则

使用make的隐含规则,在makefile中就不需要明确给出重建某一目标的命令,甚至可以不需要规则。make会自动根据已存在的源文件类型来启动相应的隐含规则。例如:

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

上述并没有给出重建foo.o的规则,make会根据隐含规则来创建这个文件。隐含规则所提供的只是一个最基本的对于关系:例如EXENAME.o对应EXENAME.c,EXENAME对应EXENAME.o。

每一个内嵌的隐含规则都存在一个目标模式和一个依赖模式,而且同一个目标模式可以对应多个依赖模式。通常make会对那些没有命令行的规则、双冒号规则寻找一个隐含规则来执行。

注:即使给一个目标指定了明确的依赖文件,那么隐含规则依然会被执行,如下所示:

foo.o : foo.p

如果当前目录下存在foo.c,那么执行make时并不用pc编译,而是用cc编译。

如果不想让make为一个没有命令行的规则中的目标搜索隐含规则时,可以使用空命令来实现。

1 make隐含规则

自动由.c生成.o                 $(CC) –c $(CPPFLAGS) $(CFLAGS)
自动由.cc或.C生成.o              $(CXX) –c $(CPPFLAGS) $(CFLAGS)
自动由.o生成可执行文件,使用链接器GNU ld $(CC) $(LDFLAGS) *.o $(LOADLIBS) $(LDLIBS)
                        仅适用于由一个源文件直接生成可执行文件的情况;
                        如果需要由多个源文件共同创建一个可执行文件,需要在makefile中添加隐含规则的依赖文件,如:x : y.o z.o

2 隐含变量

隐含变量分为两类,一类表示命令的名字,一类表示命令使用的参数,多个参数使用空格分隔。

表示命令的名字

AR

函数库打包程序,默认是ar,用于创建静态库.a

AS

汇编程序,默认是as

CC

C编译器,默认是cc

CXX

C++编译器,默认是g++

CO

从RCS中提取文件的程序,默认是co

CPP

C程序的预处理器,默认是$(CC) -E

GET

从SCCS中提取文件,默认是get

LEX

将Lex语言转变为C或Ratfo程序,默认是lex

YACC

Yacc文法分析器,默认是yacc

MAKEINFO

转换Texinfo源文件.text到Info文件,默认是makeinfo

TEX

从Tex源文件创建Tex Dvi,默认是tex

TEXI2DVI

从Tex源文件创建Tex Dvi,默认是texi2dvi

WEAVE

转换Web到Tex,默认是weave

CWEAVE

转换Web到Tex,默认是cweave

CTANGLE

转换C Web到C,默认是ctangle

RM

删除命令,默认是rm –f

命令使用的参数

ARFLAGS

执行AR时的命令行参数,默认是rv

ASFLAGS

执行AS时的命令行参数

CFLAGS

执行CC时的命令行参数

CXXFLAGS

执行g++时的命令行参数

LDFLAGS

链接器ld的参数

3 缺省规则

在make执行过程中,如果无法为一个目标文件找到一个合适的重建规则,那么make将执行缺省规则,可以定义如下:

% ::
  touch $@

表示在执行make时,对所有不存在的.c文件将会使用touch命令创建一个这样的空文件。另一种实现缺省规则的例子可以不使用万用规则%,而使用伪目标.DEFAULT,如下:

.DEFAULT :
  touch $@

三、make编写约定

1 基本约定

  • 所有的makefile中应该包含如下一行

  SHELL = /bin/sh

  • 小心处理后缀和隐含规则

  不同版本的make可识别的后缀和隐含规则可能不同,为避免混乱或者错误,在makefile中明确指定可识别的后缀是个不错的想法:

  .SUFFIXES :

  .SUFFIXES : .c .o

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

  • 小心处理规则中的路径

  应该明确给出路径,如"./"代表当前目录;"$(srcdir)"代表源文件目录等。如果没有明确指定路径,那么默认为当前目录。

  • 使用变量VPATH指定搜索目录。当规则中只有一个依赖文件时,应该使用自动化变量"$<"和"$@"代替命令行中的依赖文件和目标文件。

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

  • 尽量使用命令的通用选项而不使用基于特定系统的选项。如"mkdir -p"一般工作在Linux系统中,其他很多系统不能很好地支持该选项。
  • 重建或者安装目标(一般是伪目标)的命令行中使用变量来表示命令。这样的话,以后修改命令时,只需要修改变量值即可,不需要修改命令行语句。

  如利用CC代表cc,命令行中使用"$(CC)"即可。

3 使用变量代替命令

在makefile文件中,最好将所有的命令、选项作为变量定义,以方便后期对命令的修改和对选项的修改。所有命令执行的参数也都应该定义为一个变量,可称其为选项变量,比如CFLAGS就是c编译器的命令选项变量;LDFLAGS就是ld的命令选项变量等等。当然,如果为了编译某个特定类型的文件而增加的临时选项是不应该添加到变量CFLAGS中的。在所有编译命令行中,CFLAGS应该放在编译选项列表的最后,这样可以保证当命令行参数出现重复时,CFLAGS始终有效。

在makefile文件中定义变量INSTALL表示安装命令install。同时定义变量INSTALL_PROGRAM和INSTALL_DATA用以将程序和数据部分安装到指定位置,如下:

$(INSTALL_PROGRAM) foo $(bindir)/foo
$(INSTALL_DATA) libfoo.a $(libdir)/libfoo.a

4 安装目录变量

4.1 安装根目录

prefix

实际安装目录的父目录,变量prefix的缺省值为/usr/local。创建完整的GNU系统时,变量prefix的缺省值为空值,/usr是/的符号链接。

exec_prefix

缺省值为"$(prefix)"。通常,"$(exec_prefix)"目录中的子目录下存放机器相关文件,例如可执行文件和程序库。"$(prefix)"目录的子目录中存放通用的一般文件。

4.2 文件安装目录

bindir

安装用户可执行程序。通常为/usr/local/bin,如下使用:"$(exec_prefix)/bin"

sbindir

安装可在shell中直接调用的程序。通常为/usr/local/sbin,如下使用:"$(exec_prefix)/sbin"

libexecdir

安装由其它程序调用的可执行程序[这些程序用户不能直接使用]。通常为/usr/local/libexec

datadir

安装和机器体系结构无关的只读数据文件。通常为/usr/local/share

sysconfdir

安装特定机器相关的只读数据文件,包括:主机配置文件、邮件服务、网络配置等。通常为/usr/local/etc

sharedstatedir

安装那些在程序运行时可修改的文件,这些文件与体系结构无关。通常为/usr/local/com

localstatedir

安装那些在程序运行时可修改的文件,这些文件与体系结构有关。通常为/usr/local/var

libdir

安装编译后的.o文件。通常为/usr/local/lib

infodir

安装软件包的info文件。缺省值为/usr/local/info

lispdir

安装软件包的Emacs Lisp文件,缺省值为/usr/local/share/emacs/site-lisp

includedir

安装用户程序中使用”#include”包含的头文件,缺省值为/usr/local/include。

注:除gcc外的大多数编译器并不会在/usr/local/include中搜索头文件,因此这种方式只适合gcc编译器。对于那些依赖于其他编译器编译的库文件,需要将头文件安装在两个目录中,一个由includedir指定,另一个由oldinlcudedir指定

oldincludedir

非gcc编译器编译的程序中包含的头文件。缺省值为/usr/include

mandir

安装帮助文档的顶层目录。缺省值为/usr/local/man

man1dir

安装帮助文档的第一节。缺省值为"$(mandir)/man1"

man2dir

安装帮助文档的第二节。缺省值为"$(mandir)/man2"

...

...

manext

帮助文档名的扩展,以点号.开始的十进制数。缺省值为.1

man1ext

帮助文档第一节的文件名扩展

man2ext

...

...

...

src

程序的源文件所在目录。该变量的值是在使用configure脚本对软件包进行配置时产生的。

5 makefile的标准目标名

在所有GNU发布的软件包的makefile中,一般需要保护如下目标:

all

用于编译整个软件包,应该作为makefile的终极目标。

install

用于完成程序的编译,然后将可执行程序、库文件等拷贝到安装目录中。

uninstall

用于删除已安装的文件。注:该规则所定义的命令不能删除编译目录下的文件,仅仅是删除安装目录下的文件。

install-strip

在安装的同时对可执行文件进行strip[去掉程序内部的调试信息],定义如下:

install-strip :
$(MAKE) INSTALL_PROGRAM = '$(INSTALL_PROGRAM)' install

注:install-strip只是对安装目录下的可执行文件进行strip操作,不能对build目录下的文件产生任何影响。

clean

清除当前目录下编译生成的所有文件。注:clean不能删除软件包的配置文件,同时也不能删除build创建的文件,因为这些文件都是软件发布的一部分。

distclean

类似于clean,但是会删除当前目录下的配置文件、build过程中产生的文件。

mostlyclean

类似于clean,但是可以保留一些编译生成的文件,避免下次编译时对这些文件进行重建。例如,对于gcc来说,该目标指定的命令不删除文件libgcc.a,因为绝大多数情况下都不要对其重新编译。

maintainer-clean

删除所有当前目录下由makefile重建的文件。一般只能由维护该软件包的用户使用,不能被普通用户使用。通常maintainer-clean的命令行中以如下两行开始:

    @echo "该命令用于维护此软件包的用户使用"
@echo "其删除的文件可能需要使用特殊的工具来重建"

TAGS

用于更新tags记录文件。

info

产生必要的info文档。一般可以如下编写:

info : foo.info

foo.info : foo.texi chap1.texi chap2.texi
$(MAKEINFO) $(srcdir)/foo.texi

通常,GNU发布程序与Info文档会被一同创建,这意味着Info文档是在源文件的目录下。用户在创建发布软件时,make不需要更新Info文档,因为它们已经更新到最新了。

dvi

为所有的Texinfo文件创建对应的DVI文件。例如:

dvi : foo.dvi

foo.dvi : foo.texi chap1.texi chap2.texi
$(TEXI2DVI) $(srcdir)/foo.texi

dist

创建发布程序的tar文件。创建的tar文件应该是这个软件包的目录,文件名中也可以包含版本号。通常的做法是创建一个空目录,使用ln或者cp将所需的文件加入到这个目录中,之后对这个目录使用tar进行打包操作。打包之后的tar文件使用gzip压缩,例如:redis 3.02的发布文件为redis-3.02-tar.gz。

check

用于完成所有的自检功能。在程序没有安装的情况下执行检查操作,确保所需程序已经存在。

installcheck

执行安装检查。在安装检查之前,确保所有的程序已经被创建并且被安装。

installdirs

使用installdirs创建安装目录及其子目录在很多场合是非常有用的。脚本mkinstalldirs就是为了实现这个目的而编写的。可以如下编写:

installdirs : mkinstalldirs
$(srcdir)/mkinstalldirs $(bindir) $(datadir) \
$(libdir) $(infodir) \
$(mandir)

6 安装命令分类

makefile中的install目标设计中,其命令可以分为三类:正常命令、安装前命令和安装后命令。

正常命令

仅把文件复制到合适的目录中,这个过程不修改任何文件

安装前命令

这个命令大多数程序不需要,但应该在makefile中提供

安装后命令

多数情况下是运行install-info程序。因为该工作只能在安装完软件包后才可执行。

将install规则的命令分为这三类时,应该在命令行之间插入分类行,说明后续需要执行的是什么类别的命令。分类行是由一个Tab字符开始的对make特殊变量的引用。行尾是可选的注释。分类行执行一个特殊的空动作,因为这三个特殊的make变量没有定义,我们在makefile中也不能定义它们,如下:

"$(PRE_INSTALL)       # 以下是安装前命令"
"$(POST_INSTALL) # 以下是安装后命令"
"$(NORMAL_INSTALL) # 以下是正常命令"

相应地,以下是三个uninstall命令的分类行:

"$(PRE_UNINSTALL)       # 以下是卸载前命令"
"$(POST_UNINSTALL) # 以下是卸载后命令"
"$(NORMAL_UNINSTALL) # 以下是正常命令"

注:如果没有使用分类行,那么规则的所有命令行都被认为是正常命令。

05-02 16:04