如果在源保持不变的情况下从外部更改了目标,则重建目标

Rebuild target if it was externally changed while the source remained the same

我有一个 Flex 源文件和将其转换为 C++ 的结果都存储在版本控制中。 (很常见的做法。这允许在没有安装 Flex 的机器上编译程序。)

在某些情况下,版本控制操作可能会降级目标文件,而源仍然是最新版本。 (这是有意的。)

在这种情况下,我希望 SCons 再次构建目标,以便它是最新的。 但是,它没有检测到目标文件已过时。它似乎只检查源文件是否已更改。 我能否让 SCons 在决定是否需要重建时也检查目标文件中的更改?


您可以测试该行为,使用这一行 SConstruct:

Command('b', 'a', 'cp $SOURCE $TARGET')

如果您有这个 SConstruct 文件并且您 运行 以下命令:

echo foo >a
scons -Q b
echo bar >b
scons -Q b

你得到这个结果:

+ echo foo >a
+ scons -Q b
cp a b
+ echo bar >b
+ scons -Q b
scons: `b' is up to date.

如文档中所述 (https://scons.org/doc/production/HTML/scons-user.html#idp140211724034880),SCons 将根据输入文件做出决定。只有删除目标才会触发重建,因为目标将不再存在。

对于您的情况,您需要的是一个 Decider,这记录在:

https://scons.org/doc/production/HTML/scons-user.html#idp140211709501040

我写了一个带有决策器的小例子,它总是决定必须重建目标:

def my_decider(dependency, target, prev_ni, repo_node=None):
    print("Executing my decider...")
    print("dep: %s " % dependency)
    print("target: %s" % target)
    print("prev_ni: %s" % prev_ni)
    print("repo node: %s" % repo_node)
    return True

Decider(my_decider)
Command('b', 'a', action=[
        Copy("$TARGET", "$SOURCE"),
    ]
)

目标的校验和应该在 SCons 数据库中的某处,要检索它,您可以检查 api:

https://scons.org/doc/latest/PDF/scons-api.pdf

如果不是 get_timestamp() 函数应该 return 目标的时间戳。您可以每次都将其保存在另一个文件中,然后在您的决策程序中进行比较。

编辑

下面是一个可能的实现解决方案(是的,它带有一个您可以使用版本控制系统忽略的文件)。这将从第三次构建开始工作,因为决策者被不同的目标调用了两次......此外,文件的决策者被故意遗漏,代码已经在依赖部分的手册中。我现在不明白 SCons 的行为,稍后会尝试找到解决方案,因为它需要太多时间:)

import os

already_calculated = False

def get_name(target):
    return "." + str(target)

def store_hash(target):
    with open(get_name(target), "w+") as f:
        f.write(target.get_content_hash())
    f.close()

def get_hash(target):
    if not os.path.exists(get_name(target)):
        return None
    with open(get_name(target), "r") as f:
        return f.readline()

def my_decider(dependency, target, prev_ni, repo_node=None):
    global already_calculated
    if already_calculated:
        return
    already_calculated = True 
    if get_hash(target) == None:
        store_hash(target)
        return True
    old_hash = get_hash(target)
    new_hash = target.get_content_hash()
    if old_hash != new_hash:
        store_hash(target)
        return True
    return False

Decider(my_decider)
Command('b', 'a', action=[
        Copy("$TARGET", "$SOURCE"),
    ]
)

正如 demorgan 在他们的回答中所建议的那样,解决方案是编写一个新的 decider,在计算结果时将目标考虑在内。

但是,没有必要编写存储先前哈希值的自定义代码。我们可以使用用于在 SCons 数据库中存储依赖项的先前哈希值的相同机制。 如果我们使用函数 my_file.get_csig(),它不仅会 return 这个文件的当前哈希值,还会存储它,因此下次可以通过 my_file.get_stored_info().csig 访问它。

(记住每次调用 get_csig() 即使你这次不需要它的值,以确保存储的值没有过时。还要记住对象 return 由 get_stored_info() 并不总是有字段 csig,就像对象 prev_ni 一样,因此您需要在这两种情况下检查该字段。)

这是一个自定义决策程序的示例,它检查依赖项和目标的哈希值,并在其中任何一个发生更改时导致重建:

def source_and_target_decider(dependency, target, prev_ni, repo_node=None):
    src_old_csig = prev_ni.csig if hasattr(prev_ni, 'csig') else None
    src_new_csig = dependency.get_csig()
    print(f'"{dependency}": {src_old_csig} -> {src_new_csig}')
    
    tgt_stored_info = target.get_stored_info()
    tgt_old_csig = tgt_stored_info.ninfo.csig if hasattr(tgt_stored_info.ninfo, 'csig') else None
    tgt_new_csig = target.get_csig()
    print(f'"{target}": {tgt_old_csig} -> {tgt_new_csig}')
    
    return src_new_csig != src_old_csig or tgt_new_csig != tgt_old_csig

Decider(source_and_target_decider)
Command('b', 'a', action=Copy("$TARGET", "$SOURCE"))