如何使用cmake将git SHA1作为定义传递给编译器?

Łuk*_*Lew 51 git sha1 cmake

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

g++ -DGIT_SHA1="`git log -1 | head -n 1`" ...
Run Code Online (Sandbox Code Playgroud)

这非常有用,因为二进制文件知道确切的提交SHA1,所以它可以在segfault的情况下转储它.

我如何用CMake实现同样的目标?

Rya*_*lik 95

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

关于这些函数的好处是,每次HEAD提交更改时,它们都会在构建之前强制重新配置(重新运行cmake).与使用execute_process执行一次操作不同,您不需要记住重新编写以更新哈希定义.

出于这个特定目的,您至少需要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)
Run Code Online (Sandbox Code Playgroud)

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

add_definitions("-DGIT_SHA1=${GIT_SHA1}")
Run Code Online (Sandbox Code Playgroud)

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

GitSHA1.cpp.in:

#define GIT_SHA1 "@GIT_SHA1@"
const char g_GIT_SHA1[] = GIT_SHA1;
Run Code Online (Sandbox Code Playgroud)

GitSHA1.h:

extern const char g_GIT_SHA1[];
Run Code Online (Sandbox Code Playgroud)

将此添加到您的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)
Run Code Online (Sandbox Code Playgroud)

然后,你有一个包含SHA字符串的全局变量 - 当SHA执行时,带有extern的标题不会改变,所以你可以只包括你要引用字符串的任何地方,然后只需要生成的CPP需要在每次提交时重新编译,以便您可以访问所有SHA.

  • 是的,有一个用于源代码构建 - 它位于cmake-2.9.0-modules目录中,如果你确实包含(UseBackportedModules),它会自动包含在内.我已经向cmake提供了一些补丁,但是并没有觉得需要包含新模块 - 我很高兴将它们放在GitHub上,以便轻松共享和协作,从CMake发布周期取消链接. (2认同)
  • 谢谢你的解决方案.在我的C++共享库中,我也必须在cpp文件中使用extern关键字.否则,由于const,它默认为内部链接,导致链接时未定义的引用. (2认同)
  • 不错的答案!但对我来说,缺少一件事。我需要在 *GitSHA1.cpp.in* 中添加 `#include "GitSHA1.h"` 以将全局变量正确导出到静态库中。 (2认同)
  • FWIW,我相信我已经整合了@PiQuer提到的补丁. (2认同)

Dre*_*kes 17

我这样做是为了生成:

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";
Run Code Online (Sandbox Code Playgroud)

如果执行构建的工作空间具有挂起的未提交更改,则上面的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)
Run Code Online (Sandbox Code Playgroud)

这需要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@";
Run Code Online (Sandbox Code Playgroud)

而且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;
  };
}
Run Code Online (Sandbox Code Playgroud)

然后在代码中我可以写:

cout << "Build SHA1: " << Version::GIT_SHA1 << endl;
Run Code Online (Sandbox Code Playgroud)


the*_*itz 9

我会用的.在我的CMakeLists.txt中这样:

exec_program(
    "git"
    ${CMAKE_CURRENT_SOURCE_DIR}
    ARGS "describe"
    OUTPUT_VARIABLE VERSION )

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

add_definitions( -DGIT_SHA1="${VERSION_SHA1}" )
Run Code Online (Sandbox Code Playgroud)

  • 请注意,`exec_program` 已[弃用](https://cmake.org/cmake/help/latest/command/exec_program.html)。相反,您可以使用更简单的`execute_process`,例如对于这种情况:`execute_process( COMMAND git describe WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE VERSION )`(它看起来很忙,就像一行,这是我可以在评论中生成的内容,但是当正确地换行和缩进时会更干净。) (2认同)

eto*_*cky 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}\"")
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

解释

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

边注

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

  • 每次运行 cmake 时,这似乎都会强制完全重建。 (3认同)

小智 7

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

一些现有的解决方案:

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

第三种解决方案的一个修复是使用CMake'copy_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})
Run Code Online (Sandbox Code Playgroud)


Lar*_*dua 6

下面的解决方案是基于Git的更新日志HEAD当你观察pullcommit东西.请注意,例如,只有在每次手动重建CMake缓存后,上面的Drew建议才会更新Git信息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()
Run Code Online (Sandbox Code Playgroud)

内容gitrevision.hh将如下所示:

#define GITREVISION cb93d53 2014-03-13 11:08:15 +0100
Run Code Online (Sandbox Code Playgroud)

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

制作gitrevision.hh一个包含防护等功能的完全成熟的C++头文件留给读者练习:-)