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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1435953/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 07:03:51  来源:igfitidea点击:

How can I pass git SHA1 to compiler as definition using cmake?

gitcmakesha1

提问by ?ukasz Lew

In a Makefile this would be done with something like:

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

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

This is very useful, because the binary knows exact commit SHA1 so it can dump it in case of segfault.

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

How can I achieve the same with CMake?

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

回答by Ryan Pavlik

I've made some CMake modules that peer into a git repo for versioning and similar purposes - they're all in my repository at https://github.com/rpavlik/cmake-modules

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

The good thing about these functions is, they will force a re-configure (a rerun of cmake) before a build every time the HEAD commit changes. Unlike doing something just once with execute_process, you don't need to remember to re-cmake to update the hash definition.

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

For this specific purpose, you'd need at least the GetGitRevisionDescription.cmakeand GetGitRevisionDescription.cmake.infiles. Then, in your main CMakeLists.txtfile, you'd have something like this

为此特定目的,您至少需要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)

Then, you could either add it as a system-wide definition (which unfortunately would cause lots of rebuilding)

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

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

or, my suggested alternative: Make a generated source file. Create these two files in your source:

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

GitSHA1.cpp.in:

GitSHA1.cpp.in:

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

GitSHA1.h:

GitSHA1.h:

extern const char g_GIT_SHA1[];

Add this to your CMakeLists.txt(assuming you have a list of source files in SOURCES):

将此添加到您的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)

Then, you have a global variable containing your SHA string - the header with the extern doesn't change when the SHA does, so you can just include that any place you want to refer to the string, and then only the generated CPP needs to be recompiled on every commit to give you access to the SHA everywhere.

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

回答by Drew Noakes

I did this in such as way as to generate:

我这样做是为了生成:

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";

If the workspace that performed the build had pending, uncommitted changes, the above SHA1 string will be suffixed with -dirty.

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

In CMakeLists.txt:

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)

This requires version.cc.in:

这需要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@";

And version.hh:

并且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;
  };
}

Then in code I can write:

然后在代码中我可以写:

cout << "Build SHA1: " << Version::GIT_SHA1 << endl;

回答by the Ritz

I'd use sth. like this in my CMakeLists.txt:

我会用…… 在我的 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}" )

回答by Mark Dewing

It would be nice to have a solution that catches changes to the repository (from git describe --dirty), but only triggers recompilation if something about the git information has changed.

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

Some of the existing solutions:

一些现有的解决方案:

  1. Use execute_process. This only gets the git information at configure time, and can miss changes to the repository.
  2. Depend on .git/logs/HEAD. This only triggers recompilation when something in the repo changes, but misses the changes to get the -dirtystate.
  3. Use a custom command to rebuild the version information every time a build is run. This catches changes resulting in the -dirtystate, but triggers a recompilation all the time (based on the updated timestamp of the version information file)
  1. 使用execute_process. 这只会在配置时获取 git 信息,并且可能会错过对存储库的更改。
  2. 取决于.git/logs/HEAD. 这只会在 repo 中的某些内容发生更改时触发重新编译,但会错过更改以获取-dirty状态。
  3. 每次运行构建时,使用自定义命令重建版本信息。这会捕获导致-dirty状态的更改,但始终会触发重新编译(基于版本信息文件的更新时间戳)

One fix to the third solution is to use the CMake copy_if_differentcommand, so the timestamp on the version information file only changes if the contents change.

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

The steps in the custom command are:

自定义命令中的步骤是:

  1. Collect the git information to a temporary file
  2. Use copy_if_differentto copy the temporary file to the real file
  3. Delete the temporary file, to trigger the custom command to run again on the next make
  1. 将git信息收集到一个临时文件
  2. 使用copy_if_different临时文件复制到真正的文件
  3. 删除临时文件,触发自定义命令在下一次再次运行 make

The code (borrowing heavily from kralyk's solution):

代码(大量借鉴 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})

回答by Laryx Decidua

The following solution is based on the observation that Git updates the HEAD log whenever you pullor commitsomething. Note that e.g. Drew's suggestion above will update the Git information only if you rebuild the CMake cache manually after every commit.

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

I use a CMake "custom command" that generates a one-line header file ${SRCDIR}/gitrevision.hhwhere ${SRCDIR}is the root of your source tree. It will be re-made onlywhen a new commit is made. Here is the necessary CMake magic with some comments:

我使用 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()

The contents of gitrevision.hhwill look like this:

的内容gitrevision.hh如下所示:

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

