首页 > 解决方案 > 如何使 Git 提交哈希在 C++ 代码中可用而无需重新编译?

问题描述

一个相当普遍的要求,我想:我想myapp --version显示版本和 Git 提交哈希(包括存储库是否脏)。该应用程序是通过 a 构建的Makefile(实际上是由 生成的qmake,但现在让我们保持它“简单”)。我相当精通Makefiles,但是这个让我很难过。

我可以像这样轻松获得所需的输出:

$ git describe --always --dirty --match 'NOT A TAG'
e0e8556-dirty

C++ 代码期望提交哈希可用作名为 的预处理器宏GIT_COMMIT,例如:

#define GIT_COMMIT "e0e8556-dirty" // in an include file
-DGIT_COMMIT=e0e8556-dirty         // on the g++ command line

以下是我尝试将git describe输出连接到 C++ 的几种不同方法。它们都不能完美地工作。

方法一:$(shell)功能。

我们使用 make 的$(shell)函数来运行 shell 命令并将结果粘贴到 make 变量中:

GIT_COMMIT := $(shell git describe --always --dirty --match 'NOT A TAG')

main.o: main.cpp
    g++ -c -DGIT_COMMIT=$(GIT_COMMIT) -o$@ $<

这适用于干净的构建,但有一个问题:如果我更改 Git 哈希(例如,通过提交或修改干净工作副本中的某些文件),make 看不到这些更改,并且二进制文件不会被重建。

方法二:生成version.h

在这里,我们使用一个 make recipe 来生成一个version.h包含必要的预处理器定义的文件。目标是虚假的,因此它总是会被重建(否则,在第一次构建后它总是被视为最新的)。

.PHONY: version.h
version.h:
    echo "#define GIT_COMMIT \"$(git describe --always --dirty --match 'NOT A TAG')\"" > $@

main.o: main.cpp version.h
    g++ -c -o$@ $<

这可以可靠地工作并且不会错过对 Git 提交哈希的任何更改,但这里的问题是它总是重建version.h以及依赖它的所有内容(包括相当长的链接阶段)。

方法三:仅version.h在发生变化时生成

这个想法:如果我将输出写入version.h.tmp,然后将其与现有的进行比较,version.h并且仅在它不同时覆盖后者,我们并不总是需要重建。

但是,在实际开始运行任何配方之前,请弄清楚它需要重建什么。所以这必须在那个阶段之前,即也从一个$(shell)函数运行。

这是我的尝试:

$(shell echo "#define GIT_COMMIT \"$$(git describe --always --dirty --match 'NOT A TAG')\"" > version.h.tmp; if diff -q version.h.tmp version.h >/dev/null 2>&1; then rm version.h.tmp; else mv version.h.tmp version.h; fi)

main.o: main.cpp version.h
    g++ -c -o$@ $<

几乎可行:每当 Git 哈希更改时,第一个构建会重新生成version.h并重新编译,但第二个构建也是如此。从那时起,make 决定一切都是最新的。

因此,似乎 make 甚至在它运行$(shell)函数之前就决定了要重建的内容,这也使得这种方法也被破坏了。

这似乎是一件很常见的事情,并且由于 make 是一个如此灵活的工具,我很难相信没有办法让这 100% 正确。这种方法存在吗?

标签: c++gitmakefileg++

解决方案


我在这里找到了一个不错的解决方案:

在你的CMakeLists.txt地方:

# Get the current working branch
execute_process(
    COMMAND git rev-parse --abbrev-ref HEAD
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    OUTPUT_VARIABLE GIT_BRANCH
    OUTPUT_STRIP_TRAILING_WHITESPACE)

# Get the latest commit hash
execute_process(
    COMMAND git rev-parse HEAD
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    OUTPUT_VARIABLE GIT_COMMIT_HASH
    OUTPUT_STRIP_TRAILING_WHITESPACE)

然后在您的源代码中定义它:

target_compile_definitions(${PROJECT_NAME} PRIVATE
    "-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")

在源代码中,它现在可以作为#define. 人们可能想通过包括以下内容来确保源代码仍然正确编译:

#ifndef GIT_COMMIT_HASH
#define GIT_COMMIT_HASH "?"
#endif

然后你就可以使用了,例如:

std::string hash = GIT_COMMIT_HASH;

推荐阅读