66

在 Makefile 中,这将通过以下方式完成:

g++ -DGIT_SHA1="`git log -1 | head -n 1`" ...

这非常有用,因为二进制文件知道确切的提交 SHA1,因此它可以在出现段错误时转储它。

如何使用 CMake 实现相同的目标?

4

10 回答 10

117

我已经制作了一些 CMake 模块,这些模块可以与 git repo 进行版本控制和类似目的 - 它们都在我的存储库中https://github.com/rpavlik/cmake-modules

这些函数的好处是,每次 HEAD 提交更改时,它们都会在构建之前强制重新配置(重新运行 cmake)。与使用 execute_process 只做一次不同的是,您不需要记住重新 cmake 来更新散列定义。

为此,您至少需要GetGitRevisionDescription.cmakeGetGitRevisionDescription.cmake.in文件。然后,在你的主CMakeLists.txt文件中,你会有这样的东西

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/whereYouPutMyModules/")
include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC GIT_SHA1)

然后,您可以将其添加为系统范围的定义(不幸的是,这会导致大量重建)

add_definitions("-DGIT_SHA1=${GIT_SHA1}")

或者,我建议的替代方案:制作生成的源文件。在您的源代码中创建这两个文件:

GitSHA1.cpp.in:

#define GIT_SHA1 "@GIT_SHA1@"
const char g_GIT_SHA1[] = GIT_SHA1;

GitSHA1.h:

extern const char g_GIT_SHA1[];

将此添加到您的CMakeLists.txt(假设您在 SOURCES 中有源文件列表):

configure_file("${CMAKE_CURRENT_SOURCE_DIR}/GitSHA1.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/GitSHA1.cpp" @ONLY)
list(APPEND SOURCES "${CMAKE_CURRENT_BINARY_DIR}/GitSHA1.cpp" GitSHA1.h)

然后,您有一个包含您的 SHA 字符串的全局变量 - 当 SHA 发生变化时,带有 extern 的标头不会更改,因此您可以只包含您想要引用字符串的任何位置,然后只需要生成的 CPP在每次提交时重新编译,以便您可以在任何地方访问 SHA。

于 2010-11-30T21:05:46.160 回答
25

我这样做是为了生成:

const std::string Version::GIT_SHA1 = "e7fb69fb8ee93ac66f006406781138562d0250fb";
const std::string Version::GIT_DATE = "Thu Jan 9 14:17:56 2014";
const std::string Version::GIT_COMMIT_SUBJECT = "Fix all the bugs";

如果执行构建的工作区有待处理的、未提交的更改,则上述 SHA1 字符串将以-dirty.

CMakeLists.txt

# the commit's SHA1, and whether the building workspace was dirty or not
execute_process(COMMAND
  "${GIT_EXECUTABLE}" describe --match=NeVeRmAtCh --always --abbrev=40 --dirty
  WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
  OUTPUT_VARIABLE GIT_SHA1
  ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)

# the date of the commit
execute_process(COMMAND
  "${GIT_EXECUTABLE}" log -1 --format=%ad --date=local
  WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
  OUTPUT_VARIABLE GIT_DATE
  ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)

# the subject of the commit
execute_process(COMMAND
  "${GIT_EXECUTABLE}" log -1 --format=%s
  WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
  OUTPUT_VARIABLE GIT_COMMIT_SUBJECT
  ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)

# generate version.cc
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.cc.in" "${CMAKE_CURRENT_BINARY_DIR}/version.cc" @ONLY)

list(APPEND SOURCES "${CMAKE_CURRENT_BINARY_DIR}/version.cc" version.hh)

这需要version.cc.in

#include "version.hh"

using namespace my_app;

const std::string Version::GIT_SHA1 = "@GIT_SHA1@";
const std::string Version::GIT_DATE = "@GIT_DATE@";
const std::string Version::GIT_COMMIT_SUBJECT = "@GIT_COMMIT_SUBJECT@";

