资源描述
BuildSystem
Posted on 2011/09/01 by wy182000
Table of Contents
· 1 BuildSystem
· 1.1 GYP
· 1.1.1 设计目标
· 1.1.2 构建文件
· 1.1.3 .gyp文件剖析
· 1.1.3.1 conditions
· 1.1.3.2 targets
· 1.1.3.3 includes
· 1.1.3.4 actions
· 1.1.3.5 variables
· 1.1.4 early and late phases
· 1.1.5 operator
· 1.1.6 路径内容属性
· 1.1.7 总结
· 1.2 Scons
· 1.2.1 Make缺陷
· 1.2.2 构建文件
· 1.2.3 SConstruct文件剖析
· 1.2.3.1 Builder
· 1.2.3.1.1 编译参数和自动分析编译依赖
· 1.2.3.1.2 源文件使用不同参数编译
· 1.2.3.1.3 库链接
· 1.2.3.2 Dependencies
· 1.2.3.2.1 分析依赖
· 1.2.3.2.2 判断依赖变化
· 1.2.3.3 Environment
· 1.2.3.4 Hierarchical Builds
· 1.2.4 其他问题
· 1.2.5 总结
· 1.3 Ninja
· 1.4 ABS
· 1.5 CMake
· 1.5.1 构建文件
· 1.5.2 CMakeLists.txt解析
· 1.5.3 变量和属性
· 1.5.4 注释
· 1.5.5 编译类型
· 1.5.6 生成config.h
· 1.5.7 Makefile
· 1.5.8 总结
· 1.6 Others
· 1.7 个人感觉
1 BuildSystem
words from huangjun@(leemars528)
· 内部构建系统*必须*要求所有模块都使用这个构建系统。
· 模块的依赖*不应该*通过额外的系统来管理。
· *应该*能够指定足够细致的粒度。
· 就*应该*只依赖于需要生成接口所需要的最少内容。
1.1 GYP
gyp(generate your project)是chromium的构建系统,地址在
关于GYP和CMake的对比在
文档建设还是比较差的,并且个人使用一个很简单的例子都没有work成功。虽然wiki有UserDocumentation但是里面介绍非常粗略,基本上可以认为是一个没有成熟产品。
虽然没有比较好的使用文档,主页wiki里面还有有一些关于gyp本身比较好的描述,以及设计的初衷。 通过学习这些内容,可以对构建系统有更加深入的认识。感谢huangjun@(leemars528)给我的建议, 他透露这个可能是gg内部的一个构建系统原型。并且之前yangliu@(from google)给我举构建系统例子的时候, 表达方式上和gyp也非常相似,所以有理由相信gyp很像现在google内部的构建系统。
1.1.1 设计目标
gyp设计针对目标就是为了解决chromium浏览器构建问题,最重要的就是支持多平台的构建。因此生成的后端可能是Scons/Make(Unix/Linux),Xcode(Mac)或者是Visual Studio(Windows).并且因为chromium内部都是C/C++文件, 因此主要考虑方便C/C++程序的构建。设计时候还考虑到下面这些问题:
· debug vs. release.
· cross compile.
· toolchain interface.
1.1.2 构建文件
构建文件名字不固定,但是后缀通常是.gyp和.gypi(gyp included).构建文件内容就是python的一个数据结构(可以认为是json,不过允许#作为注释并且允许trailing的).这样做的一个方便结果就是为了读取构建文件信息, 只需要eval一下文件的内容即可,就可以得到这个构建文件的描述了。
下面是一个example:
{
'target_defaults':{
'defines':['DEBUG'],
},
'targets':[
{
'target_name':'test', #生成的文件.
'type':'executable', #可执行程序.
'sources':['test.cc'],
'defines':['FOO']
}
]
}
在后面部分会详细解释构建文件里面的每个element。
1.1.3 .gyp文件剖析
整个构建文件最顶层是一个字典,包含了下面这些key:
· conditions //条件判断
· includes //包含的构建文件
· targetdefaults //构建目标默认属性
· targets //构建目标列表
· variables //构建文件使用的变量
1.1.3.1 conditions
conditions分为两种行为。普通的conditions就在load构建文件之后立即计算,另外一种是targetconditions是在计算完成依赖之后然后来进行计算的,两个过程分别就是early and late phases阶段。对于conditions写法非常简单:
'conditions':[
['OS==Linux',{'sources':['linux_interface.cc']}]
]
对于condition的判断,主要还是为了能够修改一些描述属性。从文档上来看的话,默认提供的条件就是OS判断,其他判断应该都是变量的判断。
1.1.3.2 targets
target部分的话会对targetdefaults里面设定的内容默认进行merge。比如上面例子的话,对于target/test来说, 使用的defines就会是-DDEBUG -DFOO。当然对于这种东西是可以进行其他策略选择的,比如如果修改成下面格式,那么就是直接替换:
'defines=':['FOO']
生成的defines就是-DFOO了。或者是可以剔除掉:
'defines!':['DEBUG']
生成的defines就没有任何内容了。通过在选项key后面添加操作符号来达到自定义目的(相对于全局环境).
对于一个target包括了下面这些重要属性:
· actions(list) //执行命令
· alldependentsettings(dict) //如果依赖这个target的话,需要使用的设置
· configurations(dict) //配置
· defines(list) //对于C/C++的defines
· dependencies(list) //依赖对象。如果是本文件的话那么直接引用,如果是其他文件的话,使用path/xxx.gyp:target.
· directdependentsettings(dict) //直接依赖这个taregt的话,需要使用的设置
· includedirs(list) //头文件目录
· libraries(list) //目标需要链接的库
· linksettings(dict) //依赖这个target,需要使用的链接参数
· sources(list) //源文件
· targetconditions(list) //和conditions类似,但是是在完全计算之后然后来判断
· targetname(string) //名字
· type //目标类型,现在只是支持staticlibrary,sharedlibrary,executable和none
1.1.3.3 includes
gyp倾向的组织就是在toplevel上面存在一个gyp文件,可以存在子目录下面,但是子目录下面并不存放一个完整的构建文件, 通常只是存放构建文件的片段。为了区分,后缀为gypi。本身来说,这个gypi并不可以直接被gyp所接受生成native构建系统文件, 唯一的作用就是被toplevel的gyp文件进行include。如果对于Linux系统来说,最终生成的Makefile应该是一份大Makefile并且没有递归make的操作。 关于构造一个没有递归的Makefile是非常有价值的,不管是对于调试还是提升编译速度方面。可以参考文章Recusive Make Considered Harmful.
一旦我们允许include子目录的gypi文件进来,我们就必须规定哪些字段应该是文件。原因是假设存在src目录下面有src/BUILD.gypi这样一个文件,sources内容如下:
'sources':['src.cc']
而在上层BUILD.gyp文件里面,使用includes语法:
'includes':['./src/BUILD.gypi']
那么在生成大Makefile的时候,我们必须清楚'sources'字段里面内容都是文件,不应该直接使用src.cc, 相反应该加上目录前缀src,
最终应该使用src/src.cc这样一个文件。关于哪些字段里面内容是路径, 这个在gyp里面有详细规定,在后面小节里面我们也会提到。
1.1.3.4 actions
actions是targets里面的一个特殊属性,主要是用来进行target的自定义操作的。关于rule的部分, 应该问问huangjun@,因为他实际操作过gyp并且阅读过Chrmoium里面的.gyp文件。
每个action是一个dict,主要包含4个属性:
· actionname(string). //操作名称
· input(list) //输入文件
· outputs(list) //输出文件
· actions(list) //命令
有了这些属性就可以做一个完整的操作抽象。
1.1.3.5 variables
variables这个小节里面是进行变量的定义,格式是dict。下面是一个例子:
'variables':{
'common_files':['src/common.cc','src/interface.cc'],
}
为了引用变量,我们可以这样编写:
<(common_files)
<@(common_files)
>(common_files)
>@(common_files)
总之引用变量必须加上(),同时在前面加上<,<@,>,>@的4种中一种前缀符号。关于前缀符号的含义, 会在后面的operator小节里面说明。
对于变量类型,一共分为3类:
· predefined variables //预定义变量
· user-defined variables //用户定义变量
· automatic variables //自动变量
预定义变量比如OS(系统环境),EXECUTABLESUFFIX(可执行文件后缀).用户自定义变量就不再赘述。
自动变量类似于Makefile里面的$@,$这样的变量,好比反射。比如在targetconditions部分的话,我们根据不同类型程序来做不同的condition:
'target_conditions':[
['_type=='static_library',{'sources':['func.cc']}]
]
这样对于target为staticlibrary都会联编func.cc这个文件了,自动变量是就是属性名称之前加上构成的。
存在自动变量非常必要。有时候我们在全局环境中,希望根据不同的条件来定义不同的行为,并且是在计算的同时在来做条件判断的。 这样就提出一个要求就是,条件判断部分必须有能力知道,当前到底在计算什么东西(反射)。
1.1.4 early and late phases
对于变量展开和条件判断有两个不同的阶段:
· 载入文件之后进行,就是early phase。
· 计算完成之后进行,就是late phase。
对于两个阶段允许不同操作是非常必要的。对于early phase这个肯定需要,而对于late phase的话, 有时候我们是希望了解到gyp处理完成某个target之后所有信息,然后进行判断的。
ps:comake2在设计的时候,就没有考虑late phase这个功能。造成没有办法在应用层添加延迟计算这样一个特性。 最终只能够是修改comake2代码来完成需求。
1.1.5 operator
关于每个操作符号的含义:
· x= //字段内容进行覆盖
· x? //如果字段没有定义,那么就进行覆盖
· x+ //字段内容进行merge
· < (x) //early phase计算变量x,并且以string类型返回结果
· > (x) //late phase计算变量x,并且以string类型返回结果
· < @(x) //early phase计算变量x,并且以list类型返回结果
· > @(x) //late phase计算变量x,并且以list类型返回结果
· x! //从已有的x字段中排除部分
· x/ //操作允许使用include/exclude,内容是一个正则表达式来进行包含和排除列表里面内容
· < !(x) //认为x是一个shell command,得到执行结果作为string类型返回
· < !@(x) //认为x是一个shell command,得到执行结果作为list类型返回
1.1.6 路径内容属性
在includes这个小节提到了,gyp规定了某些属性的内容必须为路径。这些属性是:
· files.
· includedirs.
· inputs.
· libraries.
· outputs.
· sources.
但是gyp对于里面的内容也做了一些特殊处理。对于内容来说,如果以下面这些字符开头:
· / //绝对路径
· $ //变量
· - //链接参数比如-lm
· < ,>,! //operator
· 其他作为相对路径
1.1.7 总结
gyp文档缺乏导致在分析这个系统的时候,也没有完全使用只是通过阅读文档来完成的,没有一个可以run的Makefile。 同时Makefile也想当难读(尽管如此,还是稍微看了一下生成的Makefile).最好可以结合chromium源代码来看看gyp是如何使用的(时间有限,我没有做).
gyp虽然是构建系统,并且通常来说构建文件倾向于使用描述性语言来完成,但是类json风格的描述性语言很容易形成过深的嵌套不利于阅读。 可以考虑使用其他方式描述性语言来完成。如果可以的话,结合过程语言也未尝不可。
在运行时上gyp区分early和late phase两个阶段,同时允许通过targetdefaults这样一个section来设定全局属性。通过automatic variables这样的机制提供了反射功能,并且允许自定义操作,并且允许从外部shell中读取内容。 此外gyp允许提供跨模块之间的依赖管理(一个模块有如何这个模块被依赖的话,那么依赖这个模块的模块应该使用哪些属性).这些都是一个强大构建系统所必须的。
从gyp层面就考虑了如何避免调用recursive make,通过规定某些属性内容只允许为路径名称并且允许include其他目录的.gpyi文件, 理论上可以生成不需要recurisve make的Makefile。同时在生成Makefile上面考虑了cross compile,out-of-source build问题。
1.2 Scons
地址在http://www.scons.org/。
总体来说算是一个比较成熟的产品,也发布过相当多的版本经过很长时间的演化。 这里是一些已经使用scons的项目列表http://www.scons.org/wiki/SconsProjects。 可以看到比较有名的两个就是MongnoDB和v8。
1.2.1 Make缺陷
SCons对于Make缺陷也有一个总结,链接是http://scons.org/wiki/FromMakeToScons。 这里稍微总结一下,在设计Make代替品BuildSystem时候需要考虑到:
· make的命令是不可移植的,也就是说,对于*nix和win32必须单独编写不同的make命令。
· make对于递归目录处理会牺牲速度。同时从目录角度来说,可能会发生重复检查的情况。
· make语法十分ugly。
· make使用环境变量,这样造成很多问题不可复现。
· make对于一些检查过于简单,造成很多时候你必须make clean然后重新make。
· make缺乏自动分析编译依赖。
· make难以调试,主要是在规则冲突方面。
客观地说除了3之外,其他都是非常严重的问题。对于3的话,要看你使用Makefile到什么程度了。 除了这些之外,如果按照Ninja的说法来说,Makefile还有一个速度过慢的缺点。
1.2.2 构建文件
scons的构建文件名称是统一的都称为SConstruct。当然为了方便目录的组织,也允许在各个目录下面存放SConscript, 然后最上面SConstruct收集这些SConscript组织成为一个大的构建文件。
和gyp所不同的是,scons并没有生成一个所在平台的构建系统文件(linux->Makefile),而是直接解释SConstruct来进行构建。 避开native build system文件的生成在一定程度上可以简化工作量,但是自己需要来完成整个构建活动。 同时速度可能会打一些折扣(比如现在google推出的ninja的构建系统构建系统很好,而scons就不能有效利用这个工具,而gyp只需要稍加改动就支持).个人观点来看的话,生成native build system文件可能是一个更好的选择。
下面是一个简单例子
Program('main.cc')
然后使用方式是
[zhangyan@tc-cm- test-scons]$ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o main.o -c main.cc
g++ -o main main.o
scons: done building targets.
使用scons默认用来构建里面所有的内容。当然也可以使用scons <target>来构建某个或者是某些target。
scons -c相当于make clean,scons -Q的话可以安静地构建,只是打印出真正执行的命令。
如果是需要编译多个文件的话,可以使用python的列表格式。同时这些scons内置函数也是支持python的keyword argument这样的形式的:
Program(target='hello',source=['main.cc','hello.cc'])
然后我们执行
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o main.o -c main.cc
g++ -o hello.o -c hello.cc
g++ -o hello main.o hello.o
scons: done building targets.
我们可以看到,在这个SConstruct这个Python Script里面Program等东西都实现成为了scons的内置函数,
然后在表达上文件列表就是使用python列表形式。这样在一定层面上这就让用户接触到了[]这样的格式, 用户可能就会意识到当前可能是一个脚本,而不是以一种透明方式展现出来。不过通常来说,这样问题并不是很大, 用户也能够忍受列表以及keyword argument这样的东西,同时对于高级用户(熟悉python)的人也感到比较亲切。
同时我们必须注意到,scons在构建目录下面存放了一个.sconsign.dblite这样一个文件。这个文件应该是 使用sqlite这样关系数据库的。因为scons很多特性需要依赖存储数据,比如cache隐式依赖,记录文件的md5等等信息, 单独使用一个辅助文件存放信息是一个不错的选择。
1.2.3 SConstruct文件剖析
1.2.3.1 Builder
SConstruct主要支持的开发语言有C/C++以及Java。对于Java有专门的Builder,这里我们不谈.对于C/C++的Buidler有下面这几种:
· Program //可执行程序
· StaticLibrary //静态库
· SharedLibrary //动态库
· Object //目标文件
当然为了完备,还提供了一个万能的Buidler就是Command。对于Command来说的话,使用方式如下:
Command('hello',['main.cc','hello.cc'],'g++ -o $TARGET $SOURCES')
撇开具体使用方法我们不说,对于自定义命令来说,我们最主要关心三个方面:
· target
· dependencies
· commands
在Command这个Builder很完整。同时为了方便还允许用户使用automatic variables,比如$TARGET和$SOURCES这两个变量, 类似于Makefile里面的$@和$。关于Command讨论就到这里,接下来看看上面三个主要的Builder。
1.2.3.1.1 编译参数和自动分析编译依赖
默认情况下面如果这样写的话,那么是不能够制定任何编译参数的:
Program('main.cc')
要不使用下一节方法一样构建一个环境,要不就需要显示地构造Object对象:
obj=Object('main.cc',CCFLAGS='-DDEBUG')
Program('main',obj)
默认情况下面是不会自动分析编译依赖的,也就是说,如果main.h头文件发生变化的话,是不会发生重新构建的。
为了强制能够自动分析编译依赖,需要显示写明CPPPATH,就是include path:
obj=Object('main.cc',CCFLAGS='-DDEBUG',CPPPATH=['.'])
Program('main',obj)
Decider('make')
Decider的意义是说按照时间戳更新的方式来检查,这个在检查依赖这节会提到。在做法上,
个人猜测scons也是使用g++ -MM -MG这样的方式来分析编译依赖。通常来说耗时会比较长。因为scons允许在调用参数中缓存上次编译依赖, 强制刷新编译依赖等,这样可以在耗时和功能上达到一个比较好的折衷。
1.2.3.1.2 源文件使用不同参数编译
假设对于
env1=Environment(CCFLAGS='-DDEBUG')
env1.Program(target='debug_main',source=['main.cc'],)
env2=Environment()
env2.Program(target='release_main',source=['main.cc'])
关于Environment会在后面讲到,因为scons对于main.cc直接生成main.o这样文件,如果main.cc使用不同参数来进行编译的话如上,
那么就可能出错。scons可以检测到对于同一个target使用不同参数编译,那么在执行时:
[zhangyan@tc-cm- test-scons]$ scons
scons: Reading SConscript files ...
scons: *** Two environments with different actions were specified for the same target: main.o
File "/home/zhangyan/test-scons/SConstruct", line 4, in <module>
为了解决这个问题,可以使用Object这样的Builder来指定产生不同的文件名:
env1=Environment(CCFLAGS='-DDEBUG')
obj1=env1.Object(target='debug_main',source=['main.cc'])
env1.Program(obj1)
env2=Environment()
obj2=env2.Object(target='release_main',source=['main.cc'])
env2.Program(obj2)
执行之后
[zhangyan@tc-cm- test-scons]$ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o debug_main.o -c -DDEBUG main.cc
g++ -o debug_main debug_main.o
g++ -o release_main.o -c main.cc
g++ -o release_main release_main.o
scons: done building targets.
1.2.3.1.3 库链接
可执行程序需要进行库链接,使用方式也是非常简单。Program这个Builder下面提供LIBPATH和LIBS这两个keyword arguments, 可以用来指定所需要链接的库。
这样来写链接库首先是比较符合用户习惯的,但是这样会造成一个问题,这个在comake2中出现过,也是导致comake2并不提倡这个做法的原因, 那就是这样写的话不好构建依赖。假设下面两种写法:
Program('main.cc',LIBS='./lib/libcm.a')
Program('main.cc',LIBS='cm',LIBPATH=['./lib'])
很明显第1中方式不仅仅写了所链接的库,scons本身还可以从中知道依赖什么文件,而comake2的做法就是希望用户显式依赖某个库。
但是在scons中绕过了这个问题,是因为scons还有两个函数用来显示声明依赖的。对于显示声明依赖,个人看法是这个东西必须做为一个可选项, 对于小型程序显然用户希望构建系统自己生成依赖,而对于大型程序,显示声明依赖可以减少分析时间提高构建效率。
1.2.3.2 Dependencies
对于scons,除了在Builder里面提到的implicit dependencies这种隐式依赖,其他的依赖都必须显示说明的,或者是从指定的文件中进行分析。
1.2.3.2.1 分析依赖
关于依赖共有两种类型,一种是普通的依赖就是A->B,另外一种是顺序依赖。顺序依赖的意思是说,其实A->B并不是真正构建A依赖于构建B。 只不过我们希望在构建顺序上,B在A构建之前。如果B发生变化,A不一定发生构建。这两种依赖分别对应于scons里面的Depends和Requires函数, 当然这个在Makefile里面也有方法表现出来。scons还提供一个函数ParseDepends可以分析g++ -MM -MG产生的.d文件,然后来判断依赖。
1.2.3.2.2 判断依赖变化
判断依赖变化上,scons允许自己定义函数来判断文件是否发生变化。内置判断函数有下面几种:
· Decider('make') //依赖对象修改时间是否比target修改时间大
· Decider('timestamp-match') //依赖对象修改时间相对上次来说没有改变
· Decider('MD5') //依赖对象内容的md5没有发生改变
· 或者是以上这些判断方式的组合
同样依赖变化还包括构建方式的参数变化,这点是值得学习的。实现起来也不是很难,可以在数据库中记录前一个构建某个target的输入文件以及构建参数,如果构建参数发生变化的话,那么重新构建并且在数据库中进行记录。
1.2.3.3 Environment
对于scons来说存在这么几个环境:
· External Environment //外部环境,可以通过import os获得
· Construction Environment //创建环境,我们可以在SConstruct里面填写
· Execution Environment //执行环境,scons在执行执行时所拥有的
和make有点不同的是,scons有意不将External Environment导入到Construction Environment里面来。 理由简单也非常合理,那就是如果一旦将环境变量也纳入scons的逻辑的话,那么构建过程是不可重复的。 可就是说,在某个人机器上搞得定但是另外一个机器上搞不定仅仅是因为环境变量不同。
默认情况下面scons有一个默认的环境DefaultEnvironment()可以获得。然后我们可以在一个环境下面定义Builder等内容。 同时scons针对环境这个对象有相当多的操作,比如Clone,Repliace,Merge等。
1.2.3.4 Hierarchical Builds
层级目录之间的构建,在一开始也说过了,是通过导入其他目录下面的SConscript这样的文件来完成的, 最终也是在top-level directory上面发生构建。和gyp一样,SConscript也需要处理这样的问题, 就是定义哪些元素是file和directory,然后在解释的时候必须加上目录前缀。
此外,相对于gyp,scons还引入了变量的export和import机制。某个SConscript可以export一部分变量出来, 然后另一个SConscript文件可以import这个变量进去。实现方式也非常简单,就是scons假设存在一个全局的变量池(variable pool),组织形式是一个字典。然后export就是写入这个字典,而import就是从这个字典导入内容。 跨文件之间的变量共享是非常必要的,这个在comake2里面共享编译依赖(已经做到),并且还有很强烈的需求就是共享编译参数(这点没有做到).
1.2.4 其他问题
scons提供了install方法,允许把某个文件安装到某个目录下面去,以相同或者是不同的名字安装。
scons提供了平台无关的文件操作函数,比如copy,delete,rename,mkdir,touch等。这些函数做成为一个延迟函数对象.有一个Execute函数可以接受这些对象立即执行。其实关于这文件操作函数,个人认为可能完全没有必要,平台无关是非常好的概念, 但是对于文件系统实在是难于抽象出来。其他不说,首先考虑文件系统的名称就够费神半天,个人觉得把大部分细节屏蔽掉就ok。 对于需要平台相关操作,那就平台相关命令吧,scons对于命令操作也包装成了一个对象,这样可以延迟执行。
scons支持out-of-source的编译方式,实现方式是通过copy源代码到另外一个地方然后进行编译,关于这么做的原因在下面地址也说了。 个人感觉,可能out-of-source的编译也不一定需要使用这种方式来完成(Android Build System就是out-of-source编译的,但是没有copy source).http://www.scons.org/doc/2.1.0.alpha.20101125/HTML/scons-user/c3348.html
scons支持configure这样的功能,包括:
· 检查头文件是否存在
· 检查库是否存在
· 检查typedef
· 检查函数是否存在
当然这只是配置中一部分。阅读了MongnoDB的SConstruct这个文件,发现其实还有相当多的检测是需要自己来完成的, 同时可能不希望按照scons内置的configure规则来进行检查。对于configure这样一个东西是非常需要,但是相当难做。GNU Autoconf个人感觉来说相对更加成熟一些,如果一定要做configure这样功能的话,可以首先参考一下GNU Autoconf.
scons还支持cache编译产出物这样的功能,不过个人还是觉得没有必要这么做,因为这样在scons内部实现了一个cache系统, 不仅仅加重了工作量,而且并没有做好,因为相对ccache来说,scons提供过于简单了。至于cache效率的话,是另外一个问题。 关于cache系统确实完全独立实现并且通用。
1.2.5 总结
scons是一个文档相对来说比较完整,已经被一些大型项目所使用的,比较成熟和完善的产品。
scons本身使用Python Script来做描述文件,相对于gyp来说可读性会更好,表达功能上相对于gyp也更加强大, 但是容易造成一个问题就是速度会过慢,尤其对于scons这种执行方式。因为scons每次都是读取SConstruct/SConscript文件, 而不像gyp一样一次就生成Makefile,大部分时候是不需要变动Makefile,只是需要make的。
scons对于编译依赖来说,提供了隐式分析和显示说明两种方式来描述依赖,然后针对隐式分析编译依赖也做了一些有效的优化。comake2在这点上也是提供了隐式分析和显示分析,但是却没有做一些优化,这样就造成每次生成Makefile时间过长。 如果是大型项目,耗时会非常长,在这些情况下面显示编写依赖可能会更好。对于依赖变化检测,也是提供了很多种方法。 并且如果构建参数发生变化,也是认为依赖发生变化,重新构建的。
scons对于构建系统构建方式,和gyp一样认为构建必须从top-level开始进行。 和gyp一样,允许每个子目录下面存放SConscript描述文件,然后在上层最终收集上来。
scons提供了环境这个概念,这点对于构建不同参数的版本作用非常大。并且scons对于环境这个概念引入也非常自然, 直接env.Program(..)和没有使用环境的Program(..)在编写内容上相差无几,同时也提供了很多关于环境的操作,并且提供一个默认环境。
和其他构建系统一样,sco
展开阅读全文