首页 > 解决方案 > 确保生成的尚未签入的文件在 git sync 后不会重建

问题描述

我不确定这是一个 git 问题还是一个 make 问题,或者两者兼而有之,但是这里有......

想象一下,您有一个foo.cpp由生成器程序生成的文件,gen.py. 如果生成器程序被修改,则必须再次运行以生成新版本的foo.cpp.

编码此行为的 makefile 规则可能如下1

foo.cpp: gen.py
    gen.py

对于积极的发展gen.py,我认为这是理想的。

但是,请考虑通过 git 远程提供项目的情况,例如在 github 或类似站点上。用户将下载并制作您的项目。

传统的处理生成文件的方式是不要全部检查:不要全部包含foo.cpp在git中(添加到你的`.gitignore),当用户构建你的项目时,会生成文件。

但是,另一种方法将生成的文件包含foo.cpp在 git 中。这增加了一种可能性,gen.py并且foo.cpp可能是“不同步” 2如果一个在没有更新另一个的情况下被更改但具有其他一些优点:

我对签入生成的文件与始终在本地生成它们的优点不感兴趣。您可能会假设,对于某些项目,已做出签入生成文件的决定,并且该决定没有争议。

那么,在这种情况下,我的问题是:

对于新克隆项目或同步涉及两个文件的提交的用户,我如何确保make不会尝试生成foo.cpp?默认情况下,git 在同步文件时将使用当前时间,因此foo.cppgen.py将具有相同的时间戳,并且foo.cpp将被重建。

我不能要求用户更改他们的 git 配置。


1也许会有额外的依赖关系foo.cpp也将作为先决条件出现,但这是“基本情况”。

2一种合理的方法是通过 git 挂钩强制它们同步。

标签: gitmakefile

解决方案


您可以比较输出,并且仅在目标不同时才更新目标。比如说,你gen.py会将它的输出写入标准输出,所以通常你会有这样的规则:

foo.cpp: gen.py
        ./gen.py > $@

您可以将其更改为:

foo.cpp: gen.py
        ./gen.py > $@.tmp
        cmp -s $@ $@.tmp && rm -f $@.tmp || mv -f $@.tmp $@

这里的缺点是,在您更新之前foo.cppgen.py配方将始终运行。但是,不会构建任何依赖的目标foo.cpp,因为它的时间戳没有改变。

如果这很昂贵并且您想解决这个问题,您将不得不做一些更复杂的事情:如果比较是真的(没有区别),那么您需要重置时间戳gen.py,使其与目标相同;这可确保目标在未来不再被视为过时。像这样的东西:

foo.cpp: gen.py
        ./gen.py > $@.tmp
        if test -e $@ && cmp -s $@ $@.tmp; then \
            rm -f $@.tmp; \
            touch -r $@ gen.py; \
        else \
            mv -f $@.tmp $@; \
        fi

如果您不能轻易更改输出文件名,gen.py那么您将不得不做一些更令人作呕的事情,例如在运行之前重命名$@为,然后将其改回或酌情使用新的。$@.old./gen.py

ETA如果您只是想避免gen.py在相同版本的情况下运行,这更像是一个 Git 问题而不是一个 makefile 问题,因为您真正要问的是如何知道这些文件是否在同一个 Git 提交中。这很容易,但正如我在下面的评论中所说,我认为这实际上是不对的:

foo.cpp: gen.py
        tver=$$(git log -n1 --oneline $@ 2>/dev/null); \
        pver=$$(git log -n1 --oneline $< 2>/dev/null); \
        if test -z "$$tver" || test -z "$$pver" || test "$$tver" != "$$pver"; then ./gen.py; fi

推荐阅读