标签 GCC 下的文章

使用Scons改造现有项目

今天是冬至,也是入冬以来感觉最冷的一天,毫不夸张的说:你一张嘴,牙就冻上了。上午LP在家收拾卫生,我继续用Scons改造现有的项目。下午出去理发,头发长长了后,似乎会造成思维迟钝^_^。

试验性的用Scons改造现有的project,过程中对Scons了解又多了一些。上篇文章对Scons的性能没有给出定论,经过对Scons的深入后,发现Scons在执行初始时的性能的确不够快,这是因为Scons启动后,会对全部SConstruct以及下面子目录中的SConscript进行分析,子目录越多Sconscript文件个数越多,性能也就越差。但是这种分析也有一个优点,就是能帮你提前发现你SConscript中的一些“语义”错误,比如如果你在编译两个基础库,一个叫add,一个叫sub,这个基础库源码分别分布在两个目录add和sub中,编译后将分别生成libadd.a和libsub.a的库文件,但是如果你马虎了,在编写SConscript时将target都写成了'add'或都写成了'sub',则Scons会在执行gcc之前就帮你找出这个"语义"错误,提示如下:
/export/home1/tony_bai/xxlib>scons -f SC*t
scons: Reading SConscript files …
scons: *** Multiple ways to build the same target were specified for: /export/home1/tony_bai/xxlib/lib/libsub.a  (from ['/export/home1/tony_bai/xxlib/add/libsub.a'] and from ['libsub.a'])
File "/export/home1/tony_bai/xxlib/sub/SConscript", line 3, in

Scons脚本基本写的差不多了,编译也ok了,但是编译出来的可执行程序在执行时却出现了问题:提示找不到某.so文件。而用项目"原配"的Makefile编译出来的可执行程序却执行的很好,没有类似问题,百思不得其解。将.so文件所在目录放到"LD_LIBRARY_PATH"中,问题得以解决,但这更加深了对这一现象的质疑。起初我一直以为是Scons在编译选项上不规范造成的,而Scons使用gcc -G -o xx.so xx.o来编译也的确有值得的怀疑点,-G选项是我从未见过的gcc编译选项,查了半天手册也没有对该参数的说明,遂放弃。上工具吧!先用ldd对编译出来的可执行文件进行分析,我们先来假设用Scons编译出来的可执行程序名字为Bin-scons,用"原配"Make编译出来的可执行程序名字为Bin-make。ldd将列出可执行文件中动态依赖的库的名字,并在本机定位出各个动态库的位置。对Bin-scons和Bin-make分别ldd的结果却让我大吃一惊,Bin-scons的ldd结果很正常,xx.so出现在list中,并且其位置为我刚刚加入到LD_LIBRARY_PATH中的那个目录;但是Bin-make的ldd结果中却不见了xx.so的踪影,这是怎么回事呢?回头翻看Makefile,并且又执行了多遍Make,项目的Makefile明明是构造了xx.so,在生成Bin-make时链接了xx.so,并且Bin-make中使用了xx.so中提供的接口。再次仔细对比Make和Scons编译.so时的差别,这回发现了些许不同的地方,"原配"的make在编译.so时,除了用了-shared -fPIC之外,还用了"-c"选项,而从Scons日志中只能看到gcc -G -o libxx.so xx.pic.o,显然Scons先控制gcc将xx.c编译为xx.pic.o,再由xx.pic.o构成libxx.so,而且我发现用Scons和Make编译出的.so文件大小居然不同。显然"-c"对两个编译过程带来了影响。一般来说,我们在编译一个动态库时是不会使用"-c"的,这里先不论项目Makefile写的是否ok,单说"-c"会给编译过程带来什么吧。打开gcc的"–verbose"开关,我们来试试使用和不使用"-c"gcc都做了些什么。还是以add.c为例,将add.c编译为libadd.so。

