一个环境就是能够影响一个程序如何执行的值的集合。SCons里面有三种不同类型的环境:
External Environment(外部环境):
外部环境指的是在用户运行SCons的时候,用户环境中的变量的集合。这些变量在SConscript文件中通过Python的os.environ字典可以获得。
Construction Environment(构造环境):
一个构造环境是在一个SConscript文件中创建的一个唯一的对象,这个对象包含了一些值可以影响SCons编译一个目标的时候做什么动作,以及决定从那一个源中编译出目标文件。SCons一个强大的功能就是可以创建多个构造环境,包括从一个存在的构造环境中克隆一个新的自定义的构造环境。
Execution Environment(执行环境):
一个执行环境是SCons在执行一个外部命令编译一个或多个目标文件时设置的一些值。这和外部环境是不同的。
与Make不同,SCons不会自动在不同的环境之间拷贝或导入值。这是一个刻意的设计选择,保证了不管用户外部环境的值是怎么样的,编译总是可以重复的。这会避免编译中的一些问题,比如因为一个自定义的设置使得使用了一个不同的编译器或编译选项,开发者的本地代码编译成功,但是checked-in后编译不成功,因为使用了不同的环境变量设置。
7.1、使用来自外部环境的值
当执行SCons的时候,外部环境的值通过os.environ字典获得。这就以为着在任何一个你想使用外部环境的SConscript文件需要增加一个import os语句:
import os
7.2、构造环境
在一个大型复杂的系统中,所有的软件都按照同样的方式编译是比较少见的。例如,不同的源文件可能需要不同的编译选项,或者不同的可执行程序需要链接不同的库。SCons允许你创建和配置多个构造环境来控制和满足不同的编译需求。
7.2.1、创建一个构造环境:Environment函数
一个构造环境由Environment方法创建:
env=Environment()
默认情况下,SCons基于你系统中工具的一个变量集合来初始化每一个新的构造环境。
当你初始化一个构造环境时,你可以设置环境的构造变量来控制一个是如何编译的。例如:
import os
env=Environment(CC='gcc', CCFLAGS='-O2')
env.Program('foo.c')
7.2.2、从一个构造环境中获取值
你可以使用访问Python字典的方法获取单个的构造变量:
env=Environment()
print "CC is:", env['CC']
一个构造环境实际上是一个拥有方法的对象。如果你想直接访问构造变量的字典,你可以使用Dictionary方法:
env=Environment(FOO='foo', BAR='bar')
dict=env.Dictionary()
for key in ['OBJSUFFIX', 'LIBSUFFIX', 'PROGSUFFIX']:
print "key=%s, value=%s" % (key,dict[key])
如果你想循环并打印出构造环境中的所有变量:
env=Environment()
for item in sorted(env.Dictionary().items()):
print "construction variable = '%s', value = '%s'" % item
7.2.3、从一个构造环境中扩展值:subst方法
另一种从构造环境中获取信息的方式是使用subst方法。例如:
env=Environment()
print "CC is:", env.subst('$CC')
使用subst展开字符串的优势是结果中的构造变量会重新展开直到不能扩展为止。比如获取$CCCOM:
env=Environment(CCFLAGS='-DFOO')
print "CCCOM is:", env['CCCOM']
将会打印出没有扩展开的$CCCOM:
% scons -Q
CCCOM is: $CC $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES
scons: '.' is up to date.
调用subst方法来获取$CCCOM:
env=Environment(CCFLAGS='-DFOO')
print "CCCOM is:", env.subst('$CCCOM')
将会递归地扩展所有的构造变量:
% scons -Q
CCCOM is: gcc -DFOO -c -o
scons: '.' is up to date.
7.2.4、处理值扩展的问题
如果扩展一个构造变量的时候,发生了问题,默认情况下会扩展成''空字符串,不会引起scons失败。
env=Environment()
print "value is:", env.subst('->$MISSING<-')
% scons -Q
value is: -><-
scons: '.' is up to date.
使用AllowSubstException函数会改变默认的行为。当值扩展的时候发生了异常,AllowSubstExceptions控制了哪一个异常是致命的,哪一个允许安全地发生。默认情况下,NameError和IndexError这两个异常允许发生。如果需要所有的构造变量名字存在,调用AllowSubstExceptions:
AllowSubstExceptions()
env=Environment()
print "value is:", env.subst('->$MISSING<-')
% scons -Q
value is:
scons: *** NameError `MISSING' trying to evaluate `$MISSING'
File "/home/my/project/SConstruct", line 3, in <module>
也可以用来允许其他的异常发生,使用${...}构造变量语法。例如,下面的代码允许除零发生:
AllowSubstExceptions(IndexError, NameError, ZeroDivisionError)
env = Environment()
print "value is:", env.subst( '->${1 / 0}<-' )
% scons -Q
value is: -><-
scons: `.' is up to date.
7.2.5、控制默认的构造环境:DefaultEnvironment函数
我们已经介绍过的所有的Builder,比如Program和Library,实际上使用一个默认的构造环境。
你可以控制默认构造环境的设置,使用DefaultEnvironment函数:
DefaultEnvironment(CC='/usr/local/bin/gcc')
这样配置以后,所有Program或者Object的调用都将使用/usr/local/bin/gcc编译目标文件。
注意到DefaultEnvironment返回初始化了的默认构造环境对象,这个对象可以像其他构造环境一样被操作。所以如下的代码和上面的例子是等价的:
env=DefaultEnvironment()
env['CC']='/usr/local/bin/gcc'
DefaultEnvironment函数常用的一点就是用来加速SCons的初始化。为了使得大多数默认的配置能够工作,SCons将会搜索本地系统已经安装的编译器和其他工具。这个搜索过程会花费时间。如果你知道哪一个编译器或工具你想配置,你可以控制SCons执行的搜索过程通过指定一些特定的工具模块来初始化默认的构造环境:
env=DefaultEnvironment(tools=['gcc','gnulink'], CC='/usr/local/bin/gcc')
上面的例子告诉SCons显示配置默认的环境使用GNU编译器和GNU链接器设置,使用/usr/local/bin/gcc编译器。
7.2.6、多个构造环境
构造环境的真正优势是你可以创建你所需要的许多不同的构造环境,每一个构造环境对应了一种不同的方式去编译软件的一部分或其他文件。比如,如果我们需要用-O2编译一个程序,编译另一个用-g,我们可以如下做:
opt=Environment(CCFLAGS='-O2')
dbg=Environment(CCFLAGS='-g')
opt.Program('foo','foo.c')
dbg.Program('bar','bar.c')
% scons -Q
cc -o bar.o -c -g bar.c
cc -o bar bar.o
cc -o foo.o -c -O2 foo.c
cc -o foo foo.o
我们甚至可以使用多个构造环境去编译一个程序的多个版本:
opt=Environment(CCFLAGS='-O2')
dbg=Environment(CCFLAGS='-g')
opt.Program('foo','foo.c')
dbg.Program('foo','foo.c')
这个时候SCons会发生错误:
% scons -Q
scons: *** Two environments with different actions were specified for the same target: foo.o
File "/home/my/project/SConstruct", line 6, in <module>
这是因为这两个Program调用都隐式地告诉SCons产生一个叫做foo.o的目标文件。SCons无法决定它们的优先级,所以报错了。为了解决这个问题,我们应该显示指定每个环境将foo.c编译成不同名字的目标文件:
opt=Environment(CCFLAGS='-O2')
dbg=Environment(CCFLAGS='-g')
o=opt.Object('foo-opt','foo.c')
opt.Program(o)
d=dbg.Object('foo-dbg','foo.c')
dbg.Program(d)
7.2.7、拷贝构造环境:Clone方法
有时候你想多于一个构造环境对于一个或多个变量共享相同的值。当你创建每一个构造环境的时候,不是重复设置所有共用的变量,你可以使用Clone方法创建一个构造环境的拷贝。
Environment调用创建一个构造环境,Clone方法通过构造变量赋值,重载拷贝构造环境的值。例如,假设我们想使用gcc创建一个程序的三个版本,一个优化版,一个调试版,一个其他版本。我们可以创建一个基础构造环境设置$CC为gcc,然后创建两个拷贝:
env=Environment(CC='gcc')
opt=env.Clone(CCFLAGS='-O2')
dbg=env.Clone(CCFLAGS='-g')
env.Program('foo','foo.c')
o=opt.Object('foo-opt','foo.c')
opt.Program(o)
d=dbg.Object('foo-dbg','foo.c')
dbg.Program(d)
7.2.8、替换值:Replace方法
你可以使用Replace方法替换已经存在的构造变量:
env=Environment(CCFLAGS='-DDEFINE1');
env.Replace(CCFLAGS='-DDEFINE2');
env.Program('foo.c')
你可以安全地针对那些不存在的构造变量调用Replace方法:
env=Environment()
env.Replace(NEW_VARIABLE='xyzzy')
print "NEW_VARIABLE = ", env['NEW_VARIABLE']
在这个例子中,构造变量被添加到构造环境中去了:
%scons -Q
NEW_VARIABLE = xyzzy
scons: '.' is up to date.
变量不会被扩展知道构造环境真正被用来编译目标文件的时候,同时SCons函数和方法的调用是没有顺序的,最后的替换可能被用来编译所有的目标文件,而不管Replace方法的调用是在编译方法的前后:
env=Environment(CCFLAGS='-DDEFINE1')
print "CCFLAGS = ", env['CCFLAGS']
env.Program("foo.c")
env.Replace(CCFLAGS='-DDEFIN2')
print "CCFLAGS = ", env['CCFLAGS']
env.Program("bar.c")
% scons
scons: Reading SConscript files ...
CCFLAGS = -DDEFINE1
CCFLAGS = -DDEFINE2
scons: done reading SConscript files.
scons: Building targets ...
cc -o bar.o -c -DDEFINE2 bar.c
cc -o bar bar.o
cc -o foo.o -c -DDEFINE2 foo.c
cc -o foo foo.o
scons: done building targets.
因为替换发生在读取SConscript文件的时候,foo.o编译的时候$CCFLAGS变量已经被设置为-DDEFINE2,即使Relapce方法是在SConscript文件后面被调用的。
7.2.9、在没有定义的时候设置值:SetDefault方法
有时候一个构造变量应该被设置为一个值仅仅在构造环境没有定义这个变量的情况下。你可以使用SetDefault方法,这有点类似于Python字典的set_default方法:
env.SetDefault(SPECIAL_FLAG='-extra-option')
当你编写你自己的Tool模块将变量应用到构造环境中的时候非常有用。
7.2.10、追加到值的末尾:Append方法
你可以追加一个值到一个已经存在的构造变量,使用Append方法:
env=Environment(CCFLAGS=['-DMY_VALUE'])
env.Append(CCFLAGS=['-DLAST'])
env.Program('foo.c')
Scons编译目标文件的时候会应用-DMY_VALUE和-DLAST两个标志:
% scons -Q
cc -o foo.o -c -DMY_VALUE -DLAST foo.c
cc -o foo foo.o
如果构造变量不存在,Append方法将会创建它。
7.2.11、追加唯一的值:AppendUnique方法
有时候仅仅只有在已经存在的构造变量没有包含某个值的时候,才会增加这个新值。可以使用AppendUnique方法:
env.AppendUnique(CCFLAGS=['-g'])
上面的例子,仅仅只有在$CCFLAGS没有包含-g值得时候才会增加-g。
7.2.12、在值的开始位置追加值:Prepend方法
对于一个存在的构造变量,你可以使用Prepend方法追加一个值到它的值的开始位置。
env=Environment(CCFLAGS=['-DMY_VALUE'])
env.Prepend(CCFLAGS=['-DFIRST'])
env.Program('foo.c')
% scons -Q
cc -o foo.o -c -DFIRST -DMY_VALUE foo.c
cc -o foo foo.o
如果构造变量不存在,Prepend方法会创建它。
7.2.13、在前面追加唯一值:PrependUnique方法
仅仅在一个构造变量存在的值中没有包含将要增加的值的时候,这个值才被追加到前面,可以使用PrependUnique方法;
env.PrependUnique(CCFLAGS=['-g'])
7.3、控制命令的执行环境
当SCons编译一个目标文件的时候,它不会使用你用来执行SCons的同样的外部环境来执行一些命令。它会使用$ENV构造变量作为外部环境来执行命令。
这个行为最重要的体现就是PATH环境变量,它决定了操作系统将去哪里查找命令和工具,与你调用SCons使用的外部环境的不一样。这就意味着SCons将不能找到你在命令行里执行的所有工具。
PATH环境变量的默认值是/usr/local/bin:/bin:/usr/bin。如果你想执行任何命令不在这些默认地方,你需要在你的构造环境中的$ENV字典中设置PATH。
最简单的方式就是当你创建构造环境的时候初始化这些值:
path=['/usr/local/bin', '/usr/bin']
env=Environment(ENV={'PATH':PATH})
以这种方式将一个字典赋值给$ENV构造变量完全重置了外部环境,所以当外部命令执行的时候,设置的变量仅仅是PATH的值。如果你想使用$ENV中其余的值,仅仅只是设置PATH的值,你可以这样做:
env['ENV']['PATH']=['/usr/local/bin','/bin','/usr/bin']
注意SCons允许你用一个字符串定义PATH中的目录,路径用路径分隔符分隔:
env['ENV']['PATH']='/usr/local/bin:/bin:/usr/bin'
7.3.1、从外部环境获得PATH值
你可能想获得外部的PATH来作为命令的执行环境。你可以使用来自os.environ的PATH值来初始化PATH变量:
import os
env=Environment(ENV={'PATH':os.environ['PATH']})
你设置可以设置整个的外部环境:
import os
env=Environment(ENV=os.environ)
7.3.2、在执行环境里增加PATH的值
常见的一个需求就是增加一个或多个自定义的目录到PATH变量中。
env=Environment(ENV=os.environ)
env.PrependENVPath('PATH','/usr/local/bin')
env.AppendENVPath('LIB','/usr/local/lib')