[转载]Linux应用程序编译方法
2010-07-26 15:55
211 查看
原文链接:http://www.shangshuwu.cn/index.php/Linux%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E7%BC%96%E8%AF%91%E6%96%B9%E6%B3%95
现时的
。没有已复审的修订。
跳转到: 导航
, 搜索
1 编写Makefile
Makefile语法较多,下面仅说明常用的语法,并用iptables软件包的Makefile作为实例说明典型的Makefile书写方法。
1.1 Makefile语法概述
(1)编译规则格式
Makefile文件主要包含一系列的编译规则,其格式如下:
目标…:依赖文件…
命令
第一行定义目标依赖文件,目标(target)通常指编译后的文件名,如:可执行文件或.o文件,还可以是一些特定的目标。依赖文件是产生目标文件所依赖的文件,通常为源代码文件。一个目标通过依赖多个源文件。
第二行定义make执行的操作,一个规则可以包括多个命令,每个命令占一行,命令行首以Tab键开始,一般是编译命令。正常情况下make在执行命令之前首先打印命令行,称为回显。以‘@’起始的命令行不能回显,‘@’在传输给shell时被丢弃。
依赖关系符可以是":"或"::",当同一个文件作为多个双冒号规则的目标时。这些不同的规则会被独立的处理,而不是像普通规则那样合并所有的依赖到一个目标文件。
GNU make的工作分为两个阶段。在第一阶段,make读取makefile文件,构造所有目标的依赖关系列表。在第二阶段,make对目标进行指定的操作,通常是编译连接操作。
(2)make命令行参数
当执行命令make时,它寻找makefile文件名的次序是:‘GNUmakefile’、‘makefile’和‘Makefile’。还可以用"make -f 文件名"指定makefile文件。
当目标文件已存在时,只有目标文件依赖的文件发生更新时,make才会重新生成这个目标文件。
make命令本身有一些命令行参数,它们是‘-’或‘--’开头,指定make命令的选项,例如:使用‘make –t’命令改变所有目标文件的时间戳。详细的选项说明请查阅《GNU make Manual》。
make命令还有与指定Makefile相关的命令行选项,用于指定最终编译的目标和变量重载。
make命令可以使用Makefile文件中的某一最终目标作为参数,只编译这一目标,例如:make clean、make install、make program,这里program是Makefile中指定的应用程序目标名。
make命令还可以在命令行中重载Makefile文件中的变量,用来改变Makefile中的指定变量值,例如:Makefile文件对 变量CFALAGS进行了设置,命令‘make CFLAGS='-g -O'’可以重载变量CFALAGS,编译时,C编译器都使用‘cc -c -g -O’编译程序,而不使用Makefile中定义的CFALAGS值。
(3)变量赋值
1)变量的定义
变量的定义语法形式如下:
对于追加操作符‘+=’,右边变量如果在前面使用(:=)定义,则变量为是立即扩展变量,否则为延时变量。
2)变量替换引用
变量的替换引用方法如下:
3)变量重定义
变量的值还可以用关键字"overrige"进行重新指定,例如:
4)目标指定变量
"目标指定变量(Target-specific Variable)"只在指定它的目标的上下文中有效,不影响其他目标,是目标的局部变量。。例如:
上面的语句表示目标"prog"及所引发的所有规则,变量"CFLAGS"值都是"-g"。
模式指定变量定义是将一个变量值指定到所有符合特定模式的目标上去。例如:
5)隐含变量
Makefile有一些预定义的命令变量和命令行参数变量,这两种变量一般相对应成对出现。表7中列出了常用的命令变量和命令行参数变量。
表7 常用的命令变量和命令行参数变量
6)自动化变量
自动化变量的取值由具体的规则决定,对于不同的规则,它的值不同。常用的自动化变量说明如表8。
表8 常用的自动化变量说明
(4)其他目标
1)伪目标使用方法
如果目标没有任何依赖,而只有执行动作,则称这个目标为伪目标(phony targets),它只起标签作用。伪目标的定义格式类似如下:
上面语句使用.PHONY将clean定义为伪目标,这样,make clean命令就可以执行rm操作。如果不将clean定义为伪目标,则当不存在clean文件时,make clean命令不会执行。 伪目标可用于在子目录下递归运行make命令,一个典型的递归执行代码列出如下:
上面语句中第二句定义了伪目标subdirs和$(SUBDIRS),第三句让伪目标subdirs依赖于伪目标$(SUBDIRS),这是因为当伪目标 没有作为任何目标的依赖时,即没有这行时,make命令必须明确指定这个伪目标,才能执行它所定义的命令,如:"make clean"。有了第三句后,,每一次在执行这个规则时伪目标所定义的命令都会被执行,即每次make都会执行$(MAKE) -C $@。最后一句foo: baz表示baz目录必须在foo目录之前编译。
2)伪目标all
Makefile的第一个目标为"all",它是伪目标,称为"终极目标",make命令不带目标选项时,执行的命令是make all。可以用来在一个Makefile中编译多个应用程序。一个Makefile文件用于编译两个应用程序的样例列出如下:
3)强制目标FORCE 强制目标FORCE表示运行make命令时,它所在的规则定义的命令总会被执行。下面的例子表示每次执行make时,rm命令都会被执行。
(5)make递归执行时的变量传递
当在父目录中启动make,并由父目录中的Makefile递归执行子目录的Makefile时,父子目录的Makefile之间常需要进行变量传递。
在默认情况下,只有特殊变量"SHELL"和"MAKEFLAGS"会在上层Makefile和子Makefile之间传递。"MAKEFLAGS"存放着make的命令行选项。
父Makefile使用关键字export可将声明的变量通过make环境变量(只对make的一次执行有效)的方式传递给子 Makefile,子Makefile可通过关键字unexport取消变量的传递。当传递的变量与子Makefile的变量重量时,子Makefile 变量将覆盖父Makefile传递的变量。
在父Makefile中,如果export不带变量,则表示将父Makefile中的所有的变量传递给子Makefile。
父Makefile还可以使用特殊目标".EXPORT_ALL_VARIABLES",将其后的所有变量传递给子Makefile。
(6)自动生成源文件与所包含头文件的依赖关系规则
一个源文件,如:main.c,通常包含许多头文件,例如:一个main.c样例列出如下:
编写Makefile文件时,main.o依赖于多个头文件,Makefile应包括下面语句:
对于含有许多源文件的程序包来说,头文件的依赖关系会变得很复杂,并且Makefile中需要写很多这样的规则,从而使Makefile变得很难维 护。为了避免产生这些问题,编译器提供了自动产生这些规则的命令,C编译器提供了‘-M’开关自动产生依赖规则。例如:main.c产生依赖关系规则的命 令列出如下:
cc -M main.c
上述命令产生的输出列出如下:
编译器提供了自动产生依赖关系规则的命令,Makefile文件使用这些规则的方法是:每一个源程序文件‘name.c’有一个名字为‘name.d’的 Makefile文件和它对应,name.d列出了‘name.o’所依赖的文件。在Makefile文件中,将‘name.c’生成名为 ‘name.d’依赖文件的通用规则列出如下:
上述规则中,set -e告诉shell如果$(CC)命令运行失败(非零状态退出)立即退出。正常情况下,shell退出时带有最后一个命令在管道中的状态(这里是命令sed),因此make不能注意到编译器产生的非零状态。
$(CC) -M $(CPPFLAGS) $<命令将产生依赖关系规则输出,如:main.o: main.c stdio.h stdlib.h。其中,‘$<’代表了依赖文件‘%.c’。 GNU C编译器可使用‘-MM’代替‘-M’,表示省略了系统头文件的依赖。
sed 's//($*/)/.o[ :]*//1.o $@ : /g'命令是字符串模式替换语句,‘$*’表示目标文件去掉后缀,这里为main;‘/1’代表第1个匹配模式变量($*),即main;‘$@’表示目 标文件%d,这里为main.d;‘-g’表示替换字符串中所有匹配的字符。这个语句完成的功能是将依赖关系输出变换成main.o main.d: main.c stdio.h stdlib.h。
‘> $@’表示将main.o main.d: main.c stdio.h stdlib.h语句存入文件main.d文件中。这样,每个源文件都会生成一个与之对应的Makefile文件,用来表示目标文件对源文件及它所包含的头文件的依赖关系。
接着,Makefie文件包含每个源文件对应的name.d文件,方法如下:
这样,Makefile文件透明地解决了每个源文件与所包含头文件的依赖关系,而不需要在Makefile文件中去手动编写。
1.2 iptables软件Makefile实例
iptables软件包的Makefile文件由主目录下的主Makefile和Rules.make、子目录Makefile组成。主Makefile用"include"语句包含Rules.make和子目录Makefile,所有Makefile文件共享变量。
主Makefile定义了各种列表变量、编译安装规则、打包方法等,与应用程序相关的各种编译规则基本上都在主Makefile中。
Rules.make包括扩充的编译规则,如果共享库的编译规则、依赖关系的产生规则等。
子目录Makefile一般用来填充主Makefile的文件列表变量,以便由主Makefile或Rules.make完成编译,另外,子目录Makefile还定义子目录下特定的编译规则。
下面分别分析iptables软件包的Makefile。
(1) 主Makefile
主Makefile用来定义应用程序的列表变量、编译标识、安装目录、编译规则等。主Makefile的编写方法说明如下:
1)定义软件包版本、安装目录等变量。
2)定义编译命令行标识变量CFLAGS。
3)定义应用程序列表(如:EXTRAS)和应用程序安装列表(如:EXTRA_INSTALLS)等变量。
4)定义库安装目录、库编译标识(如:SH_CFLAGS)和连接标识(如:LDFLAGS)等变量。
5)检测操作系统特性,将平台特性决定的编译标识加到CFLAGS中。
6)定义依赖文件列表变量(如:DEPFILES、EXTRA_DEPENDS),列表包括以目标文件同名、后缀为.d的文件名。
7)定义编译应用程序的依赖关系及编译连接规则。定义应用程序依赖于哪些源文件、目标文件和库列表。
8)定义应用程序的安装方法。
9)定义man文档、头文件、库文件安装方法。
10)定义源文件包打包、生成补丁(patch)文件、生成打包文件校验摘要的方法。
11)定义make clean方法。
12)主Makefile包含子目录的Makefile和Rules.make,以便运行它们,通过包含关系,所有Makefile可以使用相同的变量。
iptables/Makefile说明如下:
(2)Rules.make
Rules.make定义扩展库的清除规则、共享库的编译规则、依赖文件的生成规则、tag文件的生成规则,其列出如下(在iptables/Rules.make中):
(3)扩展库子目录Makefile
扩展子目录iptables/extensions/放置各个扩展库的源文件,编译时可根据选项将源文件编译成多个共享库或静态库。
默认情况下,这个子目录下的各个源文件被编译成动态库,编译动态库的标识的主Makefile中设置,编译规则在Rule.make中设置。因此,当在扩展库子目录下运行make时,Makefile会调用下面语句去从父目录下的主Makefile开始编译共享库:
当用户使用make NO_SHARED_LIBS=1编译时,扩展库被编译成静态库,静态库的编译规则被定义在扩展库子目录Makefile中。
子目录下的Makefile一般完成将编译文件添加到列表变量中的工作,编译规则由主Makefile统一完成。另外,它还可以设置子目录下的特殊编译规则。
扩展库子目录Makefile列出如下(在iptables/extensions/Makefile中):
共享库与静态库编译差异
共享库是动态连接库,当应用程序第一次调用共享库时,它会启动共享库的自动装载机制装载共享库,并会自动调用函数_init()初始化共享库,共享库源代码文件含有函数_init()的实现代码。
静态库与普通的目标文件一样,直接被连接进行应用程序,没有自动装载机制,因此,函数_init()不会被调用,也就无法初始化静态库。
为了让静态库模拟动态库的自动装载机制,并统一共享库和静态库的编译,扩展库子目录Makefile生成了initext.c源代码文件, 并通过条件编译宏NO_SHARED_LIBS和_INIT作为开关,在主函数main()调用了静态库初始化函数init_extensions()。 这种由Makefile生成源代码初始化库的方法,屏蔽了共享库和静态库的初始化差异,同时,使得源文件清晰一致。
下面分析静态库模拟动态库的自动装载机制的方法。
1)定义共享库和静态库的变量
共享库的后缀是.so,编译标识是-fPIC,连接标识-rdynamic,库标识是-ldl -lnsl。静态库的后缀是.a,只有连接标识-static。因此,在主Makefile中需要定义不同的标识变量和列表变量。 iptables/Makefile文件与库编译相关的变量定义列出如下:
2)共享库的编译规则
共享库的编译需要使用共享库的编译标识,共享库的编译规则列出如下(在iptables/Rules.make中):
3)静态库模拟自动装载机制及静态库编译规则
静态库编译连接与普通应用程序一样,静态库模拟自动装载机制通过Makefile生成初始化函数来实现的。
扩展库子目录Makefile生成初始化函数的规则列出如下(在iptables/extensions/Makefile中):
上述规则生成源代码程序到initext.c文件,initext.c文件内容列出如下:
4)在源程序中静态库初始化函数声明
由于各个库的初始化函数都是_init(),因此,扩展库子目录Makefile通过宏_INIT将各个库初始化函数_init的名字进行了扩展。静态库的声明函数列出如下(在iptables/include/iptables_common.h中):
5)函数main()调用静态库初始化函数
函数main()必须调用明确地调用静态库的初始化函数,调用代码列出如下(在iptables/iptables_standalone.c中):
main(int argc, char *argv[])
2 automake自动产生Makefile
由于Makefile语法的复杂性,编写一个大型软件包的所有Makefile很困难,而且Makefile语法也很难根据系统的测试进行条件编 译。为了简化Makefile的生成,GNU提供了Autoconfig语言编写编译脚本,Autoconfig语言使用m4宏语言定义了各种测试系统的 宏,用户只需要组合这些宏对系统进行检测,定义需要编译的文件列表,由autoconfig、automake等工具产生Makefile。这样,将编写 Makefile的复杂工作交给工具完成,用户只需要将重心放在检测系统的项目、需要编译文件的列表上。
下面以clamav软件为例说明Autoconfig语言的语法。
2.1 configure脚本产生过程概述
一般的应用程序包都通过运行configure脚本文件来产生Makefile的,应用程序包含有configure脚本。运行"./configure"产生Makefile文件,接着运行"make"编译程序,运行"make instal"安装编译后的程序。
产生configure脚本的GNU工具软件有autoscan、aclocal、autoconf和automake、libtool,它们是用perl语言编写的脚本。分别说明如下:
autoscan检查源代码目录树中是否存在工具软件生成的文件、它们在目录树中的配置是否合理。检查configure.ac文件的完整性,如果不存在或不完全,则生成它的模板文件configure.scan。
aclocal扫描configure.ac文件或configure.in,根据它们的内容产生m4宏定义文件aclocal.m4。
autoconf从configure.ac或configure.in文件产生configure脚本,检查参数及依赖的库、测试库函数、打印实时信息等。
automake从Makefile.am文件产生Makefile.in文件。这样,configure脚本就可以从Makefile.in文件产生Makefile文件。Makefile.am文件存储着用户定义好的宏和目标文件。
libtool用于处理编译共享库的各种规则。
产生configure脚本的步骤如图3,图中带"*"号的字符串表示执行的命令,带"[]"的字符串表示可选的条目。
图3 产生configure脚本的步骤
产生configure脚本的步骤说明如下:
(1)进入源代码根目录,使用命令autoscan生成configure.scan文件。configure.scan文件是configure.in文件模板,用户需要对它进行编辑。
(2)将configure.scan文件改名为configure.in,编辑configure.in文件。
(3)使用aclocal命令生成aclocal.m4文件。
(4)使用autoconf命令生成configure文件。
(5)编辑一个Makefile.am文件,它指定了需要编辑的源文件。使用automake命令生成Makefile.in文件。
(6)使用configure命令生成Makefile文件。
configure脚本运行后产生config.cache、config.log、config.status、target.h、config.h和Makefile文件。这些文件的功能说明如下:
config.cache含有缓存的数据,是一个纯文本文件。configure通过这个文件缓存系统测试的结果,用来加速随后的测试。
config.log包含了configure运行产生的log信息,configure运行时输出消息描述测试和测试的结果,这些消息记录在config.log文件中。
config.status文件是configure产生的shell脚本,运行它将重建当前的配置文件Makefile,但不再检测系 统。如果config.status带选项‘--recheck’运行时,它会重新运行检测并更新config.status脚本本身。
target.h文件从acinclude.m4文件产生,是平台相关的宏定义。acinclude.m4文件由用户创建,含有检测平台信息的命令及宏定义语句。
config.h文件含有软件包配置相关的宏定义。
Makefile文件用于编译源代码。它从Makefile.in文件产生。
2.2 Autoconfig语言
autoconf工具用于产生自动配置软件源代码包的configure脚本,应用于类Unix操作系统。configure脚本在运行时与autoconf工具无关。
为了产生configure脚本,用户需要编辑configure.ac或confugure.in文件,加入关于软件包个性特征的测试、 各种检查、在线打印信息等。autoconf工具对confugure.in文件进行操作产生configure脚本。用户运行configure脚本 时,它就会执行定义的测试和检查,并实时打印信息。configure.ac和confugure.in文件是一样的,由于*.in类型文件含义是 configure的输入文件,为了避免混淆,优先使用configure.ac文件。
configure.ac文件内容用Autoconf语言描述,Autoconf语言建立在m4宏语言的基础上,它将代码当作纯文本,是工具autoconf能识别的语言。
下面说明Autoconfig语言的语法。
(1)初始化和输出文件
编译程序需要用户给出所需要的各种参数,如:软件包头、版本、源代码目录、安装目录、配置头文件等,这些参数需要用户使用Autoconf语言的宏定义书写在configure.in文件中。编译所需要参数的相关宏说明如表2。
表2 与用户设置编译参数相关的宏说明
Makefile的变量替换
每个Makefile文件由Makefile.in文件转换而来,configure脚本将Makefile.in文件中的 "@variable@"替换为configure.in指定的变量的值,这个指定的变量称为输出变量。输出变量用宏AC_SUBST (variable, [value])进行设置。
配置头文件
软件包常需要配置用的头文件,如:clamav-config.h,根据用户编译时的选择对软件包进行配置。软件包源代码包含这个头文件,软件包根据头文件的宏定义决定应该编译的内容。
产生配置头文件的过程是:autoheader工具运行时,它从configure.in中产生clamav-config.h.in;运 行configure工具时,configure工具从clamav-config.h.in产生clamav-config.h。文件clamav- config.h的部分内容列出如下:
clamav软件包中许多源代码文件前面含有配置头文件,还根据配置头文件的宏定义决定使用的库等。例如:clamd.c文件的部分代码列出如下:
在configure.in文件中产生配置头文件及宏定义的语句列出如下:
上面第一个语句表示产生clamav_config.h配置头文件,第二个语句表示如果系统中含有unistd.h文件,则在clamav_config.h文件中加入下面宏定义:
(2)存在测试
软件包所要求的各种特定系统特征可能需要在编译时进行测试,看系统中是否含有这些特征的文件、变量类型和库等。测试的结果应实时地打印消息告诉用户。存在测试的宏说明如表4。
表4 与存在测试相关的宏的功能说明
通用测试
用户可在configure.in中使用通用测试宏AC_CHECK_TYPES测试指定的类型是否存在。例如:在configure.in中的语句如下:
AC_CHECK_TYPES(sys/stat.h)
acheader工具在配置头文件clamav-config.h中产生如下语句,用户也可以手动在其中加入如下语句:
软件包中的源代码文件可以使用这个宏,语句如下:
(3)书写测试的宏
如果Autoconf给定的测试宏不能满足需要,用户可以用给定的宏写测试代码,这些书写测试的宏说明如表5:
表5 用户书写测试代码的宏
用户书写的测试代码运行机制
用户可以使用Autoconf语言的宏在configure.in文件书写测试源程序,源程序分段插入到Autoconf语言的宏中。例如:在 clamav软件包的configure.in文件中,宏AC_CACHE_CHECK用来检测结构msghdr的成员control,如果存 在,configure运行时输出结果yes,否则,输出no。其代码列出如下:
运行autoconf工具后,configure文件中产生如下的代码:
configure脚本运行时,它将需要脚本中的源代码通过cat命令输出到conftest.c文件中,然后编译conftest.c文件生成测试程序,并运行测试程序。运行完程序后,删除conftest.c文件。
(4)测试结果
configure脚本运行时,系统检测结果需要输出到配置头文件或Makefile的变量中,或者实时打印消息供用户查阅。Autoconfig语言提供了测试结果宏对测试结果进行处理。测试结果宏列出如表5。
表5 测试结果输出的宏的说明
定义预处理符号的机制
用户有时需要根据对系统的测试结构在配置头文件中定义宏,以例软件包根据配置头文件的宏定义进行编译。例如:在clamav软件包的configure.in文件中,一个定义预处理符号的宏列出如下:
AC_DEFINE_UNQUOTED(SENDMAIL_BIN, "$sendmailprog", [location of Sendmail binary])
configure脚本根据测试结果$sendmailprog定义宏SENDMAIL_BIN。因为测试结果为0,在配置头文件clamav-config.h中的宏定义语句列出如下:
/* location of Sendmail binary */
/* #undef SENDMAIL_BIN */
输出变量
Makefile文件中会使用到变量,这些变量需要在configure.in中定义,通过Autoconfig宏输出到Makefile文件中。例如:在configure.in文件中,一个输出变量的语句列出如下:
在运行automake工具后产生的Makefile.in文件中产生如下语句,@CFGDIR@在产生Makefile文件时会被在configure.in文件中变量CFGDIR定义的值替换。
运行configure脚本后产生的Makefile文件中产生如下变量定义:
缓存文件
缓存文件config.cache是shell脚本,用来缓存configure测试运行的结果。缺省下configure不使用缓存文件,如果 configure运行带有参数选项--config-cachefile或-C时,检测中间结果会被缓存到config.cache文件中,下次运行 时,可以直接从缓存文件中读取结果以节省运行时间。
configure.in文件可以使用宏AC_CACHE_SAVE将缓存变量存入到缓存文件中去;使用AC_CACHE_LOAD从缓存文件中装载缓存变量值到configure.in文件的变量,如果缓存文件不存在,就创建一个缓存文件config.cache。
打印消息
configure运行时,测试结果需要打印出来供用户查阅。检测的消息提示常用宏AC_MSG_CHECKING给出,这个宏会自动在提 示消息前加"checking",以消息末尾加上"…"。测试结果用宏AC_MSG_RESULT给出。例如,在configure.in中有如下测试结 果打印宏语句:
AC_MSG_CHECKING(for socklen_t)
AC_MSG_RESULT($ac_cv_socklen_t)
configure脚本运行时,会打印如下语句,
checking for socklen_t... yes
2.3 configure.in实例
下面以clamav软件包的configure.in为例说明configure.in的编写方法。
(1)生成configure.scan模板
用命令autoscan生成configure.scan模板,configure.scan模板告诉用户编写哪些方面的测试代码。命令autoscan的运行结果列出如下:
(2)编写configure.in
用户将configure.scan模板文件改名为configure.in,然后编辑configure.in文件,编写测试代码。
编写configure.in文件步骤说明如下:
(1)初始化autoconfig,检查软件包源文件是否存在,初始化automake,定义软件包名和版本号,定义目标平台头文件target.h和配置头文件clamav-config.h。
(2)检查系统是否存在所需要的工具程序,如:编译器、awk、libtool等。
(3)设置软件包配置的预定义变量,如:定义变量SCANBUFF的值为131072,存储到配置头文件clamav-config.h文件中。
(4)检测头文件,如:检查标准C库的头文件是否存在。
(5)检测在系统中数据类型的大小,如:检测数据类型int在系统中所占的字节数。
(6)检测系统中的函数库是否可用。如:通过连接nsl库的函数gethostent来确定库是否可用。
(7)检测系统函数是否可用。
(8)设置configure脚本的命令行参数选项,检查参数选项的值,并实现参数选项的操作。如:对于configure --with-zlib=/usr/local命令行参数选项,configure.in应检查指定路径下的头文件是否存在、zlib库的版本是否正确、 zlib库是否可用等。
(9)检查目标平台,并设置目标平台相关的预定义变量,存储到目标平台头文件target.h中。
(10)检查编译器特定的宏或函数,如:检查C编译器是否支持__attribute__。
(11)定义configure运行的输出,如:各子目录下的Makefile、clamav-config、man文档等。
Autoconfig语言提供了许多测试宏,用户可以使用这些宏进行测试。但一些特定的测试还需要用户自己编写测试源代码,以Autoconfig语言的格式放置在configure.in文件中。
clamav软件包的configure.in内容列出如下:
clamav软件的configure脚本运行的结果部分列出如下:
2.4 automake
automake是从Makefile.am自动产生Makefile.in文件的工具,Makefile.am文件仅包括一些定义源文件或目录的变量,这简化了Makefile的维护工作。
automake定义了Makefile.am使用的automake变量编写语法。automake还提供了公有宏和私有宏,公有宏可以在configure.in文件中使用,私有宏是automake工具本身使用的宏。
下面以clamav软件包的configure.in和Makefile.am说明automake工具的使用方法。
(1)automake变量
automake的变量遵循统一命名方案,以便容易用来编译安装程序。在运行make时,变量决定编译的目标,变量名由几部分连接而成, 如:bin_PROGRAMS,其中告诉编译类别的部分称为主要部分(primary),如:变量的PROGRAMS表示将编译成应用程序,变量是应用程 序的列表;变量的前缀部分用来表示目标文件安装的标准目录,如:变量的bin表示应用程序将安装到bindir下,bindir一般扩展为/bin目录。
当前使用的变量的主要部分(primary)有PROGRAMS、LIBRARIES、LISP、PYTHON、JAVA、SCRIPTS、DATA、HEADERS、MANS和TEXINFOS。
变量的前缀部分可以是标准目录变量,如:bindir、sbindir等,在变量中不写dir字符。用户还可以扩展安装目录,如:htmldir,但这个变量需要有定义。
特殊变量前缀EXTRA_表示变量用来列出目标对象,根据configure的配置来决定是否编译。
派生变量是生成应用程序的源代码文本的列表,如:在变量sbin_PROGRAM中列出的应用程序名clamd可以用来组合成clamd_SOURCES变量,用来存放应用程序的源文件列表。下面是clamd应用程序的变量命名样例:
还有一些Makefile变量保留给用户,由用户给它们设置值,如:CFLAGS、CC等。
(2)automake的辅助程序
automake有时需要一些辅助程序帮助更好的产生Makefile工作。常用的辅助程序说明如下:
config.guess config.sub 这两个程序功能相同,用于获得编译、主机或目标的构架信息,用户应该从ftp://ftp.gnu.org/gnu/config获取最新版本,以支持新内核版本。
depcomp 编译并产生依赖关系信息,可被编译器的自动依赖跟踪特征使用。
texinfo.tex 不是程序,当软件包含有Texinfo源代码时,它是运行make dvi、make ps和make pdf需要的文件。
install-sh 当平台上install不可用时,它是install程序的替代。
例如:clamav软件包中config.guess程序的运行结果列出如下:
^-^$ ./config.guess
x86_64-unknown-linux-gnu
^-^$ ./config.guess -t
2006-07-02 #上次修改的时间
(3)自动产生aclocal.m4
automake定义了许多宏,软件包在编译配置时将使用这些宏。这些宏必须定义在aclocal.m4中,否则,autoconf将无法找到它们。
aclocal工具基于configure.in内容自动产生aclocal.m4文件。它先扫描系统中所有的.m4文件,查找宏定义,接 着,扫描configure.in,任何找到或需要的宏将放入aclocal.m4中。另外,如果acinclude.m4文件存在,它将被自动包含进 aclocal.m4。
(4)automake提供的Autoconf宏
automake定义一些公有宏,又称Autoconf宏,可以用在configure.in文件中,这些宏又称公有宏。这些在运行aclocal时会被展开并包含在aclocal.m4文件中。
下面以clamav软件的configure.in文件为例说明常用的automake宏:
(5)条件编译
条件编译是根据用户在configure.in中设置的条件来决定编译的源代码或链接的目标文件。
条件编译的方法有两种:一种是使用configure.in定义的宏在Makefile.am中判定编译的目标。另一种方法使用变量替换, 由configure.in根据条件定义编译的目标列表变量,这个列表变量替换Makefile.in中的替换变量,如:@HELLO_SYSTEM@。
例1 使用方法一进行条件编译
用户书写Makefile.am时,可以根据条件编译宏定义判断是否编译应用程序。在clamav/clamd/Makefile.am文件中使用条件编译宏定义的语句列出如下:
在configure.in文件中与条件编译相关的语句列出如下:
例2 使用方法二进行条件编译
条件编译的程序源代码文件应放在EXTRA_*变量中,条件编译的目标对象放在*_LDADD变量中,依赖文件放在*_DEPENDENCIES变量中。应用程序hello进行条件编译时的Makefile.am列出如下:
在configure.in文件中建立@HELLO_SYSTEM@ 替代变量,运行configure时,HELLO_SYSTEM将被hello-linux.o 或hello-bsd.o替换。configure.in文件建立替代变量的方法如下:
(5)编译应用程序和静态库
应用程序的安装目录变量有bindir、sbindir、libexecdir、pkglibdir或noinst(表示无安装目录)。它们根据configure --prefix的前缀组装成实现的安装目录,如:/opt/bin。
当链接configure不能找到的库时,可以使用变量LDADD。变量LDADD指定用来链接的附加的对象或库。编译多个应用程序链接库时,可使用变量prog_LDADD指定程序特定的库,prog为程序名。例如:
LDADD = ../lib/libcpio.a @INTLLIBS@
编译静态库的方法与编译应用程序类似,只是编译变量的主要部分由_PROGRAM改为_LIBRARIES。安装目录变量为libdir 或pkglibdir。
clamav软件包的顶层Makefile.am列出如下:
子目录libclamav下的编译静态库的Makefile.am列出如下:
(6)共享库的编译方法
编译共享库需要使用GNU libtool工具,GNU libtool工具封装了平台依赖性和用户接口,将共享和静态库抽象成统一的libtool库,libtool库是用.la作为后缀的文件,直到运行 configure时才最终决定它们是静态还是共享库,由libtool工具生成相应的库。libtool目标文件以.lo作为后缀,它们连接生成 libtool库。
共享库被编译成位置无关代码,编译命令行选项需要加位置无关标识,libtool工具通过使用独立的库对象文件(以.lo代替.o)隐藏了位置无关标识的复杂性,它会自动插入位置无关标识到编译命令行中。
下面是运行libtool工具编译libtool.lo对象的方法,libtool工具调用gcc命令先将foo.c编译成位置无关的foo.o文件,然后再重命名成foo.lo文件。
下面是运行libtool工具链接.lo对象生成库的方法:
automake用_ LTLIBRARIES变量表示运行libtool编译库,libtool根据configure.in中的设置决定编译成静态库还是共享库。例如:创建libgettext.la安装在libdir的语法列出如下:
下面以Linux-PAM软件包说明使用libtool编译库的方法。
在Linux-PAM软件包中,libpam目录下的源代码根据configure.in的条件编译设置,libtool将它们编译成静态库还共享库。libpam目录下的Makefile.am列出如下:
在Linux-PAM软件包中,Linux-PAM/configure.in文件开启libtool,设置了libpam库的条件编译,与libpam库配置相关的代码列出如下:
(7)自动跟踪依赖关系
软件包编译时,通常在源代码文件之间和在源代码文件与所包含的头文件之间存在依赖关系。automake工具可以自动跟踪依赖关系的改变,它使用程 序depcomp计算所有的依赖关系,包括在源代码文件与所包含的头文件之间的依赖关系。当运行automake -a时,它还会将程序depcomp安装到源代码软件包中。
用户可以使用automake -i或者在configure.in中设置变量AUTOMAKE_OPTIONS或者使用configure --disable-dependency-tracking取消自动依赖关系跟踪。
(8)发布
在Makefile.in文件中的dist目标用来产生用gzip压缩的tar文件,发布的tar文件名基于变量PACKAGE和VERSION,文件名为package-version.tar.gz,这两个变量在AM_INIT_AUTOMAKE()中定义。
通常工具automake自动查找用于发布的文件:所有的源代码、Makefile.am和Makefile.in以及automake内建的通用文件。
自动规则没有覆盖的发布文件或目录可以列在变量EXTRA_DIST。还可以使用变量DIST_SUBDIRS设置这些发布文件的子目录。
编译变量还可以加dist前缀表示发布的文件。如:dist_data_DATA = distribute-this。
3 GCC工具及扩展
GCC提供了编译工具,还提供了检查可执行文件的工具。另外,GCC提供了语言扩展以及在源程序中设置GCC编译宏的功能。它们分别说明如下:
3.1 GNU常用工具
GCC(GNU Compiler Colletion,GNU编译程序集合)是开放源代码的编译工具的集合,它可以编译跟踪源代码、编辑文件、控制编译过程和提供调试信息等。GCC可以编译现今流行的各种编程语言,可以支持几乎所有的CPU硬件平台。
GNU开发工具链包括GCC(GNU Compiler Collection,GUN编译程序集合)、binutils工具、gdb调试器和glibc库。GNU常用的工具说明如表3,详细用法查看它们的man文档。
表3 GNU常用的工具
3.2 预处理
(1)预处理指令
源代码中的预处理指令以"#"进行标识,常用的预处理字 有#define、#if、#elif、#else、#ifdef、#ifndef、#include、#line、#pragma、#undef、#warning、#error、## 等。其中,#line将当前的行号替换成指定的行号;#warning发出警告信息,程序继续执行;#error发出致命错误信息,程序退 出;#pragma表示指定编译器特定的信息;##表示两个源代码相连接。
例如:下面是一个头文件的预处理字的应用,它确保头文件只被引用一次。
其他预定义指令的样例列出如下:
(2)预定义宏
GCC提供了预定义宏,用来打印程序的各种特定信息,GCC的一些常用宏说明如表4。
表4 GCC的常用预定义宏
程序可使用这些预定义宏进行打印输出信息等操作,例如:下面是一个打印出错信息的语句。
在源代码中使用下面的语句描述错误:
打印输出到终端的信息类似如下:
3.3 C语言扩展
(1)__alignof__
__alignof__操作返回数据类型或指定数据项的边界对齐。例如:
(2)变长数组
可以动态指定数组大小,例如:
(3)属性
用关键字__attribute__给函数或声明赋属性会下,编译器根据属性值对程序进行相应处理。常用的属性值说明如表5。
表5 __attribute__的常用属性值
例如:下面的语句表示结构blockm以32位边界对齐。
(4)内联函数
内联函数在编译时象宏定义一样被展开,编程时,程序员可将它当作函数看待,常用于使用频繁的短小函数,可以省略函数调用花费的开销。
(5)用typedef创建数据类型变量
typedef可用来定义变量,如:应用程序为了方便平台移植,将数据类型按平台分别进行定义。typedef可用来按数据的类型来定义变量,如:宏定义用它可以作用于多种不同的数据类型。
例1:应用程序为适应WIN32平台和Linux平台而定义了不同的数据类型,方法如下:
例2:下面的宏定义用于a和b相互交换数据,变量_tp和temp被定义为a的类型,这样,这个宏定义可以自动适应多种数据类型。
(6)用typeof引用变量的类型
typeof()表达式用来引用变量的类型定义新的变量,常用于宏定义适应多数据类型。例如:
3.4 C++和C语言混合使用
C++是C语言的功能扩展,它们的区别体现在函数名上,C语言使用简单的函数名,它们的区别是:C++函数将参数类型列表与函数名本身一起作为函数名。
(1)C++中调用C
C++语法通常在头文件中将整个头文件用extern "C"声明为C语言函数。方法如下:
(2)在C中调用C++及其他面向对象语言
C中调用C++时,C++定义的函数格式与C函数一样,C++函数内部用C++实现,但函数定义需要使用extern "C"声明。C中调用C++函数就如同调用C函数一样。C中调用其他面向对象语言(如:Java、Qt)的函数的方法一样。
例如:C++的函数实现如下:
C程序调用C++函数就象调用C函数一样,例如:
3.5 字符串国际化
国际化指程序中与用户交互的字符串支持多种语言。程序源代码只有一个,但与用户交互的字符串有多种。
本地化指程序中与用户交互的字符串根据系统设置的本地语言不同,而显示不同语言但含义相同的字符串。如:系统设置为中国,则用户看到的字符串为中文。
不同的语言封装有不同的函数和工具来处理国际化和本地化,它们的机制类似。下面仅说明C语言的国际化和本地化。
C语言的本地化方法如下:
(1)先使用函数setlocale()指明本地化的项目,然后在程序中使用函数gettext()得到字符串的本地化版本。
(2)程序编写完后,使用工具xgettext从源代码文件中提取函数gettext()中的字符串,生成po文件,如:starter.po。用户还可以使用工具msgmerge合并两个po文件。
(3)用户编辑po文件,将po文件中的字符串进行翻译,每种语言复制一个po文件,并翻译成对应语言的字符串。
(4)po文件翻译完成后,使用工具msgfmt将每个po文件编译成对应的二进制文件,如:starter.mo。
(5)将每个二进制文件拷贝到LOCALEDIR目录的对应语言目录下,如:对于加拿大英语,拷贝到/usr/share/local/en_CA下。这样,程序运行时,函数gettext()能根据本地化设置读取对应的字符串。
例如:一个使用国际化和本地化的C语言程序列出如下:
Linux应用程序编译方法
出自ShangShuWu
现时的
。没有已复审的修订。
跳转到: 导航
, 搜索
1 编写Makefile
Makefile语法较多,下面仅说明常用的语法,并用iptables软件包的Makefile作为实例说明典型的Makefile书写方法。 1.1 Makefile语法概述
(1)编译规则格式Makefile文件主要包含一系列的编译规则,其格式如下:
目标…:依赖文件…
命令
第一行定义目标依赖文件,目标(target)通常指编译后的文件名,如:可执行文件或.o文件,还可以是一些特定的目标。依赖文件是产生目标文件所依赖的文件,通常为源代码文件。一个目标通过依赖多个源文件。
第二行定义make执行的操作,一个规则可以包括多个命令,每个命令占一行,命令行首以Tab键开始,一般是编译命令。正常情况下make在执行命令之前首先打印命令行,称为回显。以‘@’起始的命令行不能回显,‘@’在传输给shell时被丢弃。
依赖关系符可以是":"或"::",当同一个文件作为多个双冒号规则的目标时。这些不同的规则会被独立的处理,而不是像普通规则那样合并所有的依赖到一个目标文件。
GNU make的工作分为两个阶段。在第一阶段,make读取makefile文件,构造所有目标的依赖关系列表。在第二阶段,make对目标进行指定的操作,通常是编译连接操作。
(2)make命令行参数
当执行命令make时,它寻找makefile文件名的次序是:‘GNUmakefile’、‘makefile’和‘Makefile’。还可以用"make -f 文件名"指定makefile文件。
当目标文件已存在时,只有目标文件依赖的文件发生更新时,make才会重新生成这个目标文件。
make命令本身有一些命令行参数,它们是‘-’或‘--’开头,指定make命令的选项,例如:使用‘make –t’命令改变所有目标文件的时间戳。详细的选项说明请查阅《GNU make Manual》。
make命令还有与指定Makefile相关的命令行选项,用于指定最终编译的目标和变量重载。
make命令可以使用Makefile文件中的某一最终目标作为参数,只编译这一目标,例如:make clean、make install、make program,这里program是Makefile中指定的应用程序目标名。
make命令还可以在命令行中重载Makefile文件中的变量,用来改变Makefile中的指定变量值,例如:Makefile文件对 变量CFALAGS进行了设置,命令‘make CFLAGS='-g -O'’可以重载变量CFALAGS,编译时,C编译器都使用‘cc -c -g -O’编译程序,而不使用Makefile中定义的CFALAGS值。
(3)变量赋值
1)变量的定义
变量的定义语法形式如下:
immediate = deferred #表示延迟扩展,即变量在使用时才扩展 immediate := immediate #表示引用的值立即扩展 immediate ?= deferred #表示条件赋值,如果变量没被赋值,这个语句才会给变量赋值 immediate += deferred or immediate #表示变量追加文本
对于追加操作符‘+=’,右边变量如果在前面使用(:=)定义,则变量为是立即扩展变量,否则为延时变量。
2)变量替换引用
变量的替换引用方法如下:
foo := a. o b. o c. o bar := $ ( foo:. o=. c) #替换结果:bar为a.c b.c c.c bar := $ ( foo:%. o=%. c) #替换结果:bar为a.c b.c c.c
3)变量重定义
变量的值还可以用关键字"overrige"进行重新指定,例如:
override VARIABLE := VALUE #重新指定值为VALUE
4)目标指定变量
"目标指定变量(Target-specific Variable)"只在指定它的目标的上下文中有效,不影响其他目标,是目标的局部变量。。例如:
prog : CFLAGS = - g prog : prog. o foo. o bar. o
上面的语句表示目标"prog"及所引发的所有规则,变量"CFLAGS"值都是"-g"。
模式指定变量定义是将一个变量值指定到所有符合特定模式的目标上去。例如:
%. o : CFLAGS += - O
5)隐含变量
Makefile有一些预定义的命令变量和命令行参数变量,这两种变量一般相对应成对出现。表7中列出了常用的命令变量和命令行参数变量。
表7 常用的命令变量和命令行参数变量
命令变量 | 默认值 | 说明 | 命令参数变量 | 默认值 | 说明 |
AR | ar | 函数库打包程序。 | ARFLAGS | rv | AR命令行参数。 |
AS | as | 汇编程序。 | ASFLAGS | 无 | AS命令行参数。 |
CC | cc | C编译程序 | CFLAGS | 无 | C编译器命令行参数。 |
CXX | g++ | C++编译程序。 | CXXFLAGS | 无 | C++编译器命令行参数。 |
CPP | $(CC) -E | C程序的预处理器。 | CPPFLAGS | 无 | C预处理器命令行参数。 |
RM | rm -f | 删除命令。 |
自动化变量的取值由具体的规则决定,对于不同的规则,它的值不同。常用的自动化变量说明如表8。
表8 常用的自动化变量说明
变量符号 | 说明 | 示例 |
$@ | 扩展成当前规则的目标文件名。 | 如:bar.a |
$< | 扩展成依赖列表中的第一个依赖文件。 | 如:lib:foo.o bar.o $<值为foo.o |
$^ | 扩展成整个依赖文件的列表。 | 如:lib:foo.o bar.o $^值为foo.o bar.o |
与$^功能一样,除了保留依赖文件中重复出现的文件。 | ||
$ ' | 只对更新的依赖文件进行操作。 | 如:lib:foo.o bar.o ar r lib $ ' 根据变化的.o文件更新库文件。 |
$* | 代表目标模式中%所代表的部分,还包含目录部分。 | 对于文件dir/a.foo.b,目标模式为a.%.b,则$*值为dir/a.foo |
1)伪目标使用方法
如果目标没有任何依赖,而只有执行动作,则称这个目标为伪目标(phony targets),它只起标签作用。伪目标的定义格式类似如下:
.PHONY : clean clean: rm *. o temp
上面语句使用.PHONY将clean定义为伪目标,这样,make clean命令就可以执行rm操作。如果不将clean定义为伪目标,则当不存在clean文件时,make clean命令不会执行。 伪目标可用于在子目录下递归运行make命令,一个典型的递归执行代码列出如下:
SUBDIRS = foo bar baz .PHONY : subdirs $( SUBDIRS) subdirs: $( SUBDIRS) $( SUBDIRS) : $( MAKE) - C $@ foo: baz
上面语句中第二句定义了伪目标subdirs和$(SUBDIRS),第三句让伪目标subdirs依赖于伪目标$(SUBDIRS),这是因为当伪目标 没有作为任何目标的依赖时,即没有这行时,make命令必须明确指定这个伪目标,才能执行它所定义的命令,如:"make clean"。有了第三句后,,每一次在执行这个规则时伪目标所定义的命令都会被执行,即每次make都会执行$(MAKE) -C $@。最后一句foo: baz表示baz目录必须在foo目录之前编译。
2)伪目标all
Makefile的第一个目标为"all",它是伪目标,称为"终极目标",make命令不带目标选项时,执行的命令是make all。可以用来在一个Makefile中编译多个应用程序。一个Makefile文件用于编译两个应用程序的样例列出如下:
all : prog1 prog2 prog3 .PHONY : all prog1 : prog1. o utils. o cc - o prog1 prog1. o utils. o prog2 : prog2. o cc - o prog2 prog2. o
3)强制目标FORCE 强制目标FORCE表示运行make命令时,它所在的规则定义的命令总会被执行。下面的例子表示每次执行make时,rm命令都会被执行。
clean: FORCE rm $ ( objects ) FORCE:
(5)make递归执行时的变量传递
当在父目录中启动make,并由父目录中的Makefile递归执行子目录的Makefile时,父子目录的Makefile之间常需要进行变量传递。
在默认情况下,只有特殊变量"SHELL"和"MAKEFLAGS"会在上层Makefile和子Makefile之间传递。"MAKEFLAGS"存放着make的命令行选项。
父Makefile使用关键字export可将声明的变量通过make环境变量(只对make的一次执行有效)的方式传递给子 Makefile,子Makefile可通过关键字unexport取消变量的传递。当传递的变量与子Makefile的变量重量时,子Makefile 变量将覆盖父Makefile传递的变量。
在父Makefile中,如果export不带变量,则表示将父Makefile中的所有的变量传递给子Makefile。
父Makefile还可以使用特殊目标".EXPORT_ALL_VARIABLES",将其后的所有变量传递给子Makefile。
(6)自动生成源文件与所包含头文件的依赖关系规则
一个源文件,如:main.c,通常包含许多头文件,例如:一个main.c样例列出如下:
#include #include int main( int argc, char ** argv) { ...... }
编写Makefile文件时,main.o依赖于多个头文件,Makefile应包括下面语句:
main. o: main. c stdio. h stdlib. h
对于含有许多源文件的程序包来说,头文件的依赖关系会变得很复杂,并且Makefile中需要写很多这样的规则,从而使Makefile变得很难维 护。为了避免产生这些问题,编译器提供了自动产生这些规则的命令,C编译器提供了‘-M’开关自动产生依赖规则。例如:main.c产生依赖关系规则的命 令列出如下:
cc -M main.c
上述命令产生的输出列出如下:
main. o: main. c stdio. h stdlib. h string. h libgen. h
编译器提供了自动产生依赖关系规则的命令,Makefile文件使用这些规则的方法是:每一个源程序文件‘name.c’有一个名字为‘name.d’的 Makefile文件和它对应,name.d列出了‘name.o’所依赖的文件。在Makefile文件中,将‘name.c’生成名为 ‘name.d’依赖文件的通用规则列出如下:
%. d: %. c set - e; $ ( CC ) - M $ ( CPPFLAGS ) $< | sed 's//( $*/) /. o[ :]*//1 .o $@ : /g' > $@ ; [ - s $@ ] || rm - f $@
上述规则中,set -e告诉shell如果$(CC)命令运行失败(非零状态退出)立即退出。正常情况下,shell退出时带有最后一个命令在管道中的状态(这里是命令sed),因此make不能注意到编译器产生的非零状态。
$(CC) -M $(CPPFLAGS) $<命令将产生依赖关系规则输出,如:main.o: main.c stdio.h stdlib.h。其中,‘$<’代表了依赖文件‘%.c’。 GNU C编译器可使用‘-MM’代替‘-M’,表示省略了系统头文件的依赖。
sed 's//($*/)/.o[ :]*//1.o $@ : /g'命令是字符串模式替换语句,‘$*’表示目标文件去掉后缀,这里为main;‘/1’代表第1个匹配模式变量($*),即main;‘$@’表示目 标文件%d,这里为main.d;‘-g’表示替换字符串中所有匹配的字符。这个语句完成的功能是将依赖关系输出变换成main.o main.d: main.c stdio.h stdlib.h。
‘> $@’表示将main.o main.d: main.c stdio.h stdlib.h语句存入文件main.d文件中。这样,每个源文件都会生成一个与之对应的Makefile文件,用来表示目标文件对源文件及它所包含的头文件的依赖关系。
接着,Makefie文件包含每个源文件对应的name.d文件,方法如下:
sources = foo. c bar. c include $ ( sources:. c=. d)
这样,Makefile文件透明地解决了每个源文件与所包含头文件的依赖关系,而不需要在Makefile文件中去手动编写。
1.2 iptables软件Makefile实例
iptables软件包的Makefile文件由主目录下的主Makefile和Rules.make、子目录Makefile组成。主Makefile用"include"语句包含Rules.make和子目录Makefile,所有Makefile文件共享变量。主Makefile定义了各种列表变量、编译安装规则、打包方法等,与应用程序相关的各种编译规则基本上都在主Makefile中。
Rules.make包括扩充的编译规则,如果共享库的编译规则、依赖关系的产生规则等。
子目录Makefile一般用来填充主Makefile的文件列表变量,以便由主Makefile或Rules.make完成编译,另外,子目录Makefile还定义子目录下特定的编译规则。
下面分别分析iptables软件包的Makefile。
(1) 主Makefile
主Makefile用来定义应用程序的列表变量、编译标识、安装目录、编译规则等。主Makefile的编写方法说明如下:1)定义软件包版本、安装目录等变量。
2)定义编译命令行标识变量CFLAGS。
3)定义应用程序列表(如:EXTRAS)和应用程序安装列表(如:EXTRA_INSTALLS)等变量。
4)定义库安装目录、库编译标识(如:SH_CFLAGS)和连接标识(如:LDFLAGS)等变量。
5)检测操作系统特性,将平台特性决定的编译标识加到CFLAGS中。
6)定义依赖文件列表变量(如:DEPFILES、EXTRA_DEPENDS),列表包括以目标文件同名、后缀为.d的文件名。
7)定义编译应用程序的依赖关系及编译连接规则。定义应用程序依赖于哪些源文件、目标文件和库列表。
8)定义应用程序的安装方法。
9)定义man文档、头文件、库文件安装方法。
10)定义源文件包打包、生成补丁(patch)文件、生成打包文件校验摘要的方法。
11)定义make clean方法。
12)主Makefile包含子目录的Makefile和Rules.make,以便运行它们,通过包含关系,所有Makefile可以使用相同的变量。
iptables/Makefile说明如下:
TOPLEVEL_INCLUDED= YES ifndef KERNEL_DIR KERNEL_DIR=/ usr/ src/ linux endif IPTABLES_VERSION:= 1. 3. 5 OLD_IPTABLES_VERSION:= 1. 3. 4 #定义安装目录 PREFIX:=/ usr/ local LIBDIR:=$ ( PREFIX ) / lib BINDIR:=$ ( PREFIX ) / sbin MANDIR:=$ ( PREFIX ) / man INCDIR:=$ ( PREFIX ) / include # directory for new iptables releases RELEASE_DIR:=/ tmp #如果ip6.h文件存在, 设置标识表示使用IPV6的iptalbes工具 ifeq ( $ ( shell [ - f / usr/ include / netinet/ ip6. h ] && echo YES) , YES) DO_IPV6:= 1 endif COPT_FLAGS:=- O2 #表示优化级别为2,打开不涉及代码尺寸和运行速度权衡的所有优化 #-I表示搜索目录,-D表示在程序中定义宏:#define IPTABLES_VERSION "$(IPTABLES_VERSION)" # -DDEBUG表示在程序中定义#define DEBUG 1,一般用来进入调试模式 # -g表示输出的调试信息格式可被gdb使用,-Wall表示检查规定的语法,产生相应警告信息 CFLAGS:=$ ( COPT_FLAGS ) - Wall - Wunused - I$ ( KERNEL_DIR ) / include - Iinclude/ - DIPTABLES_VERSION= /" $ ( IPTABLES_VERSION ) /" #-g -DDEBUG #-pg # -DIPTC_DEBUG #在源代码中设置“#define NO_SHARED_LIBS 1” ifdef NO_SHARED_LIBS CFLAGS += - DNO_SHARED_LIBS= 1 endif #定义编译目标列表 EXTRAS+= iptables iptables. o iptables. 8 #定义安装目标列表 EXTRA_INSTALLS+=$ ( DESTDIR ) $ ( BINDIR ) / iptables $ ( DESTDIR ) $ ( MANDIR ) / man8/ iptables. 8 # No longer experimental. ifneq ( $ ( DO_MULTI ) , 1 ) EXTRAS+= iptables- save iptables- restore endif EXTRA_INSTALLS+=$ ( DESTDIR ) $ ( BINDIR ) / iptables- save $ ( DESTDIR ) $ ( BINDIR ) / iptables- restore $ ( DESTDIR ) $ ( MANDIR ) / man8/ iptables- restore. 8 $ ( DESTDIR ) $ ( MANDIR ) / man8/ iptables- save. 8 ifeq ( $ ( DO_IPV6 ) , 1 ) EXTRAS+= ip6tables ip6tables. o ip6tables. 8 EXTRA_INSTALLS+=$ ( DESTDIR ) $ ( BINDIR ) / ip6tables $ ( DESTDIR ) $ ( MANDIR ) / man8/ ip6tables. 8 #定义experimental编译目标列表,用于实验性应用程序 EXTRAS_EXP+= ip6tables- save ip6tables- restore #定义experimental安装目标列表,用于实验性应用程序 EXTRA_INSTALLS_EXP+=$ ( DESTDIR ) $ ( BINDIR ) / ip6tables- save $ ( DESTDIR ) $ ( BINDIR / ip6tables- restore # $(DESTDIR)$(MANDIR)/man8/iptables-restore.8 $(DESTDIR)$(MANDIR)/man8/iptables-save.8 $(DESTDIR)$(MANDIR)/man8/ip6tables-save.8 $(DESTDIR)$(MANDIR)/man8/ip6tables-restore.8 endif # 检测Sparc64平台 ifeq ( $ ( shell uname - m) , sparc64) #使用uname命令查询系统平台是否是sparc64 POINTERTEST:= 1 #编译运行代码,测试用户空间是否32位 32bituser := $ ( shell echo - e "/# include /n /# if !defined(__sparcv9) && !defined(__arch64__) && !defined(_LP64)/n userspace_is_32bit/n /# endif" | $ ( CC ) ( CFLAGS ) - E - | grep userspace_is_32bit) ifdef 32bituser # 内核64位,用户空间32位,定义宏定义 CFLAGS+=- DIPT_MIN_ALIGN= 8 - DKERNEL_64_USERSPACE_32 else #64位系统 EXT_LDFLAGS=- m elf64_sparc endif endif # Alpha平台只有64位用户空间 ifeq ( $ ( shell uname - m) , alpha) POINTERTEST:= 1 endif # 如果不是上面的平台,则进行通用测试 ifneq ( $ ( POINTERTEST ) , 1 ) # 测试判断内核是否64位,而正以32位进行编译 ifeq ( $ ( shell [ - a $ ( KERNEL_DIR ) / include / asm ] && echo YES) , YES) 64bitkernel := $ ( shell echo - e "/# include /n /# if BITS_PER_LONG == 64/n kernel_is_64bits/n /# endif" | $ ( CC ) $ ( CFLAGS ) - D__KERNEL__ - E - | grep kernel_is_64bits) ifdef 64bitkernel 32bituser := $ ( shell echo - e "/# include /n /# if !defined(__arch64__) && !defined(_LP64)/n userspace_is_32bit/n /# endif" | $ ( CC ) $ ( CFLAGS ) - E - | grep userspace_is_32bit) ifdef 32bituser CFLAGS+=- DIPT_MIN_ALIGN= 8 - DKERNEL_64_USERSPACE_32 endif endif else CFLAGS+=- D_UNKNOWN_KERNEL_POINTER_SIZE endif endif ifndef IPT_LIBDIR IPT_LIBDIR:=$ ( LIBDIR ) / iptables #定义库安装路径 endif ifndef NO_SHARED_LIBS #如果是共享库 DEPFILES = $ ( SHARED_LIBS:%. so=%. d) SH_CFLAGS:=$ ( CFLAGS ) - fPIC #编译时加上位置无关标识-fPIC STATIC_LIBS = STATIC6_LIBS = LDFLAGS = - rdynamic #表示链接动态库 LDLIBS = - ldl - lnsl else DEPFILES = $ ( EXT_OBJS:%. o=%. d) STATIC_LIBS = extensions/ libext. a STATIC6_LIBS = extensions/ libext6. a LDFLAGS = - static #表示链接静态库 LDLIBS = endif .PHONY : default #伪目标default存在依赖目标时,将每次make运行时,它都被执行 default: print- extensions all .PHONY : print- extensions print- extensions: #打印变量$(OPTIONALS),即显示iptables的扩展库组成的目标信息 @ [ - n "$(OPTIONALS)" ] && echo Extensions found: $ ( OPTIONALS ) #编译生成iptables.o目标文件 iptables. o: iptables. c #$<代表规则中依赖文件列表的第一个依赖文件,$@代表规则的目标 $ ( CC ) $ ( CFLAGS ) - DIPT_LIB_DIR= /" $ ( IPT_LIBDIR ) /" - c - o $@ $< #编译生成iptables应用程序文件 ifeq ( $ ( DO_MULTI ) , 1 ) #表示支持多功能 iptables: iptables- multi. c iptables- save. c iptables- restore. c iptables- standalone. c iptables. o $ ( STATIC_LIBS ) libiptc/ libiptc. a #$@代表规则的目标,$^代表依赖文件的完整路径名列表 $ ( CC ) $ ( CFLAGS ) - DIPTABLES_MULTI - DIPT_LIB_DIR= /" $ ( IPT_LIBDIR ) /" $ ( LDFLAGS ) - o $@ $^ $ ( LDLIBS ) else iptables: iptables- standalone. c iptables. o $ ( STATIC_LIBS ) libiptc/ libiptc. a $ ( CC ) $ ( CFLAGS ) - DIPT_LIB_DIR= /" $ ( IPT_LIBDIR ) /" $ ( LDFLAGS ) - o $@ $^ $ ( LDLIBS ) endif #安装应用程序,将它拷贝到安装目录 $ ( DESTDIR ) $ ( BINDIR ) / iptables: iptables #‘@’表示回显,mkdir -p 命令表示如果目录不存在时创建,如果已存在就不做操作 @ [ - d $ ( DESTDIR ) $ ( BINDIR ) ] || mkdir - p $ ( DESTDIR ) $ ( BINDIR ) #‘$<’表示第一个依赖文件iptables,‘$@’表示目标,即拷贝目的地文件 cp $< $@ #编译连接生成应用程序iptables-save,‘$^’表示所有依赖文件 iptables- save: iptables- save. c iptables. o $ ( STATIC_LIBS ) libiptc/ libiptc. a $ ( CC ) $ ( CFLAGS ) - DIPT_LIB_DIR= /" $ ( IPT_LIBDIR ) /" $ ( LDFLAGS ) - o $@ $^ $ ( LDLIBS ) ifeq ( $ ( DO_MULTI ) , 1 ) #表示如果iptables支持多功能,就使用它代替iptables-save $ ( DESTDIR ) $ ( BINDIR ) / iptables- save: iptables @ [ - d $ ( DESTDIR ) $ ( BINDIR ) ] || mkdir - p $ ( DESTDIR ) $ ( BINDIR ) #建立符号连接,‘$<’表示第一个依赖文件iptables,‘$@’表示目标文件 ln - sf $< $@ else $ ( DESTDIR ) $ ( BINDIR ) / iptables- save: iptables- save @ [ - d $ ( DESTDIR ) $ ( BINDIR ) ] || mkdir - p $ ( DESTDIR ) $ ( BINDIR ) cp $< $@ endif ...... #安装man文件, $ ( DESTDIR ) $ ( MANDIR ) / man8/%. 8: %. 8 @ [ - d $ ( DESTDIR ) $ ( MANDIR ) / man8 ] || mkdir - p $ ( DESTDIR ) $ ( MANDIR ) / man8 cp $< $@ EXTRA_DEPENDS+= iptables- standalone. d iptables. d #使用编译命令的-M选项生成依赖文件 iptables- standalone. d iptables. d: %. d: %. c #-MG表示缺少的头文件为生成文件,并与源文件在同一目录中。 #‘$<’表示第一个依赖文件iptables,‘$@’表示目标文件,‘$*’为目标去掉后缀部分 #sed命令使用替换模式,其中,s表示替换模式,‘^.*’表示字符开头的任意字符串,例如:将“test.o:”替换为“test.d test.o:” @-$ ( CC ) - M - MG $ ( CFLAGS ) $< | sed - e 's@^.*/. o:@$*.d $*.o:@' > $@ #使用sed生成iptables.8文件 iptables. 8: iptables. 8. in extensions/ libipt_matches. man extensions/ libipt_targets. man #从文件libipt_matches.man过滤出匹配“@MATCH@”行追加到iptables.8.in,并定向输出到iptables.8 #-e表示执行脚本,‘r’表示从另一文件读取行 sed - e '/@MATCH@/ r extensions/libipt_matches.man' - e '/@TARGET@/ r extensions/libipt_targets.man' iptables. 8. in > iptables. 8 ip6tables. 8: ip6tables. 8. in extensions/ libip6t_matches. man extensions/ libip6t_targets. man sed - e '/@MATCH@/ r extensions/libip6t_matches.man' - e '/@TARGET@/ r extensions/libip6t_targets.man' ip6tables. 8. in > ip6tables. 8 # 安装man3文件 .PHONY : install- devel- man3 install- devel- man3: $ ( DEVEL_MAN3 ) @ [ - d $ ( DESTDIR ) $ ( MANDIR ) / man3 ] || mkdir - p $ ( DESTDIR ) $ ( MANDIR ) / man3 @ cp - v $ ( DEVEL_MAN3 ) $ ( DESTDIR ) $ ( MANDIR ) / man3 #安装头文件 .PHONY : install- devel- headers install- devel- headers: $ ( DEVEL_HEADERS ) @ [ - d $ ( DESTDIR ) $ ( INCDIR ) ] || mkdir - p $ ( DESTDIR ) $ ( INCDIR ) @ cp - v $ ( DEVEL_HEADERS ) $ ( DESTDIR ) $ ( INCDIR ) #安装库文件 .PHONY : install- devel- libs install- devel- libs: $ ( DEVEL_LIBS ) @ [ - d $ ( DESTDIR ) $ ( LIBDIR ) ] || mkdir - p $ ( DESTDIR ) $ ( LIBDIR ) @ cp - v $ ( DEVEL_LIBS ) $ ( DESTDIR ) $ ( LIBDIR ) .PHONY : install- devel install- devel: all install- devel- man3 install- devel- headers install- devel- libs .PHONY : distclean distclean: clean #删除以‘~’、‘.rej’、‘.d’结尾的文件,删除文件.makefirst。find命令选项‘-o’表示逻辑或 @ rm - f TAGS `find . - name '*~' - o - name '.*~' ` `find . - name '*.rej' ` `find . - name '*.d' ` . makefirst #定义发布软件包的方法 .PHONY : distrib distrib: check distclean delrelease $ ( RELEASE_DIR ) / iptables-$ ( IPTABLES_VERSION ) . tar. bz2 diff md5sums # nowhitespace # Makefile must not define: # -g -pg -DIPTC_DEBUG .PHONY : check check: #如果变量CFLAGS中含有指定的字符串,将打印提示信息 #egrep扩展grep,选项‘-e’用于保护以‘-’开头的模式。它搜索匹配-g或-pg或IPTC_DEBUG的行 @ if echo $ ( CFLAGS ) | egrep - e '-g|-pg|IPTC_DEBUG' >/ dev/ null; then echo Remove debugging flags; exit 1 ; else exit 0 ; fi .PHONY : nowhitespace nowhitespace: #find从当前目录查找名为Makefile或*.c或*.h文件 #grep从找到的文件中搜索匹配'[ ]$'的行,-n表示grep输出的每行行首为行号 @ if grep - n '[ ]$' `find . - name 'Makefile' - o - name '*.[ch]' `; then exit 1 ; else exit 0 ; fi #删除发布文件 .PHONY : delrelease delrelease: rm - f $ ( RELEASE_DIR ) / iptables-$ ( IPTABLES_VERSION ) . tar. bz2 #将源程序目录打包 $ ( RELEASE_DIR ) / iptables-$ ( IPTABLES_VERSION ) . tar. bz2: #ln -sf建立符号链接 #tar cvf - | bzip2 -9将源代码打包到*.tar.bz2文件,--exclude表示不包括文件列表 cd .. && ln - sf iptables iptables-$ ( IPTABLES_VERSION ) && tar cvf - -- exclude . svn iptables-$ ( IPTABLES_VERSION ) /. | bzip2 - 9 > $@ && rm iptables-$ ( IPTABLES_VERSION ) .PHONY : diff diff: $ ( RELEASE_DIR ) / iptables-$ ( IPTABLES_VERSION ) . tar. bz2 @ mkdir / tmp/ diffdir #在临时目录下解开压缩文件,生成iptables-$(IPTABLES_VERSION)目录 @ cd / tmp/ diffdir && tar - x -- bzip2 - f $ ( RELEASE_DIR ) / iptables-$ ( IPTABLES_VERSION ) . tar. bz2 #用tar在临时目录下解开压缩文件,生成iptables-$(OLD_IPTABLES_VERSION)目录 #接着用diff -urN比较两个目录中的文件差异,生成补丁文件,然后,用bzip2 -9将补丁文件打包 @ set - e; cd / tmp/ diffdir; tar - x -- bzip2 - f $ ( RELEASE_DIR ) / iptables-$ ( OLD_IPTABLES_VERSION ) . tar. bz2; echo Creating patch- iptables-$ ( OLD_IPTABLES_VERSION ) -$ ( IPTABLES_VERSION ) . bz2; diff - urN iptables-$ ( OLD_IPTABLES_VERSION ) iptables-$ ( IPTABLES_VERSION ) | bzip2 - 9 > $ ( RELEASE_DIR ) / patch- iptables-$ ( OLD_IPTABLES_VERSION ) -$ ( IPTABLES_VERSION ) . bz2 #删除临时目录 @ rm - rf / tmp/ diffdir #使用md5sum产生软件压缩包的完整性校验摘要 .PHONY : md5sums md5sums: cd $ ( RELEASE_DIR ) / && md5sum patch- iptables-*-$ ( IPTABLES_VERSION ) . bz2 iptables-$ ( IPTABLES_VERSION ) . tar. bz2 # 将各子目录下的Makefile包括进来,用来运行子目录下Makefile include $ ( shell echo */ Makefile) #运行Rules.make include Rules. make
(2)Rules.make
Rules.make定义扩展库的清除规则、共享库的编译规则、依赖文件的生成规则、tag文件的生成规则,其列出如下(在iptables/Rules.make中):#! /usr/bin/make #告诉操作系统这个文件应由/usr/bin/make处理 all: $ ( SHARED_LIBS ) $ ( EXTRAS ) experimental: $ ( EXTRAS_EXP ) #必须清除不再存在的扩展库文件 clean: $ ( EXTRA_CLEANS ) rm - f $ ( SHARED_LIBS ) $ ( EXTRAS ) $ ( EXTRAS_EXP ) $ ( SHARED_LIBS:%. so=% _sh. o) rm - f extensions/ initext. c extensions/ initext6. c @ find . - name '*.[ao]' - o - name '*.so' | xargs rm - f #删除旧的iptables文件,以便安装到新的目录位置 install: all $ ( EXTRA_INSTALLS ) @ if [ - f / usr/ local/ bin/ iptables - a "$(BINDIR)" = "/usr/local/sbin" ] ; / then echo 'Erasing iptables from old location (now /usr/local/sbin).' ; / rm - f / usr/ local/ bin/ iptables; / fi install- experimental: $ ( EXTRA_INSTALLS_EXP ) #产生vim工具使用的tag文件,将多个源文件产生列表标签,便于从多个源文件中查找字符串 TAGS: @ rm - f $@ #使用命令etags产生tag文件 find . - name '*.[ch]' | xargs etags - a #删除依赖性文件,依赖性文件将在下一次运行make时产生 dep: $ ( DEPFILES ) $ ( EXTRA_DEPENDS ) @ echo Dependencies will be generated on next make. rm - f $ ( DEPFILES ) $ ( EXTRA_DEPENDS ) . makefirst #给每个源文件产生依赖关系,并存入依赖关系文件。目标文件名结尾.o变换成以_sh.o结尾 $ ( SHARED_LIBS:%. so=%. d) : %. d: %. c @-$ ( CC ) - M - MG $ ( CFLAGS ) $< | / sed - e 's@^.*/. o:@$*.d $*_sh.o:@' > $@ #连接成共享库 $ ( SHARED_LIBS ) : %. so : % _sh. o $ ( LD ) - shared $ ( EXT_LDFLAGS ) - o $@ $< #共享库的源文件编译成共享库目标文件,$@表示%_sh.o,$<表示%.c % _sh. o : %. c $ ( CC ) $ ( SH_CFLAGS ) - o $@ - c $< . makefirst: @ echo Making dependencies: please wait... #用touch更新.makefirst的访问时间 @ touch . makefirst #当依赖关系完全变形出错时,删除依赖关系文件,打印提示信息 #双冒号规则将具有相同目标的多条规则相互分离,每一条双冒号规则独立运行,就像这些规则的目标不同一样 %. h:: @ echo Something wrong... deleting dependencies. @- rm - f $ ( DEPFILES ) $ ( EXTRA_DEPENDS ) . makefirst @ [ - d $ ( KERNEL_DIR ) / include / linux/ netfilter_ipv4 ] || echo - e '/n /n Please try `make KERNEL_DIR=path-to-correct-kernel' /' . '/n /n ' @ exit 1 #-include与include相同,不同点是-include忽略不存在的makefile文件,而include对文件不存在产生错误信息 - include $ ( DEPFILES ) $ ( EXTRA_DEPENDS ) - include . makefirst
(3)扩展库子目录Makefile
扩展子目录iptables/extensions/放置各个扩展库的源文件,编译时可根据选项将源文件编译成多个共享库或静态库。默认情况下,这个子目录下的各个源文件被编译成动态库,编译动态库的标识的主Makefile中设置,编译规则在Rule.make中设置。因此,当在扩展库子目录下运行make时,Makefile会调用下面语句去从父目录下的主Makefile开始编译共享库:
local: cd .. && $ ( MAKE ) $ ( SHARED_LIBS )
当用户使用make NO_SHARED_LIBS=1编译时,扩展库被编译成静态库,静态库的编译规则被定义在扩展库子目录Makefile中。
子目录下的Makefile一般完成将编译文件添加到列表变量中的工作,编译规则由主Makefile统一完成。另外,它还可以设置子目录下的特殊编译规则。
扩展库子目录Makefile列出如下(在iptables/extensions/Makefile中):
#! /usr/bin/make #定义必须编译的共享库列表 PF_EXT_SLIB:= ah addrtype ...... TRACE TTL ULOG PF6_EXT_SLIB:= connmark eui64 ...... NFQUEUE MARK TRACE # 定义可选编译的共享库列表 #函数foreach形式为$(foreach VAR,LIST,TEXT),表示将LIST中每个成员放在变量VAR中,TEXT使用VAR进行运行,并将每个TEXT运算结果返回 #函数wildcard是获取匹配模式文件名函数,将通配符号(如:*)文件名进行扩展 #例如:将extensions/.account-test扩展为$(KERNEL_DIR) extensions/.account-test PF_EXT_SLIB_OPTS:=$ ( foreach T,$ ( wildcard extensions/.*- test) ,$ ( shell KERNEL_DIR=$ ( KERNEL_DIR ) $ ( T ) ) ) PF6_EXT_SLIB_OPTS:=$ ( foreach T,$ ( wildcard extensions/.*- test6) ,$ ( shell KERNEL_DIR=$ ( KERNEL_DIR ) $ ( T ) ) ) #将extensions/libipt_*.c扩展后文件名使用模式替换,替换为%,如:从extensions/libipt_ah.c中得到ah PF_EXT_ALL_SLIB:=$ ( patsubst extensions/ libipt_%. c, %, $ ( wildcard extensions/ libipt_*. c) ) PF6_EXT_ALL_SLIB:=$ ( patsubst extensions/ libip6t_%. c, %, $ ( wildcard extensions/ libip6t_*. c) ) ...... PF_EXT_SLIB+=$ ( PF_EXT_SLIB_OPTS ) PF6_EXT_SLIB+=$ ( PF6_EXT_SLIB_OPTS ) OPTIONALS+=$ ( patsubst %, IPv4:%,$ ( PF_EXT_SLIB_OPTS ) ) OPTIONALS+=$ ( patsubst %, IPv6:%,$ ( PF6_EXT_SLIB_OPTS ) ) ifndef NO_SHARED_LIBS #列表中加入各个共享库名,如:extensions/libipt_ah.so SHARED_LIBS+=$ ( foreach T,$ ( PF_EXT_SLIB ) , extensions/ libipt_$ ( T ) . so) EXTRA_INSTALLS+=$ ( foreach T, $ ( PF_EXT_SLIB ) , $ ( DESTDIR ) $ ( LIBDIR ) / iptables/ libipt_$ ( T ) . so) ifeq ( $ ( DO_IPV6 ) , 1 ) SHARED_LIBS+=$ ( foreach T,$ ( PF6_EXT_SLIB ) , extensions/ libip6t_$ ( T ) . so) EXTRA_INSTALLS+=$ ( foreach T, $ ( PF6_EXT_SLIB ) , $ ( DESTDIR ) $ ( LIBDIR ) / iptables/ libip6t_$ ( T ) . so) endif else # NO_SHARED_LIBS EXT_OBJS+=$ ( foreach T,$ ( PF_EXT_SLIB ) , extensions/ libipt_$ ( T ) . o) EXT_FUNC+=$ ( foreach T,$ ( PF_EXT_SLIB ) , ipt_$ ( T ) ) EXT_OBJS+= extensions/ initext. o ifeq ( $ ( DO_IPV6 ) , 1 ) EXT6_OBJS+=$ ( foreach T,$ ( PF6_EXT_SLIB ) , extensions/ libip6t_$ ( T ) . o) EXT6_FUNC+=$ ( foreach T,$ ( PF6_EXT_SLIB ) , ip6t_$ ( T ) ) EXT6_OBJS+= extensions/ initext6. o endif # DO_IPV6 endif # NO_SHARED_LIBS #如果没有使用主Makefile,如:从子目录调用make,则从父目录运行make $(SHARED_LIBS)产生共享库 ifndef TOPLEVEL_INCLUDED local: #在父目录运行make $(SHARED_LIBS),编译共享库 cd .. && $ ( MAKE ) $ ( SHARED_LIBS ) endif #编译静态库的目标对象 ifdef NO_SHARED_LIBS extensions/ libext. a: $ ( EXT_OBJS ) rm - f $@ ; ar crv $@ $ ( EXT_OBJS ) extensions/ libext6. a: $ ( EXT6_OBJS ) rm - f $@ ; ar crv $@ $ ( EXT6_OBJS ) extensions/ initext. o: extensions/ initext. c extensions/ initext6. o: extensions/ initext6. c extensions/ initext. c: extensions/ Makefile #加入各个库的初始化函数到initext.c文件 echo "" > $@ for i in $ ( EXT_FUNC ) ; do / echo "extern void ${i}_init(void);" >> $@ ; / done echo "void init_extensions(void) {" >> $@ for i in $ ( EXT_FUNC ) ; do / echo " ${i}_init();" >> $@ ; / done echo "}" >> $@ ...... #编译目标文件 #动态库装载时自动调用_init()函数,而静态库需要定义_INIT extensions/ lib%. o: extensions/ lib%. c #定义宏#define _INIT ipt_ah_init $ ( CC ) $ ( CFLAGS ) - D_INIT= $* _init - c - o $@ $< endif ...... #共享库的安装 $ ( DESTDIR ) $ ( LIBDIR ) / iptables/ libipt_%. so: extensions/ libipt_%. so @ [ - d $ ( DESTDIR ) $ ( LIBDIR ) / iptables ] || mkdir - p $ ( DESTDIR ) $ ( LIBDIR ) / iptables cp $< $@ $ ( DESTDIR ) $ ( LIBDIR ) / iptables/ libip6t_%. so: extensions/ libip6t_%. so @ [ - d $ ( DESTDIR ) $ ( LIBDIR ) / iptables ] || mkdir - p $ ( DESTDIR ) $ ( LIBDIR ) / iptables cp $< $@
共享库与静态库编译差异
共享库是动态连接库,当应用程序第一次调用共享库时,它会启动共享库的自动装载机制装载共享库,并会自动调用函数_init()初始化共享库,共享库源代码文件含有函数_init()的实现代码。静态库与普通的目标文件一样,直接被连接进行应用程序,没有自动装载机制,因此,函数_init()不会被调用,也就无法初始化静态库。
为了让静态库模拟动态库的自动装载机制,并统一共享库和静态库的编译,扩展库子目录Makefile生成了initext.c源代码文件, 并通过条件编译宏NO_SHARED_LIBS和_INIT作为开关,在主函数main()调用了静态库初始化函数init_extensions()。 这种由Makefile生成源代码初始化库的方法,屏蔽了共享库和静态库的初始化差异,同时,使得源文件清晰一致。
下面分析静态库模拟动态库的自动装载机制的方法。
1)定义共享库和静态库的变量
共享库的后缀是.so,编译标识是-fPIC,连接标识-rdynamic,库标识是-ldl -lnsl。静态库的后缀是.a,只有连接标识-static。因此,在主Makefile中需要定义不同的标识变量和列表变量。 iptables/Makefile文件与库编译相关的变量定义列出如下:
ifdef NO_SHARED_LIBS CFLAGS += - DNO_SHARED_LIBS= 1 #定义宏 #define NO_SHARED_LIBS 1 endif ifndef NO_SHARED_LIBS #共享库的变量定义 DEPFILES = $ ( SHARED_LIBS:%. so=%. d) #依赖关系文件列表 SH_CFLAGS:=$ ( CFLAGS ) - fPIC STATIC_LIBS = STATIC6_LIBS = LDFLAGS = - rdynamic LDLIBS = - ldl - lnsl else #静态库的变量定义 DEPFILES = $ ( EXT_OBJS:%. o=%. d) STATIC_LIBS = extensions/ libext. a STATIC6_LIBS = extensions/ libext6. a LDFLAGS = - static LDLIBS = endif
2)共享库的编译规则
共享库的编译需要使用共享库的编译标识,共享库的编译规则列出如下(在iptables/Rules.make中):
$ ( SHARED_LIBS ) : %. so : % _sh. o $ ( LD ) - shared $ ( EXT_LDFLAGS ) - o $@ $< % _sh. o : %. c $ ( CC ) $ ( SH_CFLAGS ) - o $@ - c $<
3)静态库模拟自动装载机制及静态库编译规则
静态库编译连接与普通应用程序一样,静态库模拟自动装载机制通过Makefile生成初始化函数来实现的。
扩展库子目录Makefile生成初始化函数的规则列出如下(在iptables/extensions/Makefile中):
ifdef NO_SHARED_LIBS ...... extensions/ initext. c: extensions/ Makefile echo "" > $@ for i in $ ( EXT_FUNC ) ; do / echo "extern void ${i}_init(void);" >> $@ ; / done echo "void init_extensions(void) {" >> $@ for i in $ ( EXT_FUNC ) ; do / echo " ${i}_init();" >> $@ ; / done echo "}" >> $@ extensions/ lib%. o: extensions/ lib%. c #编译目标文件 #定义宏#define _INIT ipt_ah_init $ ( CC ) $ ( CFLAGS ) - D_INIT= $* _init - c - o $@ $< endif
上述规则生成源代码程序到initext.c文件,initext.c文件内容列出如下:
extern void ipt_ah_init( void ) ; extern void ipt_tcp_init( void ) ; ...... //省略了其他库的初始化函数 void init_extensions( void ) { ipt_ah_init( ) ; ipt_tcp_init( ) ; ...... }
4)在源程序中静态库初始化函数声明
由于各个库的初始化函数都是_init(),因此,扩展库子目录Makefile通过宏_INIT将各个库初始化函数_init的名字进行了扩展。静态库的声明函数列出如下(在iptables/include/iptables_common.h中):
#ifdef NO_SHARED_LIBS # ifdef _INIT # define _init _INIT # endif extern void init_extensions( void ) ; #endif
5)函数main()调用静态库初始化函数
函数main()必须调用明确地调用静态库的初始化函数,调用代码列出如下(在iptables/iptables_standalone.c中):
main(int argc, char *argv[])
main( int argc, char * argv[ ] ) { ...... #ifdef NO_SHARED_LIBS init_extensions( ) ; #endif ...... }
2 automake自动产生Makefile
由于Makefile语法的复杂性,编写一个大型软件包的所有Makefile很困难,而且Makefile语法也很难根据系统的测试进行条件编 译。为了简化Makefile的生成,GNU提供了Autoconfig语言编写编译脚本,Autoconfig语言使用m4宏语言定义了各种测试系统的 宏,用户只需要组合这些宏对系统进行检测,定义需要编译的文件列表,由autoconfig、automake等工具产生Makefile。这样,将编写 Makefile的复杂工作交给工具完成,用户只需要将重心放在检测系统的项目、需要编译文件的列表上。下面以clamav软件为例说明Autoconfig语言的语法。
2.1 configure脚本产生过程概述
一般的应用程序包都通过运行configure脚本文件来产生Makefile的,应用程序包含有configure脚本。运行"./configure"产生Makefile文件,接着运行"make"编译程序,运行"make instal"安装编译后的程序。产生configure脚本的GNU工具软件有autoscan、aclocal、autoconf和automake、libtool,它们是用perl语言编写的脚本。分别说明如下:
autoscan检查源代码目录树中是否存在工具软件生成的文件、它们在目录树中的配置是否合理。检查configure.ac文件的完整性,如果不存在或不完全,则生成它的模板文件configure.scan。
aclocal扫描configure.ac文件或configure.in,根据它们的内容产生m4宏定义文件aclocal.m4。
autoconf从configure.ac或configure.in文件产生configure脚本,检查参数及依赖的库、测试库函数、打印实时信息等。
automake从Makefile.am文件产生Makefile.in文件。这样,configure脚本就可以从Makefile.in文件产生Makefile文件。Makefile.am文件存储着用户定义好的宏和目标文件。
libtool用于处理编译共享库的各种规则。
产生configure脚本的步骤如图3,图中带"*"号的字符串表示执行的命令,带"[]"的字符串表示可选的条目。
图3 产生configure脚本的步骤
产生configure脚本的步骤说明如下:
(1)进入源代码根目录,使用命令autoscan生成configure.scan文件。configure.scan文件是configure.in文件模板,用户需要对它进行编辑。
(2)将configure.scan文件改名为configure.in,编辑configure.in文件。
(3)使用aclocal命令生成aclocal.m4文件。
(4)使用autoconf命令生成configure文件。
(5)编辑一个Makefile.am文件,它指定了需要编辑的源文件。使用automake命令生成Makefile.in文件。
(6)使用configure命令生成Makefile文件。
configure脚本运行后产生config.cache、config.log、config.status、target.h、config.h和Makefile文件。这些文件的功能说明如下:
config.cache含有缓存的数据,是一个纯文本文件。configure通过这个文件缓存系统测试的结果,用来加速随后的测试。
config.log包含了configure运行产生的log信息,configure运行时输出消息描述测试和测试的结果,这些消息记录在config.log文件中。
config.status文件是configure产生的shell脚本,运行它将重建当前的配置文件Makefile,但不再检测系 统。如果config.status带选项‘--recheck’运行时,它会重新运行检测并更新config.status脚本本身。
target.h文件从acinclude.m4文件产生,是平台相关的宏定义。acinclude.m4文件由用户创建,含有检测平台信息的命令及宏定义语句。
config.h文件含有软件包配置相关的宏定义。
Makefile文件用于编译源代码。它从Makefile.in文件产生。
2.2 Autoconfig语言
autoconf工具用于产生自动配置软件源代码包的configure脚本,应用于类Unix操作系统。configure脚本在运行时与autoconf工具无关。为了产生configure脚本,用户需要编辑configure.ac或confugure.in文件,加入关于软件包个性特征的测试、 各种检查、在线打印信息等。autoconf工具对confugure.in文件进行操作产生configure脚本。用户运行configure脚本 时,它就会执行定义的测试和检查,并实时打印信息。configure.ac和confugure.in文件是一样的,由于*.in类型文件含义是 configure的输入文件,为了避免混淆,优先使用configure.ac文件。
configure.ac文件内容用Autoconf语言描述,Autoconf语言建立在m4宏语言的基础上,它将代码当作纯文本,是工具autoconf能识别的语言。
下面说明Autoconfig语言的语法。
(1)初始化和输出文件
编译程序需要用户给出所需要的各种参数,如:软件包头、版本、源代码目录、安装目录、配置头文件等,这些参数需要用户使用Autoconf语言的宏定义书写在configure.in文件中。编译所需要参数的相关宏说明如表2。表2 与用户设置编译参数相关的宏说明
输出类型 | 宏定义 | 功能说明 |
初始化 | AC_INIT (package, version, [bug-report], [tarname]) | 首先调用这进行初始化,并设置软件包名和版本号。仅有一个参数时,还用来检查源代码是否存在。如:AC_INIT(clamscan/clamscan.c) |
版本提示 | AC_PREREQ (version) | 确保autoconf使用正确的版本。 |
AC_COPYRIGHT (copyright-notice) | 在configure文件前显示版权。 | |
AC_REVISION (revision-info) | 在configure文件的前面显示版本信息。 | |
查找输入 | AC_CONFIG_SRCDIR (unique-file-in-source-dir) | 通过检查文件判断源代码是否存在。 |
AC_CONFIG_AUX_DIR (dir) | 在指定目录dir中使用辅助工具,如:automake等。 | |
输出文件 | AC_OUTPUT(Makefile) | 指定输出的Makefile文件。 |
AC_PROG_MAKE_SET | 定义SET_MAKE到"MAKE=make"。 | |
创建配置文件 | AC_CONFIG_FILES (file..., [cmds], [init-cmds]) | 从输入文件file.in创建文件file,替换输出变量值。 |
输出变量 | 预置的输出变量有CFLAGS、configure_input、CPPFLAGS、CXXFLAGS、LIBS、srcdir等 | 是Autoconf预置的输出变量,与编译的规则、源代码有关。 |
安装目录变量有bindir、libdir、prefix、sbindir、sysconfdir等。 | 与软件安装有关的输出变量。 | |
编译目录的变量有VPATH。 | 指编译时查找文件的路径。 | |
配置头文件 | AC_CONFIG_HEADERS (header ..., [cmds], [init-cmds]) | 定义配置头文件header。 |
AH_VERBATIM (key, template) | 按template定义定义宏,例如: AH_VERBATIM([_GNU_SOURCE], [#ifndef _GNU_SOURCE # define _GNU_SOURCE #endif]) | |
AH_TEMPLATE (key, description) | 定义宏并加上注释description,例如: AH_TEMPLATE([CRAY_STACKSEG_END], [test]) 在配置头文件中产生代码: /* test*/ #undef CRAY_STACKSEG_END | |
AH_TOP (text) | 在配置头文件前面加上文本text。 | |
AH_BOTTOM (text) | 在配置头文件后面加上文本text。 | |
运行任意配置命令 | AC_CONFIG_COMMANDS (tag..., [cmds], [init-cmds]) | 在config.status后运行,用shell命令从configure初始化任何变量。 |
AC_CONFIG_COMMANDS_PRE (cmds) | 在config.status前运行cmd。 | |
AC_CONFIG_COMMANDS_POST (cmds) | 在config.status后运行cmd。 | |
创建配置链接 | AC_CONFIG_LINKS (dest:source..., [cmds], [init-cmds]) | 创建source的符号链接dest。 |
配置子目录中其他包 | AC_CONFIG_SUBDIRS (dir ...) | 在子目录dir中运行configure。 |
缺省的安装目录路径前缀prefix | AC_PREFIX_DEFAULT (prefix) | 设置缺省的安装前缀prefix代替/usr/local。 |
AC_PREFIX_PROGRAM (program) | 如果用户没有指定前缀,从PATH中查找program,如果找到,使用program的父目录作为前缀,否则使用缺省的/usr/local作为前缀。 |
每个Makefile文件由Makefile.in文件转换而来,configure脚本将Makefile.in文件中的 "@variable@"替换为configure.in指定的变量的值,这个指定的变量称为输出变量。输出变量用宏AC_SUBST (variable, [value])进行设置。
配置头文件
软件包常需要配置用的头文件,如:clamav-config.h,根据用户编译时的选择对软件包进行配置。软件包源代码包含这个头文件,软件包根据头文件的宏定义决定应该编译的内容。
产生配置头文件的过程是:autoheader工具运行时,它从configure.in中产生clamav-config.h.in;运 行configure工具时,configure工具从clamav-config.h.in产生clamav-config.h。文件clamav- config.h的部分内容列出如下:
/* "build clamd" */ #define BUILD_CLAMD 1 /* name of the clamav group */ #define CLAMAVGROUP "clamav" /* name of the clamav user */ #define CLAMAVUSER "clamav" /* enable clamuko */ #define CLAMUKO 1 /* enable debugging */ /* #undef CL_DEBUG */ /* enable experimental code */ /* #undef CL_EXPERIMENTAL */ /* thread safe */ #define CL_THREAD_SAFE 1 /* where to look for the config file */ #define CONFDIR "/usr/local/etc"
clamav软件包中许多源代码文件前面含有配置头文件,还根据配置头文件的宏定义决定使用的库等。例如:clamd.c文件的部分代码列出如下:
#ifdef _MSC_VER //这个变量在配置头文件中有定义 #include #endif #if HAVE_CONFIG_H #include "clamav-config.h" //配置头文件 #endif …… #ifdef HAVE_UNISTD_H //这个变量在配置头文件中有定义 #include #include #endif …… #ifndef C_WINDOWS //这个变量在配置头文件中有定义 #include #include #endif #if defined(USE_SYSLOG) && !defined(C_AIX) #include #endif #ifdef C_LINUX #include #endif
在configure.in文件中产生配置头文件及宏定义的语句列出如下:
AC_CONFIG_HEADER( clamav_config.h ) AC_CHECK_HEADERS( unistd.h )
上面第一个语句表示产生clamav_config.h配置头文件,第二个语句表示如果系统中含有unistd.h文件,则在clamav_config.h文件中加入下面宏定义:
#define HAVE_UNISTD_H 1
(2)存在测试
软件包所要求的各种特定系统特征可能需要在编译时进行测试,看系统中是否含有这些特征的文件、变量类型和库等。测试的结果应实时地打印消息告诉用户。存在测试的宏说明如表4。表4 与存在测试相关的宏的功能说明
宏类型 | 宏定义 | 说明 |
通用检查 | AC_CHECK_TYPES(struct $Expensive*) | 检测类型成功,在配置头文件中加入语句: #define HAVE_STRUCT_EXPENSIVEP 1 "*"表示末尾加"P" |
程序检查 | 特定程序检查宏有AC_PROG_AWK、AC_PROG_EGREP、AC_PROG_LEX、AC_PROG_LN_S、AC_PROG_YACC | 查找特殊的应用程序,如果找到,设置变量。例如:如果找到awk,设置输出变量AWK为awk。 |
检查一般程序和文件的宏有AC_CHECK_PROG(…)、AC_CHECK_PROGS(…)、AC_CHECK_TOOL (…)等 | 在系统中查找指定参数的程序或文件,根据查找结果,执行对应参数的指定操作。 | |
文件检查 | AC_CHECK_FILE(…)和AC_CHECK_FILES(…) | 检查文件是否存在,并执行相应的操作。 |
库检查 | AC_CHECK_LIB(…)和AC_SEARCH_LIBS(…) | 检查库文件是否存在,并执行相应的操作。 |
库函数检查 | 检查特殊函数的宏有AC_FUNC_ALLOCA、AC_FUNC_CHOWN、AC_FUNC_FORK、AC_FUNC_FSEEKO、AC_FUNC_MALLOC、AC_FUNC_MMAP等。 | 检查库函数是否存在,如果库函数存在,在配置头文件中定义相应的宏。 |
通用库函数检查的宏有AC_CHECK_FUNC(…)和AC_CHECK_FUNCS(…)。 | 检查指定的库函数是否存在,并执行相应的操作。 | |
设置库文件和目标列表的宏有AC_LIBOBJ(…)、AC_LIBSOURCE(file)、AC_LIBSOURCES(files)、AC_CONFIG_LIBOBJ_DIR(directory)等。 | 跟踪源文件,加它们的目标到目标列表LIBOBJ中。如: AC_LIBSOURCES([foo.c, bar.c]) AC_LIBOBJ($foo_or_bar) | |
头文件检查 | 检查特殊的头文件的宏有AC_HEADER_DIRENT、AC_HEADER_STAT、AC_HEADER_STDC、AC_HEADER_TIME等。 | 检查C库特殊的头文件是否存在,如果存在,在配置头文件中定义相应的宏。 |
一般头文件检查的宏有AC_CHECK_HEADER(…)和AC_CHECK_HEADERS(…)。 | 检查参数指定的头文件,并进行参数指定的操作。 | |
检查函数或变量符号 | AC_CHECK_DECL (…)和AC_CHECK_DECLS(…) | 检查参数指定的符号是否存在,并执行参数指定的操作。 |
结构成员检查 | 检查特殊结构成员的宏有AC_STRUCT_ST_BLKSIZE、AC_STRUCT_ST_RDEV等。 | 检查特殊的结构成员,如果存在,在配置头文件中定义相应的宏。 |
检查通用结构成员的宏有AC_CHECK_MEMBER(…)和AC_CHECK_MEMBERS(…)。 | 检查参数指定的结构成员,并执行参数指定的操作。 | |
数据类型检查 | 检查特殊类型的宏有AC_TYPE_MODE_T、AC_TYPE_OFF_T、AC_TYPE_PID_T、AC_TYPE_SIZE_T等。 | 检查C语言特殊的数据类型,如果存在,在配置头文件中定义相应的宏。 |
通用类型检查的宏有AC_CHECK_TYPE(…)和AC_CHECK_TYPES(…)。 | 检查参数指定的数据类型,并执行参数指定的操作。 | |
编译器检查 | AC_CHECK_SIZEOF(…) | 检查数据类型所占字节大小,并定义宏。 |
C编译器特征检查的宏有AC_PROG_CC(…)、AC_PROG_CC_C_O、AC_C_CONST、AC_C_INLINE等。 | 检查C编译器的特征,并设置变量。如:AC_PROG_CC查找编译器,例如找到gcc,就将CC设置到gcc。 | |
C++编译器特征检查的宏有AC_PROG_CXX(…)和AC_PROG_CXXCPP。 | 检查C++编译器的特征,并设置变量。 | |
系统服务检查 | AC_PATH_X、AC_SYS_LONG_FILE_NAMES、AC_SYS_POSIX_TERMIOS等。 | 检查系统是否提供宏指定的服务,如果提供,就设置变量为yes或者设置宏定义。 |
Unix变量检查 | AC_AIX、AC_GNU_SOURCE、AC_ISC_POSIX、AC_MINIX | 检查Unix相关的特征,如果存在,设置宏定义。 |
用户可在configure.in中使用通用测试宏AC_CHECK_TYPES测试指定的类型是否存在。例如:在configure.in中的语句如下:
AC_CHECK_TYPES(sys/stat.h)
acheader工具在配置头文件clamav-config.h中产生如下语句,用户也可以手动在其中加入如下语句:
#define HAVE_SYS_STAT_H 1
软件包中的源代码文件可以使用这个宏,语句如下:
#if HAVE_SYS_STAT_H # include #endif
(3)书写测试的宏
如果Autoconf给定的测试宏不能满足需要,用户可以用给定的宏写测试代码,这些书写测试的宏说明如表5:表5 用户书写测试代码的宏
宏类别 | 宏定义 | 功能说明 |
语言选择宏 | AC_LANG(language)、AC_LANG_PUSH(language)、AC_LANG_POP ([language])、AC_REQUIRE_CPP | 分别用来选择写测试代码的语言、语言压栈、弹出栈、测试预处理器。 |
写测试程序 | AC_LANG_CONFTEST(source)、AC_LANG_SOURCE(source)、AC_LANG_PROGRAM(prologue, body)等 | 用来将源代码source加入到临时的源代码文件conftest中。 |
运行预处理器 | AC_PREPROC_IFELSE(…)、AC_EGREP_HEADER(…)、AC_EGREP_CPP(…) | 它们分别根据预处理代码、头文件的字符匹配、程序代码的字符匹配的结果进行操作。 |
检查编译器 | AC_COMPILE_IFELSE (…) | 根据输入代码的编译结果进行操作。 |
检查链接器 | AC_LINK_IFELSE(…) | 根据输入代码的编译和链接结果进行操作。 |
检查运行 | AC_RUN_IFELSE(…) | 根据输入代码的编译、链接和运行结果进行操作。 |
用户可以使用Autoconf语言的宏在configure.in文件书写测试源程序,源程序分段插入到Autoconf语言的宏中。例如:在 clamav软件包的configure.in文件中,宏AC_CACHE_CHECK用来检测结构msghdr的成员control,如果存 在,configure运行时输出结果yes,否则,输出no。其代码列出如下:
AC_CACHE_CHECK( [ for msg_control field in struct msghdr] , dnl 输出消息 check for msg …… ac_cv_have_control_in_msghdr, [ dnl 存储结果的变量 AC_TRY_RUN( dnl 测试源程序 [ #include #include #include int main( ) { #ifdef msg_control exit( 1 ) ; #endif struct msghdr m; m.msg_control = 0 ; exit( 0 ) ; } ] , [ ac_cv_have_control_in_msghdr= "yes" ] , dnl 运行为true 时的操作 [ ac_cv_have_control_in_msghdr= "no" ] dnl 运行为false 时的操作 ) ] )
运行autoconf工具后,configure文件中产生如下的代码:
cat > conftest.$ac_ext << _ACEOF #将两个_ACEOF之间的代码加入到conftest.c中 /* confdefs.h. */ _ACEOF cat confdefs.h >> conftest.$ac_ext #将confdefs.h内容输出到conftest.c中 cat >> conftest.$ac_ext << _ACEOF #将两个_ACEOF之前的代码输出到conftest.c中 /* end confdefs.h. */ #include #include #include int main( ) { #ifdef msg_control exit ( 1 ) ; #endif struct msghdr m; m.msg_control = 0 ; exit ( 0 ) ; } _ACEOF rm -f conftest$ac_exeext #删除conftest.c文件
configure脚本运行时,它将需要脚本中的源代码通过cat命令输出到conftest.c文件中,然后编译conftest.c文件生成测试程序,并运行测试程序。运行完程序后,删除conftest.c文件。
(4)测试结果
configure脚本运行时,系统检测结果需要输出到配置头文件或Makefile的变量中,或者实时打印消息供用户查阅。Autoconfig语言提供了测试结果宏对测试结果进行处理。测试结果宏列出如表5。表5 测试结果输出的宏的说明
类别 | 宏定义 | 功能说明 |
定义C预处理符号 | AC_DEFINE(…)、AC_DEFINE_UNQUOTED(…) | 用来在配置头文件中定义宏。 |
设置输出变量 | AC_SUBST(…)、AC_SUBST_FILE(variable)、AC_ARG_VAR(…) | 前两个分别输出变量、文件内容到Makefile中,后一个加已输出的变量的描述到./configure --help中。 |
缓存变量结果 | AC_CACHE_VAL(…)、AC_CACHE_CHECK(…) | 从缓存文件config.cache中读取变量,如果不在其中,就设置它。第二个宏还会打印参数所指的消息。 |
缓存检查点 | AC_CACHE_LOAD、AC_CACHE_SAVE | 分别用来从缓存文件装载值、存储变量值到缓存文件。 |
打印消息 | AC_MSG_CHECKING(…)、AC_MSG_RESULT(…)、AC_MSG_NOTICE(…)、AC_MSG_ERROR(…)、AC_MSG_FAILURE(…)、AC_MSG_WARN(…) | 用于configure脚本运行时,实时打印系统检测出现的各种消息。 |
用户有时需要根据对系统的测试结构在配置头文件中定义宏,以例软件包根据配置头文件的宏定义进行编译。例如:在clamav软件包的configure.in文件中,一个定义预处理符号的宏列出如下:
AC_DEFINE_UNQUOTED(SENDMAIL_BIN, "$sendmailprog", [location of Sendmail binary])
configure脚本根据测试结果$sendmailprog定义宏SENDMAIL_BIN。因为测试结果为0,在配置头文件clamav-config.h中的宏定义语句列出如下:
/* location of Sendmail binary */
/* #undef SENDMAIL_BIN */
输出变量
Makefile文件中会使用到变量,这些变量需要在configure.in中定义,通过Autoconfig宏输出到Makefile文件中。例如:在configure.in文件中,一个输出变量的语句列出如下:
cfg_dir= "$prefix/etc" dnl 定义变量 CFGDIR= $c fg_dir dnl 定义变量 AC_SUBST( CFGDIR) dnl 输出变量
在运行automake工具后产生的Makefile.in文件中产生如下语句,@CFGDIR@在产生Makefile文件时会被在configure.in文件中变量CFGDIR定义的值替换。
CFGDIR = @ CFGDIR@
运行configure脚本后产生的Makefile文件中产生如下变量定义:
CFGDIR = / usr/ local/ etc
缓存文件
缓存文件config.cache是shell脚本,用来缓存configure测试运行的结果。缺省下configure不使用缓存文件,如果 configure运行带有参数选项--config-cachefile或-C时,检测中间结果会被缓存到config.cache文件中,下次运行 时,可以直接从缓存文件中读取结果以节省运行时间。
configure.in文件可以使用宏AC_CACHE_SAVE将缓存变量存入到缓存文件中去;使用AC_CACHE_LOAD从缓存文件中装载缓存变量值到configure.in文件的变量,如果缓存文件不存在,就创建一个缓存文件config.cache。
打印消息
configure运行时,测试结果需要打印出来供用户查阅。检测的消息提示常用宏AC_MSG_CHECKING给出,这个宏会自动在提 示消息前加"checking",以消息末尾加上"…"。测试结果用宏AC_MSG_RESULT给出。例如,在configure.in中有如下测试结 果打印宏语句:
AC_MSG_CHECKING(for socklen_t)
AC_MSG_RESULT($ac_cv_socklen_t)
configure脚本运行时,会打印如下语句,
checking for socklen_t... yes
2.3 configure.in实例
下面以clamav软件包的configure.in为例说明configure.in的编写方法。 (1)生成configure.scan模板
用命令autoscan生成configure.scan模板,configure.scan模板告诉用户编写哪些方面的测试代码。命令autoscan的运行结果列出如下:^-^$ autoscan autom4te: configure.ac: no such file or directory autoscan: / usr/ bin/ autom4te failed with exit status: 1 ^-^$ cat configure.scan # -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ( 2.59 ) AC_INIT( FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS) # Checks for programs. # Checks for libraries. # Checks for header files. # Checks for typedefs, structures, and compiler characteristics. # Checks for library functions.
(2)编写configure.in
用户将configure.scan模板文件改名为configure.in,然后编辑configure.in文件,编写测试代码。编写configure.in文件步骤说明如下:
(1)初始化autoconfig,检查软件包源文件是否存在,初始化automake,定义软件包名和版本号,定义目标平台头文件target.h和配置头文件clamav-config.h。
(2)检查系统是否存在所需要的工具程序,如:编译器、awk、libtool等。
(3)设置软件包配置的预定义变量,如:定义变量SCANBUFF的值为131072,存储到配置头文件clamav-config.h文件中。
(4)检测头文件,如:检查标准C库的头文件是否存在。
(5)检测在系统中数据类型的大小,如:检测数据类型int在系统中所占的字节数。
(6)检测系统中的函数库是否可用。如:通过连接nsl库的函数gethostent来确定库是否可用。
(7)检测系统函数是否可用。
(8)设置configure脚本的命令行参数选项,检查参数选项的值,并实现参数选项的操作。如:对于configure --with-zlib=/usr/local命令行参数选项,configure.in应检查指定路径下的头文件是否存在、zlib库的版本是否正确、 zlib库是否可用等。
(9)检查目标平台,并设置目标平台相关的预定义变量,存储到目标平台头文件target.h中。
(10)检查编译器特定的宏或函数,如:检查C编译器是否支持__attribute__。
(11)定义configure运行的输出,如:各子目录下的Makefile、clamav-config、man文档等。
Autoconfig语言提供了许多测试宏,用户可以使用这些宏进行测试。但一些特定的测试还需要用户自己编写测试源代码,以Autoconfig语言的格式放置在configure.in文件中。
clamav软件包的configure.in内容列出如下:
dnl处理命令行参数并检查文件clamscan/ clamscan. c是否存在,从而确定目录clamscan是否包含源代码 AC_INIT( clamscan/ clamscan. c) dnl 创建头文件target. h,让它含有定义目标平台的“#defines”语句,这个宏用于与平台相关的库 AC_CREATE_TARGET_H( target. h) dnl 初始化软件包名与版本号 AM_INIT_AUTOMAKE( clamav, "0.90rc3" ) dnl 新版本用宏AC_CONFIG_HEADERS替代,表示软件包使用配置头文件 AM_CONFIG_HEADER( clamav- config. h) LC_CURRENT= 1 dnl 定义变量 LC_REVISION= 33 LC_AGE= 0 LIBCLAMAV_VERSION= "$LC_CURRENT" : "$LC_REVISION" : "$LC_AGE" dnl 1 : 33 : 0 AC_SUBST( LIBCLAMAV_VERSION) dnl 表示符号LIBCLAMAV_VERSION已被定义,避免重复 AC_PROG_AWK dnl 检查awk是否存在 AC_PROG_CC dnl 检查C编译器是否存在,AC_PROG_CXX检查C++ 编译器是否存在 AC_PROG_INSTALL dnl 在编译创建install脚本之前检查install. sh或install- sh是否存在 AC_PROG_LN_S dnl 将输出参数LN_S设置为‘ln - s’,如果系统不支持,设置到‘ln’或‘cp - p’ dnl 如果已定义变量MAKE,设置输出参数MAKE_SET为空,否则,定义为‘MAKE= make’ AC_PROG_MAKE_SET AC_PROG_LIBTOOL dnl 检查 libtool 是否正确设置 dnl 预定义变量SCANBUFF的值为131072 ,描述为[ scan buffer size] ,放到config. h. in中作为宏定义的描述 AC_DEFINE( SCANBUFF, 131072 , [ scan buffer size] ) AC_DEFINE( FILEBUFF, 8192 , [ file i/ o buffer size] ) dnl 根据C库头文件是否存在定义宏STDC_HEADERS,程序中可使用#if STDC_HEADERS AC_HEADER_STDC dnl 检查头文件是否存在 AC_CHECK_HEADERS( stdint. h unistd. h sys/ int_types. h dlfcn. h inttypes. h sys/ inttypes. h memory. h ndir. h stdlib. h strings. h string. h sys/ mman. h sys/ param. h sys/ stat. h sys/ types. h malloc. h poll. h regex. h limits. h sys/ filio. h sys/ uio. h termios. h iconv. h stdbool. h) dnl 如果找到syslog. h,将变量USE_SYSLOG定义为1 ,变量说明[ use syslog] AC_CHECK_HEADER( syslog. h, AC_DEFINE( USE_SYSLOG, 1 , [ use syslog] ) , ) AC_TYPE_OFF_T dnl 如果发现类型off_t,定义为long类型, AC_COMPILE_CHECK_SIZEOF( short) dnl 检查short类型大小 AC_COMPILE_CHECK_SIZEOF( int) AC_COMPILE_CHECK_SIZEOF( long) AC_COMPILE_CHECK_SIZEOF( long long) dnl 通过socket库bind函数能否被连接来检查库,如果连接成功,设置库变量 AC_CHECK_LIB( socket, bind, [ LIBS= "$LIBS -lsocket" ; CLAMAV_MILTER_LIBS= "$CLAMAV_MILTER_LIBS -lsocket" ; LDFLAGS= "$LDFLAGS -lsocket" ; FRESHCLAM_LIBS= "$FRESHCLAM_LIBS -lsocket" ; CLAMD_LIBS= "$CLAMD_LIBS -lsocket" ] ) dnl 通过函数gethostent检查库nsl,检查正确,设置库变量 AC_CHECK_LIB( nsl, gethostent, [ LIBS= "$LIBS -lnsl" ; CLAMAV_MILTER_LIBS= "$CLAMAV_MILTER_LIBS -lnsl" ; LDFLAGS= "$LDFLAGS -lnsl" ; FRESHCLAM_LIBS= "$FRESHCLAM_LIBS -lnsl" ; CLAMD_LIBS= "$CLAMD_LIBS -lnsl" ] ) dnl 检查poll等函数是否存在 AC_CHECK_FUNCS( poll setsid memcpy snprintf vsnprintf strerror_r strlcpy strlcat inet_ntop setgroups initgroups ctime_r mkstemp) AC_FUNC_MMAP dnl 检查函数mmap是否正确工作,如果正确,定义宏HAVE_MMAP AC_FUNC_FSEEKO dnl 如果函数fseeko可用,定义宏fseeko dnl 检查snprintf if test "x$ac_cv_func_snprintf" = "xyes" ; then dnl 通知用户configure正在检查指定特征,打印checking whether … long strings … AC_MSG_CHECKING( [ whether snprintf correctly terminates long strings] ) dnl 执行检查程序 AC_TRY_RUN( [ #include int main( void) { char b[ 5 ] ; snprintf( b, 5 , "123456789" ) ; return( b[ 4 ] != '/0 ' ) ; } ] , dnl 通知用户检查结果,如果configure选项有-- quiet,则不打印这个字符串 [ AC_MSG_RESULT( yes) ] , [ AC_MSG_RESULT( no) dnl 预定义BROKEN_SNPRINTF为1 ,描述为[ Define if your snprintf is busted] AC_DEFINE( BROKEN_SNPRINTF, 1 , [ Define if your snprintf is busted] ) dnl 通知用户警告消息 AC_MSG_WARN( [ ****** Your snprintf( ) function is broken, complain to your vendor] ) ] ) fi have_pthreads= no dnl 如果头文件pthread. h可用,执行have_pthreads= yes AC_CHECK_HEADER( pthread. h, [ have_pthreads= yes] , ) dnl AM_MAINTAINER_MODE缺省下关闭“重编译规则”,不会重编译configure、Makefile. ins、Lex或Yacc输出等,常用于发布的程序,用户不需要更新。如果运行./ configure -- enable- maintainer- mode,“重编译规则”激活 AM_MAINTAINER_MODE dnl 检查并实现configure -- with- zlib=/ usr/ local dnl 打印check for zlib installation … AC_MSG_CHECKING( for zlib installation) dnl 如果用户给出configure的选项为-- with- zlib=/ usr/ local,/ usr/ local存入$w ithval AC_ARG_WITH( zlib, dnl 选项名,组合为-- with- zlib dnl 帮助信息 [ -- with- zlib= DIR path to directory containing zlib library ( default= / usr/ local or / usr if not found in / usr/ local) ] , dnl 如果给定了选项-- with- zlib,运行的操作如下 [ if test "$withval" ; then ZLIB_HOME= "$withval" dnl 变量设置为/ usr/ local AC_MSG_RESULT( using $Z LIB_HOME) fi ] , [ dnl 如果没给定选项,运行的操作如下 ZLIB_HOME=/ usr/ local dnl 设置变量 if test ! - f "$ZLIB_HOME/include/zlib.h" dnl 如果文件不存在 then ZLIB_HOME=/ usr fi AC_MSG_RESULT( $Z LIB_HOME) dnl 通知用户结果 ] ) dnl 如果configure选项为‘-- enable- zlib- vcheck’或者‘-- disable- zlib- vcheck’ AC_ARG_ENABLE( zlib- vcheck, dnl 选项名 [ -- disable- zlib- vcheck do not check for buggy zlib version ] , dnl 帮助信息 dnl 给定选项的操作,未给定选项的操作 zlib_check= $e nableval, zlib_check= "yes" ) if test ! - f "$ZLIB_HOME/include/zlib.h" then dnl 如果文件不存在,configure输出错误消息 AC_MSG_ERROR( Please install zlib and zlib- devel packages) else dnl 用grep在文件中查找版本字符串 vuln= `grep "ZLIB_VERSION /" 1.2.0" $Z LIB_HOME/ include / zlib. h` if test - z "$vuln" ; then vuln= `grep "ZLIB_VERSION /" 1.2.1" $Z LIB_HOME/ include / zlib. h` fi if test - n "$vuln" ; then dnl 查找到字符串,说明版本正确 if test "$zlib_check" = "yes" ; then dnl 如果需要检查,configure输出错误信息 AC_MSG_ERROR( The installed zlib version may contain a security bug. Please upgrade to 1. 2. 2 or later: http:// www. zlib. net. You can omit this check with -- disable- zlib- vcheck but DO NOT REPORT any stability issues then! ) else dnl 其他情况,打印警告信息 AC_MSG_WARN( [ ****** This ClamAV installation may be linked against] ) AC_MSG_WARN( [ ****** a broken zlib version. Please DO NOT report any] ) AC_MSG_WARN( [ ****** stability problems to the ClamAV developers! ] ) fi fi if test "$ZLIB_HOME" != "/usr" ; then dnl 设置指定的库路径 LDFLAGS= "$LDFLAGS -L$ZLIB_HOME/lib" CPPFLAGS= "$CPPFLAGS -I$ZLIB_HOME/include" dnl 检查库z中的inflateEnd函数是否可连接,如果正确,设置库连接字符串,否则,打印错误消息 AC_CHECK_LIB( z, inflateEnd, [ LIBCLAMAV_LIBS= "$LIBCLAMAV_LIBS -L$ZLIB_HOME/lib -lz" ; AC_DEFINE( HAVE_ZLIB_H, 1 , zlib installed) ] , AC_MSG_ERROR( Please install zlib and zlib- devel packages) ) else dnl 缺省的库路径 AC_CHECK_LIB( z, inflateEnd, [ LIBCLAMAV_LIBS= "$LIBCLAMAV_LIBS -lz" ; AC_DEFINE( HAVE_ZLIB_H, 1 , zlib installed) ] , AC_MSG_ERROR( Please install zlib and zlib- devel packages) ) fi fi …… dnl 省略了一些选项检查 dnl 检查setpgrp,如果没有参数,定义宏SETPGRP_VOID AC_FUNC_SETPGRP dnl 检查configure选项-- enable- milter或-- disable- milter AC_ARG_ENABLE( milter, [ -- enable- milter build clamav- milter] , dnl 帮助信息 dnl 选项存在时的操作,选项不存在时的操作 have_milter= $e nableval, have_milter= "no" ) if test "$have_milter" = "yes" ; then sendmailprog= no dnl 检查选项-- with- sendmail或-- without- sendmail AC_ARG_WITH( sendmail, dnl 帮助信息 [ -- with- sendmail= PATH specify location of Sendmail binary ( default= auto find) ] , dnl 选项存在时的操作,选项不存在时的操作 sendmailprog= $w ith_sendmail, sendmailprog= no) if test "$sendmailprog" = "no" ; then dnl检查sendmail是否在$P ATH:/ sbin…路径中,如果在其中,设置sendmailprog为sendmail的全路径名,如果不在,设置为no AC_PATH_PROG( sendmailprog, sendmail, no, $P ATH:/ sbin:/ usr/ sbin:/ usr/ lib:/ usr/ libexec) fi dnl 类似于AC_DEFINE,但会展开变量。当变量或值是shell变量时,替代AC_DEFINE dnl 定义变量SENDMAIL_BIN为$s endmailprog,说明为[ location of Sendmail binary] AC_DEFINE_UNQUOTED( SENDMAIL_BIN, "$sendmailprog" , [ location of Sendmail binary] ) sendmailver= `$s endmailprog - d0 < / dev/ null | head - 1 | awk '{print $2}' ` if test - n "$sendmailver" ; then dnl 变量字符串以‘. ’作为分隔符,得到第1 个域的字符串,即主版本号 sendmailver_a= `echo $s endmailver | awk - F. '{printf $1}' ` sendmailver_b= `echo $s endmailver | awk - F. '{printf $2}' ` dnl 次版本号 sendmailver_c= `echo $s endmailver | awk - F. '{printf $3}' ` AC_DEFINE_UNQUOTED( SENDMAIL_VERSION_A, $s endmailver_a, [ major version of Sendmail] ) AC_DEFINE_UNQUOTED( SENDMAIL_VERSION_B, $s endmailver_b, [ minor version of Sendmail] ) AC_DEFINE_UNQUOTED( SENDMAIL_VERSION_C, $s endmailver_c, [ subversion of Sendmail] ) fi fi …… dnl 目标平台检测 case "$target_os" in linux* ) AC_DEFINE( C_LINUX, 1 , [ target is linux] ) if test "$have_pthreads" = "yes" ; then LIBCLAMAV_LIBS= "$LIBCLAMAV_LIBS -lpthread" TH_SAFE= "-thread-safe" AC_DEFINE( CL_THREAD_SAFE, 1 , [ thread safe] ) AC_DEFINE( _REENTRANT, 1 , [ thread safe] ) CLAMD_LIBS= "$CLAMD_LIBS -lpthread" CLAMAV_MILTER_LIBS= "$CLAMAV_MILTER_LIBS -lpthread" if test "$want_clamuko" = "yes" ; then AC_DEFINE( CLAMUKO, 1 , [ enable clamuko] ) fi CLAMSCAN_LIBS= "$CLAMSCAN_LIBS -lpthread" fi case `uname - r` in 1 .*| 2.0 .* ) AC_DEFINE( INCOMPLETE_CMSG, 1 , [ Early Linux doesn't set cmsg fields]) ;; esac ;; kfreebsd*-gnu) …… ;; cygwin*) …… ;; solaris*) …… ;; freebsd*) …… ;; …… *) ;; esac AC_SUBST(LIBCLAMAV_LIBS) AC_SUBST(CLAMD_LIBS) AC_SUBST(CLAMAV_MILTER_LIBS) AC_SUBST(FRESHCLAM_LIBS) AC_SUBST(TH_SAFE) AC_SUBST(ADDITIONAL_LIBS) dnl 处理--enable-milter if test "$have_milter" = "yes"; then dnl 检查libmiter和它的头文件是否在通常的位置 save_LDFLAGS="$LDFLAGS" if test -d /usr/lib/libmilter ; then dnl 如果目录存在,指定库路径 CLAMAV_MILTER_LIBS="$CLAMAV_MILTER_LIBS -L/usr/lib/libmilter" fi dnl 指定连接的库 LDFLAGS="$LDFLAGS -lmilter $CLAMAV_MILTER_LIBS" dnl检查库milter的函数mi_stor,如果存在,设置连接的库;如果不存在搜索库 AC_CHECK_LIB(milter, mi_stop,[CLAMAV_MILTER_LIBS="-lmilter $CLAMAV_MILTER_LIBS"],[ dnl 较旧的sendmails需要库libsm或libsmutil提供支持的函数 dnl 搜索库sm或smutil,查找函数strlcpy,如果找到,执行操作 AC_SEARCH_LIBS(strlcpy, [sm smutil], [test "$ac_cv_search_strlcpy" = "none required" || CLAMAV_MILTER_XLIB="$ac_cv_search_strlcpy"]) dnl 设置连接的库 LDFLAGS="$save_LDFLAGS $CLAMAV_MILTER_LIBS $CLAMAV_MILTER_XLIB" $as_unset ac_cv_lib_milter_mi_stop AC_CHECK_LIB(milter, mi_stop,[CLAMAV_MILTER_LIBS="-lmilter $CLAMAV_MILTER_XLIB $CLAMAV_MILTER_LIBS"],[ AC_MSG_ERROR([Cannot find libmilter]) ]) ]) LDFLAGS="$save_LDFLAGS" dnl 检查头文件,如果发现,设置have_milter="yes",如果没发现打印错误信息 AC_CHECK_HEADERS(libmilter/mfapi.h,have_milter="yes",[ AC_MSG_ERROR([Please install mfapi.h from the sendmail distribution]) ]) fi dnl 如果test条件满足,设置条件编译宏定义BUILD_CLAMD AM_CONDITIONAL(BUILD_CLAMD, test "$have_pthreads" = "yes") AM_CONDITIONAL(HAVE_MILTER, test "$have_milter" = "yes") if test "$have_pthreads" = "yes" then AC_DEFINE(BUILD_CLAMD, 1, "build clamd") fi …… dnl 检查gethostbyname_r和它有参数 AC_MSG_CHECKING(for gethostbyname_r) dnl 打印check for gethostbyname_r … if test -z "$ac_cv_gethostbyname_args"; then dnl 如果变量为0,即无参数个数 dnl 编译代码并执行,用来测试函数 AC_TRY_COMPILE( dnl 包含的头文件 [ #include #include ],[ dnl 代码主体 struct hostent *hp; struct hostent h; char *name; char buffer[10]; int h_errno; hp = gethostbyname_r(name, &h, buffer, 10, &h_errno); ], ac_cv_gethostbyname_args=5) dnl 如果程序执行结果为true,设置变量,即设置参数个数为5 fi …… dnl 如果C编译器不完全支持ANSI C的const,就将const定义为空 AC_C_CONST dnl 如果C编译器支持关键字inline,则不做任何事情,否则,将inline定义为__inline__或__inline,如果编译器不支持这两个关键字,就定义为空 AC_C_INLINE AC_C_BIGENDIAN if test $ac_cv_c_bigendian = yes; then AC_DEFINE(WORDS_BIGENDIAN,1,endianess) else AC_DEFINE(WORDS_BIGENDIAN,0,endianess) fi dnl 检查__attribute__ AC_MSG_CHECKING([for structure packing via __attribute__]) AC_CACHE_VAL(have_attrib_packed,[ AC_TRY_COMPILE(, [struct { int i __attribute__; } s; ], [have_attrib_packed=yes], [have_attrib_packed=no]) ]) AC_MSG_RESULT($have_attrib_packed) if test "$have_attrib_packed" = no; then AC_MSG_CHECKING(for structure packing via pragma) AC_CACHE_VAL(have_pragma_pack,[ AC_TRY_RUN([int main(int argc, char **argv) { #pragma pack(1) /* has to be in column 1 ! */ struct { char c; long l; } s; return sizeof(s)==sizeof(s.c)+sizeof(s.l) ? 0:1; } ], [have_pragma_pack=yes], [have_pragma_pack=no]) ]) AC_MSG_RESULT($have_pragma_pack) AC_DEFINE(HAVE_PRAGMA_PACK, 1, "pragma pack") fi …… AC_OUTPUT([ libclamav/Makefile clamscan/Makefile database/Makefile docs/Makefile clamd/Makefile clamdscan/Makefile clamav-milter/Makefile freshclam/Makefile sigtool/Makefile clamconf/Makefile etc/Makefile Makefile clamav-config libclamav.pc docs/man/clamd.8 docs/man/clamd.conf.5 docs/man/freshclam.1 docs/man/freshclam.conf.5 ])
clamav软件的configure脚本运行的结果部分列出如下:
checking build system type... x86_64-unknown-linux-gnu checking host system type... x86_64-unknown-linux-gnu checking target system type... x86_64-unknown-linux-gnu creating target.h - canonical system defines checking for a BSD-compatible install... / usr/ bin/ install -c checking whether build environment is sane... yes checking for gawk... gawk checking whether make sets $( MAKE) ... yes checking for gawk... ( cached) gawk checking for gcc... gcc checking for C compiler default output file name... a.out checking whether the C compiler works... yes checking whether we are cross compiling... no ...... checking for socklen_t... yes checking for clamav in / etc/ passwd... no configure: error: User clamav ( and/ or group clamav) doesn't exist. Please read the documentation !
2.4 automake
automake是从Makefile.am自动产生Makefile.in文件的工具,Makefile.am文件仅包括一些定义源文件或目录的变量,这简化了Makefile的维护工作。automake定义了Makefile.am使用的automake变量编写语法。automake还提供了公有宏和私有宏,公有宏可以在configure.in文件中使用,私有宏是automake工具本身使用的宏。
下面以clamav软件包的configure.in和Makefile.am说明automake工具的使用方法。
(1)automake变量
automake的变量遵循统一命名方案,以便容易用来编译安装程序。在运行make时,变量决定编译的目标,变量名由几部分连接而成, 如:bin_PROGRAMS,其中告诉编译类别的部分称为主要部分(primary),如:变量的PROGRAMS表示将编译成应用程序,变量是应用程 序的列表;变量的前缀部分用来表示目标文件安装的标准目录,如:变量的bin表示应用程序将安装到bindir下,bindir一般扩展为/bin目录。当前使用的变量的主要部分(primary)有PROGRAMS、LIBRARIES、LISP、PYTHON、JAVA、SCRIPTS、DATA、HEADERS、MANS和TEXINFOS。
变量的前缀部分可以是标准目录变量,如:bindir、sbindir等,在变量中不写dir字符。用户还可以扩展安装目录,如:htmldir,但这个变量需要有定义。
特殊变量前缀EXTRA_表示变量用来列出目标对象,根据configure的配置来决定是否编译。
派生变量是生成应用程序的源代码文本的列表,如:在变量sbin_PROGRAM中列出的应用程序名clamd可以用来组合成clamd_SOURCES变量,用来存放应用程序的源文件列表。下面是clamd应用程序的变量命名样例:
sbin_PROGRAMS = clamd clamd_SOURCES = / $ ( top_srcdir ) / shared/ output. c / …… shared. h
还有一些Makefile变量保留给用户,由用户给它们设置值,如:CFLAGS、CC等。
(2)automake的辅助程序
automake有时需要一些辅助程序帮助更好的产生Makefile工作。常用的辅助程序说明如下:config.guess config.sub 这两个程序功能相同,用于获得编译、主机或目标的构架信息,用户应该从ftp://ftp.gnu.org/gnu/config获取最新版本,以支持新内核版本。
depcomp 编译并产生依赖关系信息,可被编译器的自动依赖跟踪特征使用。
texinfo.tex 不是程序,当软件包含有Texinfo源代码时,它是运行make dvi、make ps和make pdf需要的文件。
install-sh 当平台上install不可用时,它是install程序的替代。
例如:clamav软件包中config.guess程序的运行结果列出如下:
^-^$ ./config.guess
x86_64-unknown-linux-gnu
^-^$ ./config.guess -t
2006-07-02 #上次修改的时间
(3)自动产生aclocal.m4
automake定义了许多宏,软件包在编译配置时将使用这些宏。这些宏必须定义在aclocal.m4中,否则,autoconf将无法找到它们。aclocal工具基于configure.in内容自动产生aclocal.m4文件。它先扫描系统中所有的.m4文件,查找宏定义,接 着,扫描configure.in,任何找到或需要的宏将放入aclocal.m4中。另外,如果acinclude.m4文件存在,它将被自动包含进 aclocal.m4。
(4)automake提供的Autoconf宏
automake定义一些公有宏,又称Autoconf宏,可以用在configure.in文件中,这些宏又称公有宏。这些在运行aclocal时会被展开并包含在aclocal.m4文件中。下面以clamav软件的configure.in文件为例说明常用的automake宏:
dnl 执行初始化automake所需要的许多宏,设置软件包名和版本号 AM_INIT_AUTOMAKE( clamav, "0.90rc3" ) AM_CONFIG_HEADER( clamav- config. h) dnl 创建配置头文件 …… dnl 缺省下关闭再编译规则,即运行./ configure和make时,不再重创建configure, Makefile. in等 AM_MAINTAINER_MODE …… dnl 条件编译宏定义变量为BUILD_CLAMD,后面是测试编译条件 AM_CONDITIONAL( BUILD_CLAMD, test "$have_pthreads" = "yes" ) AM_CONDITIONAL( HAVE_MILTER, test "$have_milter" = "yes" ) if test "$have_pthreads" = "yes" then AC_DEFINE( BUILD_CLAMD, 1 , "build clamd" ) dnl 在配置头文件中定义宏 fi
(5)条件编译
条件编译是根据用户在configure.in中设置的条件来决定编译的源代码或链接的目标文件。条件编译的方法有两种:一种是使用configure.in定义的宏在Makefile.am中判定编译的目标。另一种方法使用变量替换, 由configure.in根据条件定义编译的目标列表变量,这个列表变量替换Makefile.in中的替换变量,如:@HELLO_SYSTEM@。
例1 使用方法一进行条件编译
用户书写Makefile.am时,可以根据条件编译宏定义判断是否编译应用程序。在clamav/clamd/Makefile.am文件中使用条件编译宏定义的语句列出如下:
if BUILD_CLAMD sbin_PROGRAMS = clamd clamd_SOURCES = / $ ( top_srcdir ) / shared/ output. c / …… dazukoio_xp. h / shared. h endif
在configure.in文件中与条件编译相关的语句列出如下:
AM_CONDITIONAL( BUILD_CLAMD, test "$have_pthreads" = "yes" ) if test "$have_pthreads" = "yes" then AC_DEFINE( BUILD_CLAMD, 1 , "build clamd" ) fi
例2 使用方法二进行条件编译
条件编译的程序源代码文件应放在EXTRA_*变量中,条件编译的目标对象放在*_LDADD变量中,依赖文件放在*_DEPENDENCIES变量中。应用程序hello进行条件编译时的Makefile.am列出如下:
bin_PROGRAMS = hello #定义应用程序安装目录 hello_SOURCES = hello- common. c #hello应用程序的必须编译的源文件列表 EXTRA_hello_SOURCES = hello- linux. c hello- generic. c # hello应用程序的条件编译的源文件列表 hello_LDADD = @ HELLO_SYSTEM@ #条件编译的目标对象列表 hello_DEPENDENCIES = @ HELLO_SYSTEM@ #依赖目标文件列表
在configure.in文件中建立@HELLO_SYSTEM@ 替代变量,运行configure时,HELLO_SYSTEM将被hello-linux.o 或hello-bsd.o替换。configure.in文件建立替代变量的方法如下:
dnl 根据不同的平台定义变量 case $h ost in * linux* ) HELLO_SYSTEM= 'hello-linux.$(OBJEXT)' ;; dnl 定义变量 * ) HELLO_SYSTEM= 'hello-generic.$(OBJEXT)' ;; esac AC_SUBST( [ HELLO_SYSTEM] ) dnl 输出变量 ...
(5)编译应用程序和静态库
应用程序的安装目录变量有bindir、sbindir、libexecdir、pkglibdir或noinst(表示无安装目录)。它们根据configure --prefix的前缀组装成实现的安装目录,如:/opt/bin。当链接configure不能找到的库时,可以使用变量LDADD。变量LDADD指定用来链接的附加的对象或库。编译多个应用程序链接库时,可使用变量prog_LDADD指定程序特定的库,prog为程序名。例如:
LDADD = ../lib/libcpio.a @INTLLIBS@
编译静态库的方法与编译应用程序类似,只是编译变量的主要部分由_PROGRAM改为_LIBRARIES。安装目录变量为libdir 或pkglibdir。
clamav软件包的顶层Makefile.am列出如下:
#含有Makefile.am的子目录,目录顺序还表示编译顺序 SUBDIRS = libclamav clamscan clamd clamdscan freshclam sigtool clamconf database docs etc clamav- milter #自动规则未覆盖到的需要发布的文件 EXTRA_DIST = FAQ contrib test examples BUGS shared libclamav. pc. in UPGRADE #定义配置文件放置目录的变量 bin_SCRIPTS= clamav- config #定义变量的目录部分 pkgconfigdir = $ ( libdir ) / pkgconfig #变量中目录部分pkgconfig将扩展成目录位置pkgconfigdir,表示数据文件libclamav.pc安装在这个目录下 pkgconfig_DATA = libclamav. pc
子目录libclamav下的编译静态库的Makefile.am列出如下:
# -I表示加目录到目录搜索链表,@srcdir@表示变量srcdir将被configure运行时决定的值替换 INCLUDES = - I$ ( top_srcdir ) - I@ srcdir@/ unrar #库变量用于存放目标的目录,这些目标将被编译进库,库根据configure决定是否编译,库名为libclamav.la libclamav_la_LIBADD = @ LIBCLAMAV_LIBS@ libclamav_la_LDFLAGS = @ TH_SAFE@ - version- info @ LIBCLAMAV_VERSION@ - no- undefined #头文件安装在includedir变量扩展后的目录下 include_HEADERS = clamav. h #这是编译进库libclamav.la的源代码文件列表变量 libclamav_la_SOURCES = / clamav. h / matcher- ac. c / ...... lockdb. h #库libclamav.la安装在liblib变量扩展后的目录下 lib_LTLIBRARIES = libclamav. la 子目录clamscan下的编译应用程序的Makefile. am列出如下: #表示应用程序名为clamscan,安装目录为bindir扩展的目录 bin_PROGRAMS = clamscan #表示应用程序clamscan的源代码文件列表,生成源文件对应的目标文件后,再连接成应用程序clamscan clamscan_SOURCES = / $ ( top_srcdir ) / shared/ output. c / ...... treewalk. h DEFS = @ DEFS@ - DCL_NOTHREADS #定义库列表 LIBS = $ ( top_builddir ) / libclamav/ libclamav. la @ ADDITIONAL_LIBS@ #定义在源程序中include语句查找的目录 INCLUDES = - I$ ( top_srcdir ) - I$ ( top_srcdir ) / shared - I$ ( top_srcdir ) / libclamav
(6)共享库的编译方法
编译共享库需要使用GNU libtool工具,GNU libtool工具封装了平台依赖性和用户接口,将共享和静态库抽象成统一的libtool库,libtool库是用.la作为后缀的文件,直到运行 configure时才最终决定它们是静态还是共享库,由libtool工具生成相应的库。libtool目标文件以.lo作为后缀,它们连接生成 libtool库。共享库被编译成位置无关代码,编译命令行选项需要加位置无关标识,libtool工具通过使用独立的库对象文件(以.lo代替.o)隐藏了位置无关标识的复杂性,它会自动插入位置无关标识到编译命令行中。
下面是运行libtool工具编译libtool.lo对象的方法,libtool工具调用gcc命令先将foo.c编译成位置无关的foo.o文件,然后再重命名成foo.lo文件。
$ libtool --mode =compile gcc -g -O -c foo.c gcc -g -O -c -fPIC -DPIC foo.c mv -f foo.o foo.lo
下面是运行libtool工具链接.lo对象生成库的方法:
$ libtool --mode =link gcc -g -O -o libhello.la foo.lo hello.lo / -rpath / usr/ local/ lib -lm mkdir .libs ld -Bshareable -o .libs/ libhello.so.0.0 foo.lo hello.lo -lm ar cru .libs/ libhello.a foo.o hello.o ranlib .libs/ libhello.a creating libhello.la
automake用_ LTLIBRARIES变量表示运行libtool编译库,libtool根据configure.in中的设置决定编译成静态库还是共享库。例如:创建libgettext.la安装在libdir的语法列出如下:
lib_LTLIBRARIES = libgettext. la libgettext_la_SOURCES = gettext. c gettext. h ...
下面以Linux-PAM软件包说明使用libtool编译库的方法。
在Linux-PAM软件包中,libpam目录下的源代码根据configure.in的条件编译设置,libtool将它们编译成静态库还共享库。libpam目录下的Makefile.am列出如下:
# AM_CFLAGS 传递附加的C编译器标识 AM_CFLAGS = - DDEFAULT_MODULE_PATH= /" $ ( SECUREDIR ) / /" - DLIBPAM_COMPILE / - I$ ( srcdir ) / include $ ( LIBPRELUDE_CFLAGS ) - DPAM_VERSION= /" $ ( VERSION ) /" if HAVE_LIBSELINUX AM_CFLAGS += - D"WITH_SELINUX" endif CLEANFILES = * ~ #额外的发布文件,即无法自动找到的发布文件 EXTRA_DIST = libpam. map #设置安装到includedir扩展目录的头文件列表 include_HEADERS = include / security/ _pam_compat. h / include / security/ _pam_macros. h include / security/ _pam_types. h / include / security/ pam_appl. h include / security/ pam_modules. h / include / security/ pam_ext. h include / security/ pam_modutil. h #设置不用安装的头文件列表,这些头文件也必须列出,否则,不会包括到发布软件包中去 noinst_HEADERS = pam_prelude. h pam_private. h pam_tokens. h / pam_modutil_private. h pam_static_modules. h #定义库链接标识 libpam_la_LDFLAGS = - no- undefined - version- info 81 : 6 : 81 / @ LIBAUDIT@ $ ( LIBPRELUDE_LIBS ) @ LIBDL@ if STATIC_MODULES #编译静态库 libpam_la_LDFLAGS += `ls ../ modules/ pam_*/*. lo` / @ LIBDB@ @ LIBCRYPT@ @ LIBNSL@ @ LIBCRACK@ - lutil endif if HAVE_VERSIONING libpam_la_LDFLAGS += - Wl,-- version- script=$ ( srcdir ) / libpam. map endif #设置安装到libdir扩展目录、使用libtool编译的库列表,它会根据configure.in的条件编译选项生成静态或共享库 lib_LTLIBRARIES = libpam. la #生成库libpam.la的源代码文件列表 libpam_la_SOURCES = pam_account. c pam_auth. c pam_data. c pam_delay. c / ...... pam_modutil_getspnam. c pam_modutil_getlogin. c pam_modutil_ingroup. c
在Linux-PAM软件包中,Linux-PAM/configure.in文件开启libtool,设置了libpam库的条件编译,与libpam库配置相关的代码列出如下:
...... dnl automake 将开启libtool的处理 AC_PROG_LIBTOOL ...... dnl 检查是否链接成静态库 dnl 检查configure选项-- enable- static- modules, AC_ARG_ENABLE( static- modules, AS_HELP_STRING( [ -- enable- static- modules] , [ do not make the modules dynamically loadable] ) , dnl 选项的帮助说明 STATIC_MODULES= $e nableval, STATIC_MODULES= no) if test "$STATIC_MODULES" != "no" ; then dnl 编译成静态库 CFLAGS= "$CFLAGS -DPAM_STATIC" AC_ENABLE_STATIC( [ yes] ) dnl libtool工具会根据这个设置编译静态库 AC_ENABLE_SHARED( [ no] ) else dnl 编译成动态库 AC_ENABLE_STATIC( [ no] ) AC_ENABLE_SHARED( [ yes] ) fi #设置条件编译宏定义STATIC_MODULES AM_CONDITIONAL( [ STATIC_MODULES] , [ test "$STATIC_MODULES" != "no" ] )
(7)自动跟踪依赖关系
软件包编译时,通常在源代码文件之间和在源代码文件与所包含的头文件之间存在依赖关系。automake工具可以自动跟踪依赖关系的改变,它使用程 序depcomp计算所有的依赖关系,包括在源代码文件与所包含的头文件之间的依赖关系。当运行automake -a时,它还会将程序depcomp安装到源代码软件包中。用户可以使用automake -i或者在configure.in中设置变量AUTOMAKE_OPTIONS或者使用configure --disable-dependency-tracking取消自动依赖关系跟踪。
(8)发布
在Makefile.in文件中的dist目标用来产生用gzip压缩的tar文件,发布的tar文件名基于变量PACKAGE和VERSION,文件名为package-version.tar.gz,这两个变量在AM_INIT_AUTOMAKE()中定义。通常工具automake自动查找用于发布的文件:所有的源代码、Makefile.am和Makefile.in以及automake内建的通用文件。
自动规则没有覆盖的发布文件或目录可以列在变量EXTRA_DIST。还可以使用变量DIST_SUBDIRS设置这些发布文件的子目录。
编译变量还可以加dist前缀表示发布的文件。如:dist_data_DATA = distribute-this。
3 GCC工具及扩展
GCC提供了编译工具,还提供了检查可执行文件的工具。另外,GCC提供了语言扩展以及在源程序中设置GCC编译宏的功能。它们分别说明如下: 3.1 GNU常用工具
GCC(GNU Compiler Colletion,GNU编译程序集合)是开放源代码的编译工具的集合,它可以编译跟踪源代码、编辑文件、控制编译过程和提供调试信息等。GCC可以编译现今流行的各种编程语言,可以支持几乎所有的CPU硬件平台。GNU开发工具链包括GCC(GNU Compiler Collection,GUN编译程序集合)、binutils工具、gdb调试器和glibc库。GNU常用的工具说明如表3,详细用法查看它们的man文档。
表3 GNU常用的工具
工具 | 描述 |
addr2line | 将可执行文件的地址翻译成源代码文件名和行号。 |
ar | 创建、修改和解析档案文件(archive),用于创建和管理连接程序使用的目标文件。档案文件是含有其他文件集合的单个文件。与as一起在编译程序时使用。 |
as | GNU汇编器,将汇编语言的程序汇编成二进制执行文件。与ar一起在编译程序时使用。 |
autoconf | 根据配置文件产生自动配置源代码包的脚本,用来编译源代码。 |
gcov | gprof使用的配置工具,用来确定程序运行时耗时最大的部分。 |
gdb | GNU调试器用于调试跟踪程序,Linux下还提供了DDD图形界面工具封装gdb指令。 |
gprof | 报告执行文件各个函数的运行状况、调用情况等信息。 |
ld | GNU连接程序,将目标文件连接成可执行文件。 |
make | 读取Makefile来编译程序或对文件进行指定的操作。 |
nm | 列出目标文件中定义的符号。 |
objdump | 显示目标文件的信息。 |
readelf | 从ELF格式的目标文件显示文件信息。 |
size | 列出目标文件中每个部分的名字和尺寸。 |
strings | 打印文件中可以打印的字符串。 |
3.2 预处理
(1)预处理指令
源代码中的预处理指令以"#"进行标识,常用的预处理字 有#define、#if、#elif、#else、#ifdef、#ifndef、#include、#line、#pragma、#undef、#warning、#error、## 等。其中,#line将当前的行号替换成指定的行号;#warning发出警告信息,程序继续执行;#error发出致命错误信息,程序退 出;#pragma表示指定编译器特定的信息;##表示两个源代码相连接。例如:下面是一个头文件的预处理字的应用,它确保头文件只被引用一次。
#ifndef _LINUX_KERNEL_H //用于确保头文件只被引用一次 #define _LINUX_KERNEL_H #include //包含头文件 #include #include #undef debug //取消原来的宏定义 #define debug printk //宏定义debug ...... //头文件的函数定义 #endif
其他预定义指令的样例列出如下:
#define PASTE(a) a##house //PASTE(farm)的结果为farmhouse /*如果没有宏定义__unix__,则打印错误信息并退出程序*/ #ifndef __unix__ #error “This section will only work on UNIX system” #endif /*如果lexgen.tbl比当前目标文件新,将产生信息:warning:current file is older than “lexgen.tbl” */ #pragma GCC dependency “lexgen.tbl” /*如果程序中使用了函数memcpy,将产生信息:show.c: 38: 9: attempt to use poisoned “memcpy” */ #pragma GCC poison memcpy
(2)预定义宏
GCC提供了预定义宏,用来打印程序的各种特定信息,GCC的一些常用宏说明如表4。表4 GCC的常用预定义宏
宏 | 说明 |
__DATE__ | 打印编译程序的日期,格式为:May 3 2007 |
__FILE__ | 用于打印它所在的文件的名字。 |
__LINE__ | 用于打印它在文件中的行号。 |
__TIME__ | 用于打印编译时的时间,格式为:19:34:55。 |
__VERSION__ | 用于打印完整的版本号。 |
#define msg(str) / fprintf(stderr, “File: %s Line: %d Function: %s /n%s/n”, / __FILE__, __LINE__, __FUNC__, str);
在源代码中使用下面的语句描述错误:
msg( “There is an error here.”) ;
打印输出到终端的信息类似如下:
File: test.c Line: 334 Function: testfunction There is an error here.
3.3 C语言扩展
(1)__alignof____alignof__操作返回数据类型或指定数据项的边界对齐。例如:
printf ( “__alignof__( char ) =% d/n”, __alignof__( char ) ) ;
(2)变长数组
可以动态指定数组大小,例如:
void arraydynamicsize( int size) { char outstr[ size] ; }
(3)属性
用关键字__attribute__给函数或声明赋属性会下,编译器根据属性值对程序进行相应处理。常用的属性值说明如表5。
表5 __attribute__的常用属性值
类型 | 属性 | 说明 |
函数 | constructor | 含有此属性的函数在主函数main()之前被自动调用。 |
destructor | 含有此属性的函数在主函数main()或exit()之后被自动调用。 | |
deprecated | 含有此属性的函数被调用时,编译器打印更多此函数的警告信息。 | |
noreturn | 含有此属性的函数不会返回。 | |
pure | 含有此属性的函数除了考虑返回值外,无副作用。它不会修改全局变量、参数的位置和文件内容。 | |
section | 编译器将含有此属性的函数的代码放在命名的小节中,而不会放以默认的text小节中。 | |
used | 含有此属性的函数不管是否被使用,编译器都会它。 | |
变量 | aligned | 指定变量的对齐方法。 |
deprecated | 含有此属性的变量被引用时,编译器给出警告信息。 | |
packed | 表示编译器将成员在成员与前一成员之间没有空隙地放置。 | |
section | 含有此属性的变量被放置到指定的小节中,而非默认的data或bss小节。 | |
类型 | aligned | 表示所定义的类型按指定的位进行对齐。 |
deprecated | 含有此属性的类型每次用于声明变量时,编译器会发出警告信息。 | |
packed | 将枚举或结构类型的每个成员放置在一起,不留空隙。 | |
unused | 表示含有此属性的类型的数据,都显示为没被使用。 |
struct blockm { char j[ 3 ] ; } __attribute__ ( ( aligned( 32 ) ) ) ;
(4)内联函数
内联函数在编译时象宏定义一样被展开,编程时,程序员可将它当作函数看待,常用于使用频繁的短小函数,可以省略函数调用花费的开销。
(5)用typedef创建数据类型变量
typedef可用来定义变量,如:应用程序为了方便平台移植,将数据类型按平台分别进行定义。typedef可用来按数据的类型来定义变量,如:宏定义用它可以作用于多种不同的数据类型。
例1:应用程序为适应WIN32平台和Linux平台而定义了不同的数据类型,方法如下:
#if defined(WIN32) //! 16-bit data. typedef unsigned __int16 Bit16T; //typedef unsigned __int32 Bit24T; //! 32-bit data. typedef unsigned __int32 Bit32T; #elif defined(LINUX) #include //! Byte. typedef unsigned char ByteT; #endif
例2:下面的宏定义用于a和b相互交换数据,变量_tp和temp被定义为a的类型,这样,这个宏定义可以自动适应多种数据类型。
#define swap(a, b) / ({ typedef _tp = a; / _tp temp = a; / a = b; / b = temp; })
(6)用typeof引用变量的类型
typeof()表达式用来引用变量的类型定义新的变量,常用于宏定义适应多数据类型。例如:
char * chptr; //char指针 typeof ( * chptr) ch; //char字符 typeof( ch) * chptr; //char指针 typeof( chptr) chparray[ 10 ] ; //10个char指针 typeof( * chptr) charray[ 10 ] ; //10个char字符 #define array(type, size) typeof(type[size]) array( double , 10 ) dblarray; //定义double类型的数组 array( float , 10 ) fltarray; //定义float类型的数组
3.4 C++和C语言混合使用
C++是C语言的功能扩展,它们的区别体现在函数名上,C语言使用简单的函数名,它们的区别是:C++函数将参数类型列表与函数名本身一起作为函数名。(1)C++中调用C
C++语法通常在头文件中将整个头文件用extern "C"声明为C语言函数。方法如下:
extern “C” { int mlimitav( int lowend, int highend) ; double getpct( char * name) ; …… }
(2)在C中调用C++及其他面向对象语言
C中调用C++时,C++定义的函数格式与C函数一样,C++函数内部用C++实现,但函数定义需要使用extern "C"声明。C中调用C++函数就如同调用C函数一样。C中调用其他面向对象语言(如:Java、Qt)的函数的方法一样。
例如:C++的函数实现如下:
/* cppsayhello.cpp */ #include extern “c” void cppsayhello( char * str) ; void cppsayhello( char * str) { std:: cout << str << “/n”; }
C程序调用C++函数就象调用C函数一样,例如:
/*c2cpp.c*/ int main( int argc, char * argv[ ] ) { cppsayhello( “Hello from c to c++ ”) ; return ( 0 ) ; }
3.5 字符串国际化
国际化指程序中与用户交互的字符串支持多种语言。程序源代码只有一个,但与用户交互的字符串有多种。本地化指程序中与用户交互的字符串根据系统设置的本地语言不同,而显示不同语言但含义相同的字符串。如:系统设置为中国,则用户看到的字符串为中文。
不同的语言封装有不同的函数和工具来处理国际化和本地化,它们的机制类似。下面仅说明C语言的国际化和本地化。
C语言的本地化方法如下:
(1)先使用函数setlocale()指明本地化的项目,然后在程序中使用函数gettext()得到字符串的本地化版本。
(2)程序编写完后,使用工具xgettext从源代码文件中提取函数gettext()中的字符串,生成po文件,如:starter.po。用户还可以使用工具msgmerge合并两个po文件。
(3)用户编辑po文件,将po文件中的字符串进行翻译,每种语言复制一个po文件,并翻译成对应语言的字符串。
(4)po文件翻译完成后,使用工具msgfmt将每个po文件编译成对应的二进制文件,如:starter.mo。
(5)将每个二进制文件拷贝到LOCALEDIR目录的对应语言目录下,如:对于加拿大英语,拷贝到/usr/share/local/en_CA下。这样,程序运行时,函数gettext()能根据本地化设置读取对应的字符串。
例如:一个使用国际化和本地化的C语言程序列出如下:
/*starter.c*/ #include //定义用于本地化处理的数据类型、货币符号等数据结构 #include //包括需要配置和激活国际化处理的函数原型 #define PACKAGE “starter” //定义翻译的字符串文件名 #define LOCALEDIR “/usr/share/local” //定义本地化的字符串所在路径 int main( int argc, char * argv[ ] ) { setlocale( LC_ALL, “ ”) ; //指定需要国际化的项目 /*改变缺省值,设置翻译字符串文件路径为:/usr/share/local/category/starter.mo,category是本地语言宏定义LC_MESSAGES,值根据本地语言不同而不同,如:en_CA*/ bindtextdomain( PACKAGE, LOCALDIR) ; textdomain( PACKAGE) ; //设置域为starter,域是一套可翻译的msgid消息,为翻译字符串的文件名 printf ( “% s/n”, getext( “This string will translate.”) ; }
相关文章推荐
- linux(ubuntu 10.10) 64位系统 gcc编译(c)成32位(静态)可执行应用程序的方法
- linux编译引用一个或多个第三方jar包方法(总结转载)
- Linux环境编译OpenCV的应用程序方法
- Linux软件安装常用方法(转载)
- RedHat6.3(linux)编译Qt4.8.6中文不显示的解决方法
- 【转载】让女孩子更快学会Linux的方法与建议
- Nginx在Linux下配置的详细说明及配置方法 -转载好文章
- linux下debug与release编译的方法
- 【转载】在Linux中使用VS Code编译调试C++项目
- Linux系统平台下关于GCC编译及使用的方法
- linux下《UNIX环境高级编程》(apue2)源码编译出错的处理方法
- Linux下计算程序运行时间的两种方法(转载)
- linux下《UNIX环境高级编程》(apue2)源码编译出错的处理方法汇总
- Linux的源码及应用程序的编译浅析
- 1 开发一个注重性能的JDBC应用程序不是一件容易的事. 当你的代码运行很慢的时候JDBC驱动程序并不会抛出异常告诉你。 本系列的性能提示将为改善JDBC应用程序的性能介绍一些基本的指导原则,这其中的原则已经被许多现有的JDBC应用程序编译运行并验证过。 这些指导原则包括: 正确的使用数据库MetaData方法 只获取需要的数据 选用最佳性能的功能 管理连
- linux中编译静态库(.a)和动态库(.so)的基本方法
- 转载:在Linux平台中调试C/C++内存泄漏方法
- linux 内核编译 传统方法和新方法
- APUE2作者提供的源码编译方法及单个源码编译的实现(转载)
- linux 编译内核几个常见问题解决方法