gcc -o libadd.so -shared -fPIC -c add.c –verbose
执行结果:
Reading specs from /usr/local/lib/gcc-lib/sparc-sun-solaris2.9/3.2/specs
Configured with: ../configure –with-as=/usr/ccs/bin/as –with-ld=/usr/ccs/bin/ld –disable-nls
Thread model: posix
gcc version 3.2
 /usr/local/lib/gcc-lib/sparc-sun-solaris2.9/3.2/cc1 -lang-c -v -D__GNUC__=3 -D__GNUC_MINOR__=2 -D__GNUC_PATCHLEVEL__=0 -D__GXX_ABI_VERSION=102 -Dsparc -Dsun -Dunix -D__svr4__ -D__SVR4 -D__PRAGMA_REDEFINE_EXTNAME -D__sparc__ -D__sun__ -D__unix__ -D__svr4__ -D__SVR4 -D__PRAGMA_REDEFINE_EXTNAME -D__sparc -D__sun -D__unix -Asystem=unix -Asystem=svr4 -D__NO_INLINE__ -D__STDC_HOSTED__=1 -D__SIZE_TYPE__=unsigned int -D__PTRDIFF_TYPE__=int -D__WCHAR_TYPE__=long int -D__WINT_TYPE__=long int -D__GCC_NEW_VARARGS__ -Acpu=sparc -Amachine=sparc add.c -quiet -dumpbase add.c -version -fPIC -o /var/tmp//cca0mHxn.s
GNU CPP version 3.2 (cpplib) (sparc ELF)
GNU C version 3.2 (sparc-sun-solaris2.9)
        compiled by GNU C version 3.2.
ignoring nonexistent directory "NONE/include"
ignoring nonexistent directory "/usr/local/sparc-sun-solaris2.9/include"
#include "…" search starts here:
#include search starts here:
 /usr/local/include
 /usr/local/lib/gcc-lib/sparc-sun-solaris2.9/3.2/include
 /usr/include
End of search list.
 /usr/ccs/bin/as -V -Qy -s -K PIC -o libadd.so /var/tmp//cca0mHxn.s
/usr/ccs/bin/as: Sun WorkShop 6 update 2 Compiler Common 6.2 Solaris_9_CBE 2001/04/02

gcc -o libadd.so -shared -fPIC add.c –verbose
执行结果:
Reading specs from /usr/local/lib/gcc-lib/sparc-sun-solaris2.9/3.2/specs
Configured with: ../configure –with-as=/usr/ccs/bin/as –with-ld=/usr/ccs/bin/ld –disable-nls
Thread model: posix
gcc version 3.2
 /usr/local/lib/gcc-lib/sparc-sun-solaris2.9/3.2/cc1 -lang-c -v -D__GNUC__=3 -D__GNUC_MINOR__=2 -D__GNUC_PATCHLEVEL__=0 -D__GXX_ABI_VERSION=102 -Dsparc -Dsun -Dunix -D__svr4__ -D__SVR4 -D__PRAGMA_REDEFINE_EXTNAME -D__sparc__ -D__sun__ -D__unix__ -D__svr4__ -D__SVR4 -D__PRAGMA_REDEFINE_EXTNAME -D__sparc -D__sun -D__unix -Asystem=unix -Asystem=svr4 -D__NO_INLINE__ -D__STDC_HOSTED__=1 -D__SIZE_TYPE__=unsigned int -D__PTRDIFF_TYPE__=int -D__WCHAR_TYPE__=long int -D__WINT_TYPE__=long int -D__GCC_NEW_VARARGS__ -Acpu=sparc -Amachine=sparc add.c -quiet -dumpbase add.c -version -fPIC -o /var/tmp//ccz128Nl.s
GNU CPP version 3.2 (cpplib) (sparc ELF)
GNU C version 3.2 (sparc-sun-solaris2.9)
        compiled by GNU C version 3.2.
ignoring nonexistent directory "NONE/include"
ignoring nonexistent directory "/usr/local/sparc-sun-solaris2.9/include"
#include "…" search starts here:
#include search starts here:
 /usr/local/include
 /usr/local/lib/gcc-lib/sparc-sun-solaris2.9/3.2/include
 /usr/include
End of search list.

 /usr/ccs/bin/as -V -Qy -s -K PIC -o /var/tmp//ccoU5RTD.o /var/tmp//ccz128Nl.s