并且version.hh

#pragma once

#include <string>

namespace my_app
{
  struct Version
  {
    static const std::string GIT_SHA1;
    static const std::string GIT_DATE;
    static const std::string GIT_COMMIT_SUBJECT;
  };
}

然后在代码中我可以写:

cout << "Build SHA1: " << Version::GIT_SHA1 << endl;
于 2014-01-09T18:27:01.160 回答
14

如果有一个解决方案可以捕获对存储库的更改(来自git describe --dirty),但只有在有关 git 信息的某些内容发生更改时才会触发重新编译。

一些现有的解决方案:

  1. 使用execute_process. 这只会在配置时获取 git 信息,并且可能会错过对存储库的更改。
  2. 依赖.git/logs/HEAD. 这只会在 repo 中的某些内容发生更改时触发重新编译,但会错过获取-dirty状态的更改。
  3. 每次运行构建时,使用自定义命令重新构建版本信息。这会捕获导致-dirty状态的更改,但会一直触发重新编译(基于版本信息文件的更新时间戳)

第三种解决方案的一个修复方法是使用 CMakecopy_if_different命令,因此版本信息文件上的时间戳仅在内容更改时才会更改。

自定义命令中的步骤是:

  1. 将 git 信息收集到一个临时文件中
  2. 用于copy_if_different将临时文件复制到真实文件
  3. 删除临时文件,下次触发自定义命令再次运行make

代码(大量借鉴 kralyk 的解决方案):

# The 'real' git information file
SET(GITREV_BARE_FILE git-rev.h)
# The temporary git information file
SET(GITREV_BARE_TMP git-rev-tmp.h)
SET(GITREV_FILE ${CMAKE_BINARY_DIR}/${GITREV_BARE_FILE})
SET(GITREV_TMP ${CMAKE_BINARY_DIR}/${GITREV_BARE_TMP})

ADD_CUSTOM_COMMAND(
  OUTPUT ${GITREV_TMP}
  COMMAND ${CMAKE_COMMAND} -E echo_append "#define GIT_BRANCH_RAW " > ${GITREV_TMP}
  COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD >> ${GITREV_TMP}
  COMMAND ${CMAKE_COMMAND} -E echo_append "#define GIT_HASH_RAW " >> ${GITREV_TMP}
  COMMAND ${GIT_EXECUTABLE} describe --always --dirty --abbrev=40 --match="NoTagWithThisName" >> ${GITREV_TMP}
  COMMAND ${CMAKE_COMMAND} -E copy_if_different ${GITREV_TMP} ${GITREV_FILE}
  COMMAND ${CMAKE_COMMAND} -E remove ${GITREV_TMP}
  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
  VERBATIM
)
# Finally, the temporary file should be added as a dependency to the target

ADD_EXECUTABLE(test source.cpp ${GITREV_TMP})
于 2017-01-19T19:23:13.600 回答
10

我会在我的 CMakeLists.txt 中使用类似的东西:

execute_process(
    COMMAND git describe
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    OUTPUT_VARIABLE VERSION )

string( REGEX MATCH "-g.*$" VERSION_SHA1 ${VERSION} )
string( REGEX REPLACE "[-g]" "" VERSION_SHA1 ${VERSION_SHA1} )

add_definitions( -DGIT_SHA1="${VERSION_SHA1}" )
于 2010-06-30T20:49:49.667 回答
7

以下解决方案是基于观察到 Git 会在您pullcommit某事时更新 HEAD 日志。请注意,仅当您在每个commit.

我使用 CMake“自定义命令”生成一个单行头文件${SRCDIR}/gitrevision.hh,其中${SRCDIR}是源代码树的根。只有在做出新的提交时才会重新制作。以下是必要的 CMake 魔法和一些评论:

# Generate gitrevision.hh if Git is available
# and the .git directory is present
# this is the case when the software is checked out from a Git repo
find_program(GIT_SCM git DOC "Git version control")
mark_as_advanced(GIT_SCM)
find_file(GITDIR NAMES .git PATHS ${CMAKE_SOURCE_DIR} NO_DEFAULT_PATH)
if (GIT_SCM AND GITDIR)
    # Create gitrevision.hh
    # that depends on the Git HEAD log
    add_custom_command(OUTPUT ${SRCDIR}/gitrevision.hh
        COMMAND ${CMAKE_COMMAND} -E echo_append "#define GITREVISION " > ${SRCDIR}/gitrevision.hh
        COMMAND ${GIT_SCM} log -1 "--pretty=format:%h %ai" >> ${SRCDIR}/gitrevision.hh
        DEPENDS ${GITDIR}/logs/HEAD
        VERBATIM
    )
else()
    # No version control
    # e.g. when the software is built from a source tarball
    # and gitrevision.hh is packaged with it but no Git is available
    message(STATUS "Will not remake ${SRCDIR}/gitrevision.hh")
endif()

的内容gitrevision.hh将如下所示:

#define GITREVISION cb93d53 2014-03-13 11:08:15 +0100

如果要更改此设置,请--pretty=format:相应地编辑规范。例如,使用%H而不是%h将打印完整的 SHA1 摘要。有关详细信息,请参阅 Git 手册。

使用包含保护等制作gitrevision.hh一个成熟的 C++ 头文件作为练习留给读者:-)

于 2014-03-13T20:06:05.703 回答
7

解决方案

只需将一些代码添加到仅 2 个文件中:CMakeList.txtmain.cpp.

1.CMakeList.txt

# git commit hash macro
execute_process(
  COMMAND git log -1 --format=%h
  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
  OUTPUT_VARIABLE GIT_COMMIT_HASH
  OUTPUT_STRIP_TRAILING_WHITESPACE
)
add_definitions("-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")

2. main.cpp

inline void LogGitCommitHash() {
#ifndef GIT_COMMIT_HASH
#define GIT_COMMIT_HASH "0000000" // 0000000 means uninitialized
#endif
    std::cout << "GIT_COMMIT_HASH[" << GIT_COMMIT_HASH << "]"; // 4f34ee8
}

解释

CMakeList.txt中,CMake 命令execute_process()用于调用命令git log -1 --format=%h,该命令为您的 SHA-1 值提供简短且唯一的缩写,如4f34ee8. 该字符串被分配给名为 的 CMake 变量GIT_COMMIT_HASH。CMake 命令add_definitions()将宏定义为gcc 编译之前GIT_COMMIT_HASH的值。4f34ee8哈希值用于通过预处理器替换 C++ 代码中的宏,因此存在于目标文件main.o和编译后的二进制文件中a.out

边注

另一种实现方法是使用名为 的 CMake 命令configure_file(),但我不喜欢使用它,因为在运行 CMake 之前该文件不存在。

于 2016-09-28T06:06:19.863 回答
4

这是我的解决方案,我认为它相当短但有效;-)

首先,在源代码树中需要一个文件(我命名它git-rev.h.in),它应该看起来像这样:

#define STR_EXPAND(x) #x
#define STR(x) STR_EXPAND(x)
#define GIT_REV STR(GIT_REV_)
#define GIT_REV_ \ 
 

(请不要介意那些宏,用原始值制作字符串有点疯狂。)这个文件最后必须有一个空换行符,以便可以附加值。

现在这段代码进入各自的CMakeLists.txt文件:

# --- Git revision ---
add_dependencies(your_awesome_target gitrev)      #put name of your target here
include_directories(${CMAKE_CURRENT_BINARY_DIR})  #so that the include file is found
set(gitrev_in git-rev.h.in)                       #just filenames, feel free to change them...
set(gitrev git-rev.h)
add_custom_target(gitrev
  ${CMAKE_COMMAND} -E remove -f ${CMAKE_CURRENT_BINARY_DIR}/${gitrev}
  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${gitrev_in} ${CMAKE_CURRENT_BINARY_DIR}/${gitrev}
  COMMAND git rev-parse HEAD >> ${CMAKE_CURRENT_BINARY_DIR}/${gitrev}
  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}         #very important, otherwise git repo might not be found in shadow build
  VERBATIM                                              #portability wanted
)

