【问题标题】:Avoid recompilation with git and make避免使用 git 和 make 重新编译
【发布时间】:2012-05-08 12:40:49
【问题描述】:

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

Ofc,makefile 被配置为将项目构建到两个不同的构建目录中。

有没有办法解决这个问题?编译是一个非常漫长和耗时的过程……

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

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

但是,由于 Basic.h 由于结帐而更改了时间戳,所有包含 Basic.h 的文件都将被重新编译。我想避免这种情况。

【问题讨论】:

  • 我认为 git 只会更改确实已更改的文件的时间戳。在这种情况下,无论如何您都必须重建才能获得正确的结果。你在寻找什么行为?
  • 参见this 相关主题。基本上不建议这样做。
  • @sinelaw :- a.cpp 包括 Basic.h。我在 branch1_build/a.o 和 branch2_build/a.o 有两个 a.o 副本。现在,当我将分支从 branch1 切换到 branch2 时,即使它是最新的,它也会重新编译 branch2_build/a.o

标签: c++ c git makefile git-pull


【解决方案1】:

Git 仅更改在分支之间更新的文件。但是,如果您的编译器在任何单个文件被更改的情况下进行完全重建,您始终可以克隆并将不同的分支检出到不同的目录中。就像:

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

这会占用额外的磁盘空间,但比在一个巨大的 repo 中切换不同的分支要方便得多。

【讨论】:

  • 您还可以使用--shared 来节省大部分磁盘空间:选择其中一个克隆作为您的“主”存储,其余部分使用git clone --shared 参考主存储-克隆。只是不要删除“主”repo 中的任何分支(或只删除未共享的分支),这样你就安全了。
  • 嘿托雷克。你能扩展你关于共享克隆的答案吗?这不会创建一个组可写的共享存储库吗?
  • 不,--shared 只影响新克隆。事实上,大多数时候你甚至不需要--shared,因为 git(如果不是太古老的话)足够聪明,可以意识到它是本地克隆并使用硬链接。 (我想--shared 在 Windows 上是必需的。)
  • 嗯,这不是我希望实现的,但我想这是最接近可行的解决方案......
  • 另外,您可以使用git worktree 代替完整克隆。这样你至少可以共享 .git。
【解决方案2】:

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

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

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

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

因此,即使您执行 git pull 并获得对 Basic.h 的新更改,它也会有所帮助。

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      【解决方案4】:

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

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

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

      每次运行 Make 时,Makefile 中的逻辑都会计算 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 的更改。基本上,我们可以将 anything 中的更改转换为时间戳文件,该文件控制构建的内容。

      【讨论】:

      • 我希望你能意识到你的回答很荒谬。我给出的是恰当地说明手头问题的最小子集。
      • 显然,您认为尽管由于Basic.h 更改而重新编译了许多文件,但实际上并非必须如此。这意味着您认为这些文件在Basic.h 上有一些错误的依赖项(除了真实的)。改变它的唯一方法是分解Basic.h,或者引入一种完全不同的构建系统,它可以管理细粒度的依赖关系,例如“函数foo的参数3改变;重新编译所有调用foo的东西”。
      • 您回答的第二部分是关于我同意的一般编程技术的完全有效的观点,但是您完全错过了我的问题的全部要点并回答了我什至没有要求的内容。无论如何谢谢...
      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-03-25
        • 1970-01-01
        • 2014-11-01
        • 2015-09-18
        • 2020-12-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多