/usr/ccs/bin/as: Sun WorkShop 6 update 2 Compiler Common 6.2 Solaris_9_CBE 2001/04/02
 /usr/local/lib/gcc-lib/sparc-sun-solaris2.9/3.2/collect2 -V -G -dy -z text -Y P,/usr/ccs/lib:/usr/lib -Qy -o libadd.so /usr/local/lib/gcc-lib/sparc-sun-
solaris2.9/3.2/crti.o /usr/ccs/lib/values-Xa.o /usr/local/lib/gcc-lib/sparc-sun-solaris2.9/3.2/crtbegin.o -L/usr/local/lib/gcc-lib/sparc-sun-
solaris2.9/3.2 -L/usr/ccs/bin -L/usr/ccs/lib -L/usr/local/lib/gcc-lib/sparc-sun-solaris2.9/3.2/../../.. /var/tmp//ccoU5RTD.o -lgcc_s -lgcc_s
/usr/local/lib/gcc-lib/sparc-sun-solaris2.9/3.2/crtend.o /usr/local/lib/gcc-lib/sparc-sun-solaris2.9/3.2/crtn.o
ld: Software Generation Utilities – Solaris Link Editors: 5.9-1.276

对比这两次的执行结果,我们可以发现,使用了-c的编译过程实际上不是一个完整的共享库(动态库.so)的构建过程,而只是一个带有"-shared, -fPIC"的目标文件(.o)的编译过程,缺少gcc crt目标文件的链接过程,只是目标文件被命名为libadd.so了。这恰恰能解释我们前面提到了两点疑问了。为什么ldd Bin-make时没有发现其依赖xx.so以及Bin-make执行时一切ok,没有报“找不到xx.so”,这一切都是因为xx.so实际上是以.o形式存在的一个文件,在构建Bin-make链接xx.so时,实际上做到是静态链接而不是动态链接,xx.so中的接口代码都已经存在于Bin-make中了,所以ldd无法找到对xx.so的依赖,Bin-make执行时也无需找到xx.so了。看来这是项目Makefile中的一个问题了,只是这个"问题"隐藏太久而未能被发现罢了。

从收音机中得知"冬至"这天应该吃饺子,晚上和LP煮了两包水饺,热腾腾的,吃得直打饱嗝^_^。

发掘Scons

发现或者说知道SCons是缘于Google的comp.lang.c group上的一则名为"Best Build Tool for large C projects "的帖子,帖子的作者列出了11条他认为"Best Build Tool"应该具备的特点,并欲找到这样的Build Tool。在该帖子的回复中,有人提到了Scons,说来惭愧,这是我第一次听说到有这样一个工具。一直在Unix下编写C程序,习惯了Make,也对Make的复杂度和较为陡峭的学习曲线有所了解,曾经尝试使用AutoconfAutomake,但是都因上手困难而放弃。自己心底也一直想找到一个更简单一些的但又不失功能的适合C的Build Tool,Scons是否能满足的需要的呢?好奇心驱使着我去发掘一下Scons。

工具的进化一直在持续着。高手能把Make玩弄于股掌之中,但是大多数人水平还是一般的,在经历了"Make hell"后他们要寻求更简单、更人性的工具,这也是工具进化的动力之一。Scons是用Python实现的一款跨平台的开源Build Tool,用Python实现意味着Scons比Make所使用的类Shell语言更贴近于自然语言,更易于理解和控制;用Python实现的另一个好处也是Make所不具备的就是很好的跨平台能力,一次编写Build脚本,在多种平台上无需修改即可运行无误,特别是从Unix->Windows这样的移植,如果使用Make则势必要修改。

先简单说说Scons的安装,要运行Scons势必你的机器上要有Python,虽然Python 3.0已经Release,但目前主流Python开源项目仍然在用2.x版本。我的机器上安装的就是Python 2.5。下载Scon-1.10稳定版,unzip,进入unzip后的目录,执行安装命令:python setup.py install即可。Scons会被安装到默认目录下,如果你想指定安装目标目录的话,可以使用–prefix=YOUR_INSTALL_DIR参数。

按照惯例,我们先来一个"Hello, World!"的例子,在你的测试目录下,编写一个HelloWorld.c
/* HelloWorld.c */
#include <stdio.h>
 
