最近用scons的收获
时间:2010-05-29 来源:sophia_wang99
背景
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工具集中的链接过程的输出。
暂时这么多吧…想到再补充。