标签 GNU 下的文章

buildc 0.1.5版本发布

这两天对buildc的改动比较频繁,今天又修正了一些问题,也增加了一些小功能。主要包括这么几点:

 
1、在Make.rules.in中增加了STATIC_LIBS和DYNAMIC_LIBS
 
项目源代码和项目中单元测试代码使用同一个Make.rules,也此编译时也就共享同一个LIBS变量。对于静态共享库还好说,但对于动态共享库,诸如Oracle的instantclient库,单元测试代码中即使没有使用到动态共享库中的接口,也要对该动态共享库产生一个依赖。这样在执行单元测试用例时就会因无法寻得动态共享库而导致用例执行失败。
 
为此,我在Make.rules.in中增加了STATIC_LIBS和DYNAMIC_LIBS两个变量,即将原LIBS变量中的静态共享库和动态共享库分开,分别放入STATIC_LIBS和DYNAMIC_LIBS中。然后让项目中单元测试代码的编译只依赖STATIC_LIBS,上述问题就得到了解决(如果你的单元测试真实需要链接动态共享库,那就另当别论了)。
 
2、添加system_libs,并进一步明确了external_libs、custom_libs和新增的system_libs的含义
 
buildc设计之初,设计了三种lib:external_libs、custom_libs和default_libs,最初的设想是这样的:
 
  * external_libs – 一般配置第三方库或组织内部公共库;
  * custom_libs – 项目相关的C运行库和*nix系统库依赖库,或一些项目内部实现的库;
  * default_libs – C后台应用一般都需要链接的C运行库和*nix系统库,惯例优于配置,直接写死在代码中。
 
但实际运用时发现,custom_libs如果既配置C运行库或*nix系统库依赖库,又配置一些项目内部实现的库,会存在静态共享库依赖顺序问题,另外custom_libs与external_libs之间也会因此而存在库链接顺序之问题。而default_libs目前为空,因为很难找到各个项目的一般依赖。
 
于是这次我对这个设计进行了一些修正,增加了SYSTEM_LIBS,并进一步明确了这些lib配置的含义,依顺序如下:
 
  * custom_libs – 一般配置项目自实现、自用的库,可能包含在项目源码库内部,与项目源码库一并发布;
  * external_libs – 一般配置第三方库或组织内部的公共库;
  * system_libs – 用来替代default_libs,配置项目需要的C运行时库或*nix系统库,放在所有库的最后面。
 
default_libs似乎没有太大必要了,后续也许会从代码中remove出去。
 
3、增加cache upgrade
 
通过实践发现,目前buildc提供的对本地缓存的Library的管理手段还有欠缺,特别是当.buildc.rc发生变更时,需要执行buildc cache remove和buildc cache init才能正确完成更新,稍显繁琐,因此今天给buildc增加了一个cache upgrade命令,用于改善这个情况。而buildc cache update一般仅用于.buildc.rc的库配置没有改变,但subversion库中的库二进制文件被更新(比如重新制作了)的情况。这样看来还是.buildc.rc变更的情况常见些,比如某个库的版本升级了(例如,lcut从0.2.0升级为0.3.0),或某个库的位置发生了变化,或删减了某些库或新增了某些库等等。
 
以上就是buildc 0.1.5版本的所有改动了。

buildc 0.1.4版本发布

年后buildc开始逐渐在产品线的项目里应用了,随之而来的是大家反馈的各种意见和bug。尤其是bug,我都会很认真地应对,也会及时发布相应的版本修复这些bug。buildc 0.1.4版本就是一个bugfix版本,其修复的bug源于今天上午的一次持续集成的失败。

 
上午收到Jenkins发送的一个"build failed"的mail,一个安装包项目CI job执行失败了,于是到Jenkins web页面上检查错误原因。这个Job会在两个slave node上执行集成,一个在x86 linux上,一个在x86 solaris上。这次失败是因为x86 linux上的一个配置问题导致的,页面显示x86 solaris那个节点的集成是成功的。我无意间查看了x86 solaris节点集成过程的命令行输出,发现如下内容:
 
Config Make.rules OK!
Failed to execute cmd [make CMODE=64-bit], errno = [512]
Finished: SUCCESS
 
显然,构建脚本并未真正成功,但Jenkins却认为这次集成是OK的,这是怎么回事呢?难道是Jenkins有问题?为了弄清原因,我登到那个Solaris节点上,进入到Jenkins slave工作目录下的workspace中,在对应的Job目录下,手工执行了集成脚本,执行结束后,通过"echo $?"查看命令的返回值,居然是"0",也就是说执行结果是成功的,这怎么可能?明明代码中输出是"errno= [512]"。
 
为了验证问题,我编写了一个测试程序以验证代码执行是否正确:
 
# testerrno.py
 
#! /usr/bin/env python
 
import commands
import sys
 
def execute(cmd):
        o = commands.getstatusoutput(cmd)
        if o[0] != 0:
            print "Failed to execute cmd [%s], errno = [%d]” % (cmd, o[0])
            sys.exit(o[0])
        return o
 
if __name__ == '__main__':
        execute('ls -l foo')
 
由于当前目录下并未有foo文件,因此预期testerrno.py的执行结果应该是失败的,执行过程如下:
 
$> testerrno.py
Failed to execute cmd [ls -l foo], errno = [512]
$> echo $?
0
 
