发掘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的复杂度和较为陡峭的学习曲线有所了解,曾经尝试使用Autoconf和Automake,但是都因上手困难而放弃。自己心底也一直想找到一个更简单一些的但又不失功能的适合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下的两个库
评论