获取 scons 以在重建时区分空源和不存在的源

Get scons to distinguish an empty and a non-existing source while rebuilding

在构建我的程序时,区分不存在的文件和空文件很重要。 但是,scons 似乎对它们一视同仁,并在源文件从其中一种状态更改为另一种状态时忽略了重建目标。


分步示例:

第 0 步:

S构造

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 的命令是 运行。它检查 foo 是否存在并且 returns 是否存在错误。构建失败(到目前为止一切如预期)。

第 1 步:

S构造:

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因为改了所以重建。这次它创建了一个空文件。 bar 正在重建,因为它之前失败了。这次成功了。构建成功(仍然符合预期)。

第 2 步:

S构造

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 被重建,因为它又发生了变化(恢复到以前的版本)。文件 foo 不再存在,因为 scons 在构建文件之前删除文件并且命令无法重新创建它。 bar 未重建,因为 scons 似乎未检测到源文件中的更改。 构建成功但不应该成功!


如何在最后一步中强制 scons 重建 bar

该解决方案应该可以很好地扩展到生成许多文件的“foo”命令,这些文件的列表是在 SConscript 中以编程方式生成的,不能硬编码。

如果你说“我怎样才能强制 SCons 做 xyz?”,那么你对 SCons 的理解是不完整的。

SCons 只会构建过时的目标。

除非..

你用AlwaysBuild(target)见:https://scons.org/doc/production/HTML/scons-man.html#f-AlwaysBuild

似乎您也不希望 foo 在(重新)构建之前被删除?

那么你应该使用Precious(target)见:https://scons.org/doc/production/HTML/scons-man.html#f-Precious

此外..用空源调用构建器是一种不好的形式。

SCons 如何知道它是否已过时?

对于您的示例,是什么导致(重新)构建 foo

SCons 确定文件已挂起的默认方式是比较文件的先前版本和当前版本的 MD5 签名。 一个不存在的文件的签名是从 0 字节的数据计算出来的,就像一个空文件一样,所以 SCons 看不出它们之间的区别。这通常是可以的,尤其是不创建目标文件并不是对 SCons 的完全合法使用。 但是,我们可以通过提供决定文件是否不同的不同函数来使其工作。

SCons 中的此类函数称为Decider。 其中三个是开箱即用的。默认使用 MD5。第二个使用时间戳。第三个使用 MD5,但前提是时间戳不同。

在这种情况下,时间戳可能会起作用,因为对于不存在的文件,时间戳为 0。但是,当时间戳发生变化而文件内容没有变化时,它会产生误报。

相反,我们可以提供我们自己的决策程序,它会完全按照我们的要求执行:

from os import path

env = DefaultEnvironment()
decider_env = env.Clone()

def decide_if_changed(dependency, target, prev_ni, repo_node=None):
    csig = dependency.get_csig() # it has to be called every time or the value won't be in `prev_ni` for the next check
    return not path.isfile(dependency.abspath) or not hasattr(prev_ni, 'csig') or prev_ni.csig != csig
decider_env.Decider(decide_if_changed)

foo = env.Command('foo', [], 'echo $TARGET is not created here!')
bar = decider_env.Command('bar', foo, 'touch $TARGET ; test -f $SOURCE')
Default(bar)

这个自定义决策器类似于基于 MD5 的默认实现,除了它还会在文件不存在时报告更改。 这应该涵盖问题中描述的问题。

新的决策者被分配给默认环境的克隆。这样我们就可以控制哪个目标使用它。在这种情况下,只有 bar 使用非默认决策器。

顺便说一句,scons 现在可以区分 emtpy 和不存在,这是最近的更改(提交 3b7f8b4ce0,github。com/SCons/scons/pull/3833)。