从结果中可以看到,居然执行结果返回的真的是0。我将上述代码中的sys.exit(o[0])直接改为了sys.exit(512),我要看看究竟是怎么回事,结果执行结果又出乎我的意料:
 
$> testerrno.py
Failed to execute cmd [ls -l foo], errno = [512]
$> echo $?
0
 
居然还是0。突然脑海中冒出一个思路:难道是Shell不支持512这么大的errno?于是将512改为23,再试:
 
$> testerrno.py
Failed to execute cmd [ls -l foo], errno = [512]
$> echo $?
23
 
执行后得到的结果为:23。看来我的思路是正确的,那Shell支持的最大errno值究竟是多少呢?经验告诉我很可能是255。于是乎我又分别将返回值硬编码为255和256并执行之,结果当返回值为255时,echo $?的输出为255;当返回值为256时,echo $?的返回值居然变成了0,看来就是这个问题了。关于为何Python获取到的ls -l foo的执行结果为512(commands.getstatusoutput的返回结果),我没有深究,但如果手工在命令行上执行'ls -l foo',得到的返回值实际上是2,而不是512。另外我的Shell版本为:GNU bash, version 3.00.16。
 
如何修复buildc的这个问题呢?我的方法是让command.execute在执行命令出错时返回同一个指定的错误码,目前为errors.py中shell_cmd_exec_failed值。但command.execute会打印commands.getstatusoutput返回结果中的真实错误码值,两不耽误。
 
于是就有了这buildc 0.1.4版本,该版本在Python 2.4.3和Python 2.6.2下都测试OK。

关于编译阶段符号多重定义的问题

印象中关于编译以及链接的问题早已是老生常谈了。但今天又遇到了一个这样的问题,这里还总想提及一下下^_^。

 
这次要说的问题依旧发生在使用lcut进行单元测试的过程中。一位同事在编译使用了mock函数的测试用例代码时出现了"multiple definition of 'xxx'“的错误。这里简单模拟其场景如下:
 
/* testall.c */
 
/* mock lib function */
int lib_function(…) {
    …
    return (int)LCUT_MOCK_RETV();
}
 
int function_to_be_tested(…) {
    …
    ret = lib_function(…);
    …
}
 
void test_case(lcut_tc_t *tc, void *data) {
    ret = function_to_be_tested(…);
    LCUT_INT_EQUAL(tc, 0, ret);
}
 
lib_function是静态共享库中的一个接口,但这里被mock了。不过由于一些其他test_case使用了静态共享库(.a)的其他接口,因此在编译时程序链接了这个静态共享库。但结果编译器却报错:lib_function被多重定义了。
 
经过各种排查(编译器命令行中的目标文件与库链接顺序是否正确等),我们发现编译器报错的原因居然是忘记mock几个与lib_function同属一库模块(xx.o)的接口。
 
这里就不拐弯抹角了。由于漏掉了一些本该mock的接口,因此程序在编译testall.c时有许多unresolved的符号需要到静态共享库中去查找。这里又涉及到了符号resolve的过程,而更为重要的一点是要弄清楚编译器是如何对待静态共享库中那个拥有testall.o中未resolved的符号的库模块的(一个静态库.a文件实际上是由诸多库模块.o文件组合而成的)。我们来看看下面例子。
 
一个libcommon.a的组成如下:
libcommon.a
    – foo.o
        – function: foo1
        – function: foo2
    – bar.o
        – function: bar1
        – function: bar2 
 
我们来看一下一个调用了foo1函数且链接了libcommon.a的可执行文件(a.out,对应的源文件main.c)中都有哪些已定义的符号:
$ nm a.out
080483d4 T foo1
080483b4 T main
080483e2 T foo2
 
通过nm输出的结果可以看到,最终的可执行程序中居然包含了程序并未调用的函数foo2的定义。似乎一切都清晰了:编译器在libcommon.a的foo.o中找到了unresolved的符号foo1,但编译器并非只是将foo1的定义放入最终的可执行文件中,而是将foo.o从libcommon.a中取出,并将其与main.o放在一处同等对待,编译器会扫描foo.o中所有的符号,并确保其中的符号都是具有定义的,这样才能保证最终的可执行程序中所有的符号都是具有定义的。
 
现在我们可以回过头来回答本文开始处所遇到的那个"多重定义"的问题了。因为testall.c中存在未resolved的符号,即那些被漏掉的未mock的库接口,因此编译器找到了静态共享库中定义了这些库接口的库模块(某个.o文件),但编译器并非只是处理这些符号,和上面的例子一样,编译器还会扫描这个库模块文件中的所有符号以确保所有符号都有定义。而就在这个过程中编译器发现了其中有的符号(比如lib_function)的定义与testall.c中mock的同名接口(lib_function)定义相冲突,从而才作出了错误提示。
 
之前写过一篇文章《从mock malloc说起》,其中有关于编译过程中符号resolve的详细说明,有兴趣的朋友不妨看看。




这里是Tony Bai的个人Blog,欢迎访问、订阅和留言!订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:


以太币:


如果您喜欢通过微信App浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:



本站Powered by Digital Ocean VPS。

选择Digital Ocean VPS主机,即可获得10美元现金充值,可免费使用两个月哟!

著名主机提供商Linode 10$优惠码:linode10,在这里注册即可免费获得。

阿里云推荐码:1WFZ0V立享9折!

View Tony Bai's profile on LinkedIn


文章

评论

  • 正在加载...

分类

标签

归档











更多