如何传递混帐SHA1使用cmake编译器的定义是什么?(How can I pass git SHA

2019-07-18 07:22发布

在Makefile中,这将喜欢的东西来完成:

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

这是非常有用的,因为二进制的人都知道确切的承诺SHA1这样就可以在段错误的情况下,放弃它。

我怎样才能实现与C进行相同?

Answer 1:

我做了窥视一个混帐回购协议的版本和类似用途的一些CMake的模块-他们都在我的仓库在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 (假设你的源文件的源列表):

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字符串的全局变量 - 与EXTERN的头球时SHA确实不会改变,所以你可以包括任何地方,你想指的是字符串,然后只产生CPP需要在每次重新编译承诺给你访问到SHA无处不在。



Answer 2:

我这样做是在诸如方式产生:

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;


Answer 3:

我会以某事物。 像这样在我的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}" )


Answer 4:

这将是不错的,捕捉到版本库(从溶液git describe --dirty ),但只有一些关于git的信息已更改触发重新编译。

现有的一些解决方案:

  1. 使用 'execute_process'。 这仅得到在配置时git的信息,并能错过资料库的修改。
  2. 依靠.git/logs/HEAD 。 这仅触发重新编译的时候什么东西在回购的变化,但错过了变化,以获得“-dirty”状态。
  3. 使用自定义命令每一个构建运行时重建的版本信息。 这捕获产生的变化-dirty状态,但触发重新编译所有的时间(基于版本信息文件的更新的时间戳)

一个固定的第三个解决方案是使用CMake的“copy_if_different”命令,这样的版本信息文件的时间戳仅在内容的变化而变化。

在自定义命令的步骤如下:

  1. 收集的git信息到一个临时文件
  2. 使用“copy_if_different”临时文件复制到真正的文件
  3. 删除临时文件,以触发自定义命令对下一个“制作”再次运行

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


Answer 5:

下面的解决方案是基于Git的更新每当你的HEAD日志观察pullcommit的东西。 请注意,如德鲁的建议只有当你重建CMake的缓存后,手动每个上面会更新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()

内容gitrevision.hh看起来就像这样:

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

如果你想改变这个再编辑--pretty=format:相应的规范。 例如,使用%H代替%h将打印完整的SHA1摘要。 详情请参阅Git的手册。

使gitrevision.hh与包括警卫等一个完全成熟的C ++头文件留给读者作为练习向读者:-)



Answer 6:

我不能帮你与CMake的一面,但相对于Git的身边,我会建议考虑看看如何Linux内核和Git项目本身做它,通过GIT-VERSION-GEN脚本,或TIG如何它在它的Makefile ,通过使用git describe ,如果有git仓库本,回落到“ version ” /“ VERSION ” /“ GIT-VERSION-FILE ”而生成和存在于压缩包,最后落回在脚本(或生成文件)的硬编码默认值。

第一部分(使用git describe ),您需要使用标签注释(以及可能的GPG签署)标签的版本。 或使用git describe --tags也使用轻量级的标签。



Answer 7:

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

首先,在源代码树(我命名它需要一个文件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
)

该该命令ensuers git-rev.h.in在构建树作为复制git-rev.h和git修订在其端部被附加。

因此,所有你需要做其次是包括git-rev.h在文件中的一个,做任何你想用GIT_REV宏,它产生电流的Git版本哈希为一个字符串值。

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

这也应该是相当便携-未使用任何非便携式外部工具,甚至在cmd血腥愚蠢的窗口支持>>>运营商;-)



Answer 8:

如果CMake的不具有内置的能力做这个替代,那么你可以写一个包装外壳脚本读取一个模板文件,在正确的位置替代如上SHA1哈希(使用sed等),创建真正的CMake构建文件,然后调用CMake的构建项目。

稍有不同的办法可能是让SHA1替代可选 。 您可以创建一个虚拟散列值的CMake文件,如"NO_OFFICIAL_SHA1_HASH" 。 当开发人员构建自己从他们的工作目录版本,内置的代码将不包括SHA1散列值(仅适用于虚拟值),因为从工作目录的代码甚至没有相应的SHA1哈希值呢。

在另一方面,当正式版本是由您的构建服务器进行,​​从源从一个中央存储库拉,那么你知道的源代码的SHA1哈希值。 在这一点上,你可以替代在CMake的文件的哈希值,然后运行CMake的。



Answer 9:

简单地增加一些代码,只有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()定义宏GIT_COMMIT_HASH到的值4f34ee8只是GCC编译之前。 散列值是通过使用预处理器来代替在C ++代码宏,并且因此存在于对象文件main.o和在编译的二进制文件a.out

边注

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



Answer 10:

对于一个快速和肮脏的,可能不能移植的方式来获得git的SHA-1成C或使用CMake的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是可用的系统上,并且输出重定向将外壳正确解析。

然后,您可以使这个目标的任何其他目标的依赖使用

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 ,但我没有必要这么远。)



文章来源: How can I pass git SHA1 to compiler as definition using cmake?
标签: git cmake sha1