sconsrevertrebuild

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


I have a Flex source file and the result of converting it to C++ both stored in version control. (Pretty common practice. This allows compiling the program on machines that don't have Flex installed.)

In some situations, operations on version control may downgrade the target file while the source remains in the latest version. (This is intended.)

In such cases, I would like SCons to just build the target again, so it is up to date. However, it doesn't detect that the target file is outdated. It seems to only check if the source file has changed. Can I make SCons also check for changes in the target file while it's deciding if a rebuild is required?


You can test that behavior, using this one-line SConstruct:

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

If you have this SConstruct file and you run the following commands:

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

you get the this result:

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

Solution

  • As demorgan has suggested in their answer, the solution is to write a new decider, which takes the target into consideration while calculating the result.

    However, it is not necessary to write a custom code that stores the previous hash value. We can use the same mechanism that is used to store in the SCons database previous hash values of dependencies. If we use function my_file.get_csig() it will not only return the current hash for this file but also will store it, so it is accessible via my_file.get_stored_info().csig the next time.

    (Remember to call get_csig() each time even if you don't need its value this time, to ensure the stored value is not outdated. Also remember that the object returned by get_stored_info() doesn't always have the field csig, just like the object prev_ni, so you need to check for that field in both cases.)

    Here is an example of a custom decider that check hashes of the dependency and the target and cause a rebuild if any of them has changed:

    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"))