如何让scons在重构建时区分空源文件与不存在的源文件
SCons 源文件不存在/空状态切换不触发重构问题
构建程序时区分不存在的文件和空文件十分重要,但SCons默认会将二者视为相同状态,当源文件在这两种状态间切换时,不会触发对应目标的重构建,以下为问题复现步骤:
分步示例
步骤0
SConstruct代码:
foo = Command('foo', [], 'echo $TARGET is not created here!') bar = Command('bar', foo, 'touch $TARGET ; test -f $SOURCE') Default(bar)
运行结果:
scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... echo foo is not created here! foo is not created here! touch bar ; test -f foo scons: *** [bar] Error 1 scons: building terminated because of errors.
理解:foo的构建命令未生成对应文件也未抛出错误,因此继续执行bar的构建命令,bar命令检测foo是否存在时报错,构建失败,符合预期。
步骤1
SConstruct代码:
foo = Command('foo', [], 'touch $TARGET') bar = Command('bar', foo, 'touch $TARGET ; test -f $SOURCE') Default(bar)
运行结果:
scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... touch foo touch bar ; test -f foo scons: done building targets.
理解:foo的构建规则变更后触发重构建,本次生成了空的foo文件;bar因上一次构建失败也触发重构建,执行成功,整体构建成功,符合预期。
步骤2
SConstruct代码改回步骤0版本:
foo = Command('foo', [], 'echo $TARGET is not created here!') bar = Command('bar', foo, 'touch $TARGET ; test -f $SOURCE') Default(bar)
运行结果:
scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... echo foo is not created here! foo is not created here! scons: `bar' is up to date. scons: done building targets.
理解:foo的构建规则再次变更触发重构建,scons在构建前会删除原有foo文件,而新命令未重新生成foo,因此foo处于不存在状态;但scons未检测到源文件状态变更,未触发bar的重构建,最终构建提示成功,不符合预期。
解决方案
核心思路是自定义SCons的内容签名生成逻辑,把文件是否存在的状态纳入签名计算,空文件和不存在的文件就会生成不同的签名,SCons检测到签名变化就会自动触发依赖目标的重构建。
在SConstruct开头添加如下代码即可,原有构建逻辑不需要任何修改,动态生成的多输出目标也会自动适配:
def exists_aware_decider(dependency, target, prev_ni): # 先判断文件当前是否存在 cur_exists = dependency.exists() # 读取上一次构建存储的存在状态 prev_exists = prev_ni.ninfo.get('exists', None) # 存在状态发生变化直接判定为内容变更 if prev_exists is not None and cur_exists != prev_exists: return True # 状态一致的场景下:存在的文件走默认MD5内容比对,不存在的文件直接判定为无变更 if cur_exists: import SCons.Sig cur_sig = SCons.Sig.MD5.sign(dependency.get_contents()) prev_sig = prev_ni.ninfo.get('csig', None) return cur_sig != prev_sig else: return False # 构建完成后存储依赖的存在状态和内容签名 def exists_aware_ninfo_hook(target, source, env): for item in target.children() + source: if item.is_derived(): ninfo = item.get_ninfo() ninfo.exists = item.exists() if item.exists(): import SCons.Sig ninfo.csig = SCons.Sig.MD5.sign(item.get_contents()) # 全局应用自定义判定规则 Decider(exists_aware_decider) AddPostAction('*', exists_aware_ninfo_hook)
多输出场景适配说明
如果foo命令会动态生成多个输出文件,不需要额外硬编码输出列表,上述逻辑会自动遍历所有依赖的派生文件,逐个检查存在状态和内容签名,只要其中任意一个文件在空和不存在之间切换,都会触发所有依赖该文件的目标重构建。
内容的提问来源于stack exchange,提问作者Piotr Siupa
相关产品推荐
相关产品推荐

