避免使用 git 和 make 重新编译

Avoid recompilation with git and make

本文关键字:新编译 编译 make git      更新时间:2023-10-16

我在 git 中有两个开发分支,我经常需要在这两个分支之间切换。然而,真正令人沮丧的是,每次我在 git 中更改分支时,整个项目都会被重建,因为某些文件的文件系统时间戳会发生变化。

Ofc,生成文件配置为将项目构建到两个不同的构建目录中。

有什么办法吗?编译是一个非常漫长且耗时的过程...

编辑:- 这是对问题的稍微更详细的解释...假设我有一个头文件Basic.h,它包含在许多其他文件中。分支 1 和分支 2 之间的 Basic.h 不同。

现在假设我已经将分支 1 编译为 build_branch1,将分支 2 编译为 build_branch2。假设我当前已签出分支 2。现在我签出分支 1 并更改 File1.cpp 并重新编译。理想情况下,由于自上次编译以来只有 File1.cpp 发生了变化,因此这是唯一应该重新编译的文件。

但是,由于 Basic.h 的

时间戳因签出而更改,因此包含 Basic.h 的所有文件都将重新编译。我想避免这种情况。

Git 只更改在分支之间更新的文件。但是,如果您的编译器执行完全重建,即使更改了任何单个文件,您始终可以将不同的分支克隆并签出到不同的目录中。这就像:

/your-repo-name.branch1
/your-repo-name.branch2

这需要额外的磁盘空间,但比在大型存储库中切换不同的分支要方便得多。

另一个部分答案:编译器缓存。

当您切换回原始分支并重建时,尽管依赖项说必须重建依赖于Basic.h的大量文件,但可以从编译器缓存中提取目标文件。

ccache(http://ccache.samba.org/(仍然需要做一些相当昂贵的工作(处理预处理的翻译单元,因为整个翻译单元都使用哈希键(,但它比编译便宜得多。

在某些情况下,ccache可以在面对不影响编译的更改(例如某些空格更改(时消除编译。例如,如果更改依赖文件(标头或源(中的注释,则不会使缓存的对象文件失效。

因此,即使您进行git pull并选择新的更改以Basic.h您以前从未见过的更改,它也会有所帮助。

如果你知道哪些文件实际上需要编译,并且手动执行这些操作,GNU Make(至少,我不知道其他实现(为你提供了一个标志:-t,它基本上运行在你的Makefile上并更改时间戳而不是运行命令。

在使用此标志之前,您仍然需要更新需要更新的文件,否则您最终会得到合法过时但看起来已更新的对象文件。 有关详细信息,请参阅链接的文档。

-o选项也可能让您感兴趣,具体取决于切换分支时的变化量。

如果分支之间的Basic.h实际上不同,那么唯一的解决方法是将其分解为具有更细粒度依赖项的较小文件,以便在更改时重建更少的内容。

但假设分支之间的Basic.h实际上完全相同,但git正在更改其时间戳。这是一个错误的触发器,就像做touch Basic.h一样,它揭示了基于时间戳的构建系统的局限性。理想情况下,我们希望构建系统在内容更改时重建,而不是在时间戳更改时重建。使用时间戳是因为它们是检测内容更改的廉价替代品。高级方法是让构建系统保留所有文件的哈希并检测实际修改,而不考虑时间戳。这也修复了诸如"检测到时钟偏差;您的构建可能不完整"。

你不会从Make中得到这种构建系统;但你可以处理它的某些方面。例如,您可以编写 Make 规则,使目标文件不直接依赖于Basic.h而是依赖于 Basic.h.sha,这是一个戳记文件。现在,Basic.h.sha是什么?此文件包含 Basic.h 的 SHA256 哈希。

每次运行 Make 时,生成文件中的逻辑都会计算Basic.h哈希,并将其与存储在 Basic.h.sha 中的哈希进行比较。如果它们不同,则Basic.h.sha被新哈希覆盖。因此,它的时间戳被撞了,触发了重建。

BASIC_H_SHA := $(shell sha256sum Basic.h)
BASIC_H_SHA_OLD := $(shell cat Basic.h.sha)
ifneq ($(BASIC_H_SHA),$(BASIC_H_SHA_OLD))
$(info Basic.h has changed!)
DUMMY := $(shell echo "$(BASIC_H_SHA)" > Basic.h.sha)
endif

我已经按照这些思路实现了逻辑,使模块依赖于对各自CFLAGS的更改。基本上,我们可以将任何内容的更改转换为时间戳文件的触摸,该文件控制构建的内容。

没有很好的方法来保留时间戳并且不会在构建环境中遇到麻烦。 使用 git clone/git checkout branchB 创建第二个工作目录,用于构建和使用分支 B。 不要让你的 Makefile 同时构建,让每个构建到自己的构建目录中。