If you want to change this then edit the --pretty=format:specification accordingly. E.g. using %Hinstead of %hwill print the full SHA1 digest. See the Git manual for details.

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

Making gitrevision.hha fully-fledged C++ header file with include guards etc. is left as an exercise to the reader :-)

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

回答by etoricky

Solution

解决方案

Simply adding some code to only 2 files: CMakeList.txtand main.cpp.

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

1. CMakeList.txt

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

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
}

Explanation

解释

In CMakeList.txt, the CMake commandexecute_process()is used to call command git log -1 --format=%hthat give you the short and unique abbreviation for your SHA-1 values in string like 4f34ee8. This string is assigned to CMake variable called GIT_COMMIT_HASH. The CMake command add_definitions()defines the macro GIT_COMMIT_HASHto the value of 4f34ee8just before gcc compilation. The hash value is used to replace the macro in C++ code by preprocessor, and hence exists in the object file main.oand in the compiled binaries a.out.

在 中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

Side Note

边注

Another way to achieve is to use CMake command called configure_file(), but I don't like to use it because the file does not exist before CMake is run.

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

回答by kralyk

Here's my solution, which I think is reasonably short yet effective ;-)

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

First, a file is needed in the source tree (I name it git-rev.h.in), it should looks something like this:

首先,源树中需要一个文件(我将其命名为git-rev.h.in),它应该如下所示:

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

(Please never mind those macros, that's a little bit insane trick to make a string out of a raw value.) It is essential that this file has exactlyone empty newline at the end so that value can be appended.

(请别提这些宏,这是一个有点疯狂的技巧,使一个串出原始值)。重要的是,此文件具有完全相同在最后一个空的换行,使价值可以追加。

And now this code goes in respective CMakeLists.txtfile:

现在这段代码在各自的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
)

This command ensuers that the git-rev.h.inis copied in the build tree as git-rev.hand git revision is appended at its end.

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

So all you need to do next is include git-rev.hin one of your files and do whatever you want with the GIT_REVmacro, which yields current git revision hash as a string value.

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

The nice thing about this solution is that the git-rev.his recreated each time you build the associated target, so you don't have to run cmakeover and over again.

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

It also should be pretty portable - no non-portable external tools were used and even the bloody stupid windows cmd supports the >and >>operators ;-)

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

回答by Jakub Nar?bski

I can't help you with the CMake side, but with respect to Git sideI would recommend taking a look how Linux kernel and Git project itself does it, via GIT-VERSION-GENscript, or how tig does it in its Makefile, by using git describeif there is git repository present, falling back to "version" / "VERSION" / "GIT-VERSION-FILE" generated and present in tarballs, finally falling back to default value hardcoded in script (or Makefile).

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

The first part (using git describe) requires that you tag releases using annotated (and possibly GPG signed) tags. Or use git describe --tagsto use also lightweight tags.

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

回答by Greg Hewgill

If CMake doesn't have a built-in capability to do this substitution, then you could write a wrapper shell script that reads a template file, substitutes the SHA1 hash as above in the correct location (using sed, for example), creates the real CMake build file, and then calls CMake to build your project.

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

A slightly different approach might be to make the SHA1 substitution optional. You would create the CMake file with a dummy hash value such as "NO_OFFICIAL_SHA1_HASH". When developers build their own builds from their working directories, the built code would not include a SHA1 hash value (only the dummy value) because the code from the working directory doesn't even have a corresponding SHA1 hash value yet.

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

On the other hand, when an official build is made by your build server, from sources pulled from a central repository, then you know the SHA1 hash value for the source code. At that point, you can substitute the hash value in the CMake file and then run CMake.

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

回答by David Z

For a quick and dirty, possibly not portable way to get the git SHA-1 into a C or C++ project using CMake, I use this in CMakeLists.txt:

对于使用 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)

It assumes that CMAKE_SOURCE_DIRis part of a git repository, and that git is available on the system, and that an output redirection will be properly parsed by the shell.

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

You can then make this target a dependency of any other target using

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

add_dependencies(your_program git_revision.h)

Each time your_programis built, the Makefile (or other build system, if this works on other build systems) will recreate git_revision.h in the source directory, with the contents

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

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

So you can #include git_revision.hfrom some source code file and use it that way. Note that the header is created at literally everybuild, i.e. even if every other object file is up to date, it will still run this command to recreate git_revision.h. I figure that shouldn't be a huge problem because usually you don't rebuild the same git revision over and over again, but it's something to be aware of, and if it isa problem for you, then don't use this. (It's probably possible to hack up a workaround using add_custom_commandbut I haven't needed it so far.)

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