此命令确保将其git-rev.h.in复制到构建树中,git-rev.h并在其末尾附加 git 修订。

所以接下来你需要做的就是包含git-rev.h在你的一个文件中,并使用宏做任何你想做的事情GIT_REV,这会产生当前的 git 修订哈希作为字符串值。

这个解决方案的git-rev.h好处是每次构建关联目标时都会重新创建,因此您不必cmake一遍又一遍地运行。

它也应该非常便携——没有使用非便携的外部工具,甚至该死的愚蠢的 Windows cmd 也支持>and>>运算符;-)

于 2011-09-24T15:40:12.723 回答
3

我无法在 CMake 方面为您提供帮助,但关于Git 方面,我建议您通过GIT-VERSION-GEN脚本查看 Linux 内核和 Git 项目本身是如何做到的,或者 tig 如何在其Makefile中做到这一点,通过使用git describe是否存在 git 存储库,回退到生成并存在于 tarball 中的“ version”/“ VERSION”/“ GIT-VERSION-FILE”,最后回退到脚本(或 Makefile)中硬编码的默认值。

第一部分(使用git describe)要求您使用带注释的(可能是 GPG 签名的)标签来标记发布。或者git describe --tags也可以使用轻量级标签。

于 2009-09-17T16:02:33.793 回答
1

如果 CMake 没有内置功能来执行此替换,那么您可以编写一个读取模板文件的包装器 shell 脚本,在正确的位置替换上面的 SHA1 哈希(sed例如,使用 ),创建真正的CMake 构建文件,然后调用 CMake 来构建您的项目。

稍微不同的方法可能是使 SHA1 替换可选。您将创建具有虚拟哈希值的 CMake 文件,例如"NO_OFFICIAL_SHA1_HASH". 当开发人员从他们的工作目录构建他们自己的构建时,构建的代码不会包含 SHA1 哈希值(只有虚拟值),因为工作目录中的代码甚至还没有相应的 SHA1 哈希值。

另一方面,当您的构建服务器从中央存储库中提取的源进行正式构建时,您就会知道源代码的 SHA1 哈希值。此时,您可以替换 CMake 文件中的哈希值,然后运行 ​​CMake。

于 2009-09-17T01:53:00.237 回答
0

对于使用 CMake 将 git SHA-1 导入 C 或 C++ 项目的一种快速而肮脏、可能不可移植的方法,我在 CMakeLists.txt 中使用它:

add_custom_target(git_revision.h
 git log -1 "--format=format:#define GIT_REVISION \"%H\"%n" HEAD > git_revision.h
 WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} VERBATIM)

它假定它CMAKE_SOURCE_DIR是 git 存储库的一部分,并且 git 在系统上可用,并且输出重定向将由 shell 正确解析。

然后,您可以使用此目标使该目标成为任何其他目标的依赖项

add_dependencies(your_program git_revision.h)

每次your_program构建时,Makefile(或其他构建系统,如果这适用于其他构建系统)将在源目录中重新创建 git_revision.h,其内容

#define GIT_REVISION "<SHA-1 of the current git revision>"

所以你可以#include git_revision.h从一些源代码文件中使用它。请注意,标头实际上是在每次构建时创建的,即即使每个其他目标文件都是最新的,它仍然会运行此命令来重新创建 git_revision.h。我认为这不应该是一个大问题,因为通常你不会一遍又一遍地重建相同的 git 版本,但这是需要注意的,如果这对你来说个问题,那就不要使用它。(可能有可能破解一个解决方法,add_custom_command但到目前为止我还不需要它。)

于 2013-01-18T05:29:01.147 回答