SCons依赖
前面已经知道,SCons如何第一次去编译源文件,当我们需要重新编译源文件时,SCons需要知道哪些文件需要重新编译,哪些文件不需要编译。而不是每次编译都重新编译所有文件,这样会浪费掉很多时间。实际上这些就是文件依赖规则来决定的。
依赖规则方法 (Decider Function),SCons内置了几种Decider Function,我们还可以定义自己的 Decider Function。下面看看几种Decider 方法
1.Decider方法做出决策当输入的文件发生变化
我们不希望去重新编译那些不需要编译的文件,SCons默认使用内容签名的方式判断文件是否改变需要重新编译,当然我们也可以替换成时间戳方式来判断文件是否需要重新编译,甚至可以写自己的判断方法。
1.1使用文件内容签名决策方法(content signatures)
这种方式是默认方式,SCons使用哈希加密来判断文件是否改变。
如果你想显示的申明使用内容签名方式来判断文件是否改变,可以按照下面方式操作。
Program('hello.c')
Decider('content')
1.2使用时间戳决策方法(time stamps)
这种方式是判断源文件的修改时间比目标文件更新来判断源文件是否需要重新编译,使用下面方式:
Object('hello.c')
Decider('timestamp-newer') or Decider('make')
使用这种方式的一个缺点是,当我们从源码备份硬盘中复制一些老的源代码时,因为这些源代码时间都比我们目标文件的时间老,所以不会被编译。
不过这种缺点是可以避免的,我们只需要使用Decider('timestamp-match') 代替 Decider('timestamp-newer')即可。
这种方式SCons会在编译的时候准确的记录源文的时间信息。
1.3使用签名和时间戳决策方法(MD Signatures and Time Stamps)
这种方式是增强型的,SCons只有在源文件时间戳改变时才会读取内容,方式如下
Program('hello.c')
Decider('content-timestamp')
这种方式中的内容签名规则仍然遵守前面说过的Decider('content')规则。
这种方式的缺点是:修改源文件的时间不能再1秒内,也就是说,如果你在1秒内修改源文件并且执行编译,那么SCons将不会重新编译源文件。
工程师不可能会做到,但是自动构建工具有可能会在1秒内做到。
1.4自定义决策方法
这种方式是我们使用python编写自己的决策方法,这里不过多叙述,看下模板样式:
Program('hello.c')
def decide_if_changed(dependency, target, prev_ni, repo_node=None):
if dependency.get_timestamp() != prev_ni.timestamp:
dep = str(dependency)
tgt = str(target)
if specific_part_of_file_has_changed(dep, tgt):
return True
return False
Decider(decide_if_changed)
1.5混合决策方法的方式
假如我们编译多个不同目标文件,想指定不同的决策方法,SCons也是支持的。(虽然这种情况我们很少使用)
将使用env.Decider方式,我们需要创建不同的环境env,如下:
# 环境env1
env1 = Environment(CPPPATH = ['.'])
# 从env1克隆 出env2
env2 = env1.Clone()
# env2 设置决策方式
# env1 没有设置,使用的就是默认 Decider('content')
env2.Decider('timestamp-match')
# 在env1环境下 编译 program1.c 生成 prog-content 程序
env1.Program('prog-content' , 'program1.c')
#在env2环境下 编译 program2.c生成 prog-timestamp 程序
env2.Program('prog-timestamp' , 'program2.c')
2.隐式依赖:$CPPPATH构造变量
隐式依赖就是不需要我们指定依赖关系,SCons能够自己识别的依赖关系。
比如我们的例子hello.c 一般还会包含一个hello.h文件,hello.c如下:
#include <hello.h>
int
main()
{
printf("Hello, %s!\n", string);
}
hello.h
#define string "world
如果hello.h的内容改变,那么我们希望SCons能够重新编译hello.c,语法如下:
# CPPPATH 环境变量指定源文件和头文件路径
Program('hello.c', CPPPATH='.')
CPPPATH和LIBPATH 一样都支持列表类型,如下:
Program('hello.c', CPPPATH = ['include', '/home/project/inc'])
3.缓存隐式依赖
SCons编译时会自动扫描源文件中包含的#include 行,以便于他能知道隐式依赖的文件是否改变。
在构建一个大型项目时,SCons扫描文件所花费的时间占总的构造项目的时间是很少的比例。假如如果你还觉的自动扫描会浪费不必要的时间,那么SCons可以使用缓存隐式依赖,语法如下:
SetOption('implicit_cache' ,1)
# 或在命令行窗口执行
scons -Q --implicit-cache hello
注意:如果开启了缓存依赖,那么SCons每次从缓存中查找依赖,即使源文件有改变,SCons也不会重新编译。
其实这样做的意义,对于我们编译一些比较稳定的、不需要修改的源代码意义很大。比如我们引用了第三方的库,只要重新扫描一次缓存隐式的依赖。
3.1 --implicit-deps-changed 选项
那么如上面的参数设置,假如我们缓存的隐式依赖的源文件有变化,如何更新缓存呢?语法如下:
scons -Q --implicit-deps-changed hello
3.2 --implicit-deps-unchanged 选项
scons -Q --implicit-deps-unchanged 命令行告诉SCons强制使用缓存的隐式依赖。
4.显示依赖:Depends 方法
当一个文件依赖另一个文件,但是SCons无法检测这种依赖,那么我们可以显式的指定依赖关系,如下:
hello = Program('hello.c')
Depends(hello, 'other_file')
5.依赖外部文件:ParseDepends 方法
有时候SCons扫描器扫描隐式依赖失败,比如下面代码:
#define FOO_HEADER <foo.h>
#include FOO_HEADER
int main() {
return FOO;
}
扫描枪将无法扫描到foo.h头文件,这时需要:
ParseDepends()方法
obj = Object('hello.c', CCFLAGS='-MD -MF hello.d', CPPPATH='.')
SideEffect('hello.d', obj)
ParseDepends('hello.d')
Program('hello', obj)
这种方式,只有在扫描器扫描不出依赖关系时才推荐使用。
6.忽略依赖: Ignore 方法
有时我们希望忽略依赖,即使依赖的文件有改变,也不希望SCons重新编译,语法如下:
hello_obj=Object('hello.c')
hello = Program(hello_obj)
Ignore(hello_obj, 'hello.h')
7.顺序依赖(Order-Only): Requires 方法
有时我们的源文件a.c包含了另外一个源文件b.c,当b.c改变时我们不希望a.c被重新编译。
 比如hello.c 包含version.c,version.c的内容每次在编译的时候都会改变,比如我们使用python代码生成version.c的内容,
内容包括版本号,编译日期,等等信息。
hello.c #include "version.h"文件,hello.c只要访问version.c里面的数组地址就可以获取版本,日期等信息。而不必关心具体内容。
但是根据隐式依赖关系,每次verison.c的内容改变时,hello.c都会被重新编译。
看下编译文件:
import time
version_c_text = """
char *date = "%s";
""" % time.ctime(time.time())
open('version.c', 'w').write(version_c_text)
hello = Program(['hello.c', 'version.c'])
改进后的编译文件:
import time
version_c_text = """
char *date = "%s";
""" % time.ctime(time.time())
open('version.c', 'w').write(version_c_text)
version_obj = Object('version.c')
# 将version.c的目标文件和hello目标文件链接在一起
# 这样 hello.c就不需要每次重复编译了
hello = Program('hello.c',
LINKFLAGS = str(version_obj[0]))
# 指定 hello文件需要version_obj文件
Requires(hello, version_obj)
8.AlwayBuild 方法
这个方法意思并不是字面意思,他是在需要编译的时候总是编译。语法如下:
hello = Program('hello.c')
AlwaysBuild(hello)