int main(int argc, char* argv[])
{
   printf("Hello, world!\n");
   return 0;
}
在同一级目录下,建立一个新文件SConstruct,编辑该文件,输入内容:
Program(‘HelloWorld.c’)
在命令行下执行scons,一个名为HelloWorld.exe的可执行文件(在Unix下可执行文件为HelloWorld)被编译链接成功。第一次上手成功会给使用者带来莫大的成就感,提高该使用者继续发掘该工具的可能性。

SConstruct是个什么文件?SConstruct之于Scons就好比Makefile之于Make;它是Scons的输入,SConstruct中的内容采用的是Python的语法,而Python的语法比较简单,这样很容易被接受,而Program则只是一个方法调用。Program(‘HelloWorld.c’)意味着告诉Scons我要将HelloWorld.c编译成一个名为‘HelloWorld.exe’的可执行文件,当然了Scons会自动分析HelloWorld.c,自动得出目标程序名字。

我们日常工作构建代码的类型不外乎如下几种:简单一点的包括编译object文件、构建静态库、构建动态链接库和构建可执行程序;复杂的则是要对一个拥有众多目录和几十万、上百万行代码的项目进行整体体系构建,而复杂的构建也是由一系列的简单构建组合而成的,我们先说说简单类构建。

HelloWorld例子只是一个最简单的由单个源文件构建程序的例子,现实中我们构建可执行程序可能依赖的不止是一个文件,可能还有头文件或链接其他第三方库;下面这个SConstruct文件中的语句就是一个稍微复杂些的例子:
Program(target = ‘test’, source = ['main.c', 'file1.c', 'file2.c'], LIBS = ['lib1', 'lib2'], LIBPATH = ['lib1/lib', 'lib2/lib'], CPPPATH = ['include', '/lib1/include', 'lib2/include'], CCFLAGS=’-D_DEBUG’)

这个例子中具备我们常用的诸多元素,这些参数中:’test’是构建后的程序名,source是一个源文件数组,LIBPATH则是要链接库的目录数组,LIBS是要链接的具体的库文件的名字。CPPPATH则是-I的替代品,是头文件所在目录的数组,CCFLAGS则是负责传递编译器的编译选项参数。

通过这些Keyword Arguments,Scons可以在用户和编译器之间传递信息,并控制编译器完成构建。同样的,编译目标文件,构建静态库、动态库可以由下面的一些builder来完成。

Library(‘foo’, ['f1.c', 'f2.c', 'f3.c'])   #生成名为foo的静态库,在Windows上是foo.lib,在unix上为libfoo.a
<=> StaticLibrary(‘foo’, ['f1.c', 'f2.c', 'f3.c']) #生成名为foo的静态库,在Windows上是foo.lib,在unix上为libfoo.a
SharedLibrary(‘foo’, ['f1.c', 'f2.c', 'f3.c']) #生成名为foo的动态库,在Windows上是foo.dll,在unix上为libfoo.so
Object(‘add.c’) #生成名为add的目标文件,在Windows上是add.obj,在unix上为add.o

Scons没有明显的依赖定义,Scons会为我们自动扫描依赖。我们只需告诉它构建出一个目标需要什么即可。Scons检查依赖关系中的文件变化的方法,除了通过时间戳,还可以通过MD5来判别,你可以通过设置Env来决定使用哪个。另外更强大的是你也可以自己编写文件更新检查方法放到SConstruct中被Scons调用,这些都是高级一些的功能,这里不细说,详情可参见Scons的doc

前面说过,实际项目的代码往往不可能都放到单一目录下,而是按照一定规则被放到有层次结构的目录体系中,Scons提供一个叫SConscript的方法支持这种情形。下面用一个复杂一些的例子来说明这种情形。
我们假设有一项目的目录结构如下:
- Test_Proj
    - SConstruct
    - include
        - base.h
        - module1.h
        - module2.h
    - main
        - main.c
    - module1
        - module1.c
    - module2
        - module2.c
    - xlib
        - include
            - xlib_base.h
            - add.h
            - sub.h
        - add
            - add.c
        - sub
            - sub.c
        - lib
针对该Proj,我们要将整个工程构建为一个可执行程序。简单分析一下,这个程序依赖xlib下的两个库

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

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

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

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

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

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats