Chinaunix首页 | 论坛 | 博客
  • 博客访问: 180595
  • 博文数量: 116
  • 博客积分: 1845
  • 博客等级: 上尉
  • 技术积分: 1292
  • 用 户 组: 普通用户
  • 注册时间: 2008-06-18 19:53
文章分类

全部博文(116)

文章存档

2014年(25)

2013年(1)

2012年(2)

2011年(20)

2010年(43)

2009年(17)

2008年(8)

我的朋友

分类: Python/Ruby

2010-05-29 00:10:39

背景

Windows平台上的编译工程工具很多,Visual C++ 6的dsp文件,2003以后用的vcproj,以及最近2010支持的用msbuild编译的vcxproj,加上古老的nmake,cygwin、msys移植的make,等等很多。

make的问题是扩展性比较差,尤其是在Windows平台上,nmake的功能更弱,导致写一个工程文件很费劲,管理多个工程有大量的重复工作要做。make最大的问题是不能automake那种简洁的工程写法(automake同样存在扩展问题,m4语言不懂).

最新的msbuild 4.0试用了一下,感觉不是很好,很费劲。因为最近迷恋上了命令行,一直不愿意安装完整的Visual Studio 2010 Beta 2,直接把2010的命令行拷贝出来用cl.exe之类的。想只装.Net Framework 4.0就试试msbuild,发现不行,把2010里的msbuild相关资源拷贝出来也不行。而且用XML手写工程文件很麻烦。

猛然想起之前听说过,鼎鼎大名,但之前简单了解后放弃了的scons。再次使用之后,才发现scons的妙处。


简介Scons

Scons首页赫然是Eric Raymond的推荐,很是唬人的样子。Scons是python写的,这不重要。Scons脚本用语言是python,这是Scons最吸引人的地方。用python写工程文件无疑对我这种熟悉python的人有很大的吸引力。

实际上,Scons最大的优势,就是描述语言本身沿用python。这种设计极大的提升的Scons的扩展能力。Scons本身功能是否强大不重要,对于熟悉python的人来说,扩展Scons达到他的要求,变得比以前简单的多。下面是我用Scons写的一个zlib的工程文件,其中WinLib和WinDLL是我自己扩展的在Windows平台上编译静态库和动态库的方法(cl的宏定义和link的参数有所不同)。

target = 'libz'
defs = 'ASMV ASMINF'
incs = '.'
 
Import('env')
lib_src = Split('contrib/masmx86/inffas32.asm contrib/masmx86/gvmat32.asm adler32.c compress.c crc32.c deflate.c gzio.c infback.c inffast.c inflate.c inftrees.c trees.c uncompr.c zutil.c contrib/masmx86/gvmat32c.c')
lib_src += env.RES('win32/zlib1.rc')
dll_src = lib_src + Split('win32/zlib.def')
 
Import('WinLib WinDLL')
lib = WinLib(target, lib_src, CPPDEFINES=defs, CPPPATH=incs)
dll = WinDLL(target, dll_src, CPPDEFINES=defs, CPPPATH=incs)
 
Alias(target, [lib, dll])

实际上Scons本身的功能比较有限,为了写成这个简洁的模式,还费了不少劲。

Scons的特点

简单列举下最近有体会的Scons的特点,权当备忘录了。

1. Variables的设计很好。

通过Variables可以扩展scons支持的命令行参数,限制参数种类(Bool,枚举,多选),可以把上次的参数保存到文件,下次编译不需要命令行输入过多的参数。下面摘下我的Variable定义:

vars = Variables('options.ini')
vars.AddVariables(
    BoolVariable('verbose', 'Show verbose messages', 'no'),
    BoolVariable('unicode', 'Use unicode API', 'yes'),
    BoolVariable('debug', 'Generate debug code', 'no'),
    EnumVariable('warn', 'Show warning level', 'no', ['no', 'low', 'all']),
    EnumVariable('optimize', 'Set optimization mode', 'normal', ['normal', 'max']),
    PathVariable('repos_root', 'Path to root of source repository', '..'),
    PathVariable('build_root', 'Path to root of build directory', 'build',
        PathVariable.PathIsDirCreate),
    PathVariable('output_root', 'Path to output directory', '.',
        PathVariable.PathIsDirCreate),
)
env = Environment(ENV = os.environ, variables = vars)

2. Environment的设计也很好。

Environment可以保存全套的编译命令,参数配置,用Clone操作可以从一个基本编译环境作出很多变种,支持不同项目类型,不同配置的编译。Clone实在是太方便了。下面是前面提到的Windows平台下编译DLL和静态库、命令行程序的不同参数配置,用Clone操作可以在基本编译环境上,生成多个用于编译静态库、DLL等不同的编译环境,使用不同的参数编译。

libEnv = env.Clone()
libEnv.Append(CPPDEFINES = Split('_WINDOWS _WINDLL'), LINKFLAGS = ['/SUBSYSTEM:WINDOWS'])
dllEnv = env.Clone()
dllEnv.Append(CPPDEFINES = ['_WINDOWS'], LINKFLAGS = ['/SUBSYSTEM:WINDOWS'])
conEnv = env.Clone()
conEnv.Append(CPPDEFINES = ['_CONSOLE'], LINKFLAGS = ['/SUBSYSTEM:CONSOLE'])

题外话,关于Windows下编译不同工程的参数,这篇文章总结的很全。

3. Builder的设计问题。

Scons可以用env.Library(‘liba’, ‘a.c’)的方法用环境的定义编译静态库,或者用Program定义可执行程序。并且支持输入和环境定义不同的参数,如

env.Library(‘liba’, ‘a.c’, CPPDEFINES = ['NEWMACRO'], CCFLAGS=['/Ox'])。

不过,很可惜,Builder默认是覆盖环境中现有参数,而不是追加。所以,要用多个编译参数稍有不同的程序和库编译只能用Clone后Append。

tempEnv = libEnv.Clone()
tempEnv.Append(CPPDEFINES = ['NEWMACRO'], CCFLAGS=['/Ox'])
tempEnv.Library('liba', 'a.c')

或许我要求太高,不过这样写看起来实在太傻了。实际使用中,不同的程序用的编译参数就没有完全一样的,尤其是Include目录、宏定义、库路径、库这四个参数肯定有或多或少的不一样。不能追加的话,就只能Clone出很多个Env,甚至是一个Program或Library一个Env,这就失去了Env封装编译环境的通用意义了。

4. Sconscript与源码的位置问题

Scons设计时更多是支持将Sconscript放在源码同一目录下,甚至每个源码目录一个Soncscript。如果不习惯或者不能用这种方式,比如为别的项目写工程文件,遇到一些困扰。

Scons好在有类似make的VPATH功能,叫Repository,用着还行。

5. 源码和编译目录分开的问题

这个问题和上面类似,但更严重。Scons支持用VariantDir设定与源码目录不同的编译目录,但必须所有的文件都用编译目录的路径,如下

VariantDir('build_dir', 'src_dir')
Program('build_dir/prog', 'build_src/main.c')

这就很傻了,明明源码在src_dir目录,一定要写成build_src。不这么写也行,那就写两个Sconcript文件,一个引用另外一个

#SConscriptA
Program('prog', 'src/main.c')
 
#SConscriptB
SConscript('SconscriptA', variant_dir='build')

要用好VariantDir,就必须写多个脚本。尤其是要支持编译同一程序的多个版本,还只能用两个脚本。SconscriptB可以按如下方式写,就能编译两个版本的prog,分别在debug和release目录下。用一个Sconscript完成同样功能实在是很费劲的。

SConscript('SconscriptA', variant_dir='debug')
SConscript('SconscriptA', variant_dir='release')

6. 同一个源码编译多份的问题

这个问题和上面类似,但不同。例如编译静态库和动态库都用一批源码,但编译参数不一样,Scons就会报错。但这个问题用variant_dir解决不方便。如下例:

Program('hello1', 'hello.c', CCFLAGS=['-Od'])
Program('hello2', 'hello.c', CCFLAGS=['-Ox'])

Scons报错如下:

scons: *** Two environments with different actions were specified for the same target:

经过我多方论证,发现这个问题目前看来最佳、最简单的解决方法是给OBJ文件设置前缀。

Program('hello1', 'hello.c', OBJPREFIX='dbg-', CCFLAGS=['-Od'])
Program('hello2', 'hello.c', OBJPREFIX='opt-', CCFLAGS=['-Ox'])

7. 简化编译输出的问题

Scons支持自定义的编译输出。但有些命令不能替换,例如msvc工具集中的链接过程的输出。

暂时这么多吧…想到再补充。

阅读(5826) | 评论(0) | 转发(0) |
0

上一篇:SCons User Guide 1.3.0

下一篇:皮蛋拌豆腐

给主人留下些什么吧!~~