我不确定这是一个 git 问题还是一个 make 问题,或者两者都是,但这里是......
假设您有一个由生成器程序 gen.py
生成的文件 foo.cpp
。如果修改了生成器程序,则必须再次运行它才能生成新版本的 foo.cpp
。
编码此行为的 makefile 规则可能如下1:
foo.cpp: gen.py
gen.py
对于 gen.py
上的积极开发,我认为这是理想的选择。
但是,请考虑通过 git(例如在 github 或其他类似网站上)远程提供项目的情况。用户将下载并制作您的项目。
处理生成文件的传统方法不是全部检查它们:不要在 git all 中包含 foo.cpp
(将其添加到您的 `.gitignore),并且当用户构建时您的项目,将生成该文件。
但是,另一种方法是将生成的文件foo.cpp
包含在git中。如果更改其中一个而不更新另一个,则 gen.py
和 foo.cpp
可能会“不同步”2,但还有一些其他优点:
- 只想构建项目但从不更新
gen.py
的用户不需要具备运行gen.py
的先决条件(例如 Python)。 - 未修改的构建速度更快,因为生成步骤不运行。
- 在暂存/提交期间,您会获得有关
gen.py
隐含的foo.cpp
更改的直接反馈,因为该文件将成为 diff 的一部分。<
我对检查生成的文件与始终在本地生成文件的优点不感兴趣。您可能会认为对于某些项目,已做出 checkin 生成文件的决定,并且该决定没有争议。
在这种情况下,我的问题是:
对于新克隆项目或同步涉及两个文件的提交的用户,如何确保 make
不会尝试生成 foo.cpp
?默认情况下,git 同步文件时会使用当前时间,因此 foo.cpp 和 gen.py 将具有相同的时间戳,并且 foo.cpp 会具有相同的时间戳。 code> 将被重建。
我不能要求用户更改他们的 git 配置。
1 也许 foo.cpp
会有额外的依赖项,这也将作为先决条件出现,但这是“基本情况”。
2 一个合理的方法是通过 git hook 强制它们同步。
最佳答案
您可以比较输出,并且仅在不同时更新目标。例如,您的gen.py
会将其输出写入标准输出,因此通常您会有这样的规则:
foo.cpp: gen.py
./gen.py > $@
您可以将其更改为:
foo.cpp: gen.py
./gen.py > <a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="b591f59bc1d8c5" rel="noreferrer noopener nofollow">[email protected]</a>
cmp -s $@ <a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="b793f799c3dac7" rel="noreferrer noopener nofollow">[email protected]</a> && rm -f <a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7f5b3f510b120f" rel="noreferrer noopener nofollow">[email protected]</a> || mv -f <a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="16325638627b66" rel="noreferrer noopener nofollow">[email protected]</a> $@
这里的缺点是,直到您DO更新 foo.cpp
gen.py
配方将始终运行。但是没有依赖的目标foo.cpp
将被构建,因为它的时间戳没有改变。
如果这很昂贵并且您想解决这个问题,您将不得不做一些更复杂的事情:如果比较是正确的(没有区别)那么您需要重置 gen.py
上的时间戳使其与目标相同;这确保了目标在未来将不再被认为是过时的。像这样的事情:
foo.cpp: gen.py
./gen.py > <a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="597d19772d3429" rel="noreferrer noopener nofollow">[email protected]</a>
if test -e $@ && cmp -s $@ <a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="1e3a5e306a736e" rel="noreferrer noopener nofollow">[email protected]</a>; then \
rm -f <a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f7d3b7d9839a87" rel="noreferrer noopener nofollow">[email protected]</a>; \
touch -r $@ gen.py; \
else \
mv -f <a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="2206620c564f52" rel="noreferrer noopener nofollow">[email protected]</a> $@; \
fi
如果您无法轻松更改 gen.py
的输出文件名那么你就必须做一些更恶心的事情,比如重命名 $@
至<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="8ca8cca2e3e0e8" rel="noreferrer noopener nofollow">[email protected]</a>
在运行之前./gen.py
然后根据需要将其改回或使用新的。
预计到达时间如果您只是想避免运行 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
关于git - 确保已生成但 checkin 的文件在 git 同步后不会重建,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53233870/