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

时间:2009-09-16 23:01:30

标签: git cmake sha1

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

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

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

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

10 个答案:

答案 0 :(得分:90)

我已经制作了一些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)

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

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。

答案 1 :(得分:14)

我这样做是为了生成:

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;

答案 2 :(得分: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}" )

答案 3 :(得分:6)

最好有一个解决方案能够捕获存储库的更改(来自git describe --dirty),但只有在有关git信息的内容发生变化时才会触发重新编译。

一些现有的解决方案:

  1. 使用'execute_process'。这只会在配置时获取git信息,并且可能会错过对存储库的更改。
  2. 取决于.git/logs/HEAD。这只会在repo中的某些内容发生更改时触发重新编译,但会错过更改以获得“-dirty”状态。
  3. 每次运行构建时,使用自定义命令重建版本信息。这会捕获导致-dirty状态的更改,但会一直触发重新编译(基于版本信息文件的更新时间戳)
  4. 对第三种解决方案的一个修复是使用CMake'copy_if_different'命令,因此版本信息文件中的时间戳仅在内容更改时才会更改。

    自定义命令中的步骤是:

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

      代码(从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})
      

答案 4 :(得分:5)

以下解决方案基于以下观察:当您pullcommit时,Git会更新HEAD日志。请注意,例如只有在每commit之后手动重建CMake缓存时,上面的Drew建议才会更新Git信息。

我使用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 ++头文件留给读者: - )

答案 5 :(得分:3)

我对CMake方面无法帮助你,但对于 Git方,我建议通过GIT-VERSION-GEN脚本看看Linux内核和Git项目本身是如何做到的如果存在git存储库,则使用 git describe ,或者version /“{{1 “/”VERSION“生成并存在于tarball中,最后回退到脚本(或Makefile)中硬编码的默认值。

第一部分(使用GIT-VERSION-FILE)要求您使用带注释的(可能是GPG签名的)标记来标记版本。或者使用git describe来使用轻量级标签。

答案 6 :(得分:3)

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

首先,源树中需要一个文件(我将其命名为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 revision。

所以你需要做的就是在你的一个文件中加入git-rev.h并用GIT_REV宏做你想做的事情,这会产生当前的git revision hash作为字符串值。

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

它也应该是非常便携的 - 没有使用非便携式外部工具,甚至血腥的笨窗口cmd支持>>>运算符; - )

答案 7 :(得分:1)

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

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

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

答案 8 :(得分:1)

解决方案

只需将一些代码添加到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 。此字符串分配给名为GIT_COMMIT_HASH的CMake变量。 CMake命令add_definitions()在gcc编译之前将宏GIT_COMMIT_HASH定义为4f34ee8的值。哈希值用于通过预处理器替换C ++代码中的宏,因此存在于目标文件main.o和编译的二进制文件a.out中。

旁注

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

答案 9 :(得分: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来解决变通方法,但到目前为止我还没有需要它。)