在Makefile中,这将喜欢的东西来完成:
g++ -DGIT_SHA1="`git log -1 | head -n 1`" ...
这是非常有用的,因为二进制的人都知道确切的承诺SHA1这样就可以在段错误的情况下,放弃它。
我怎样才能实现与C进行相同?
在Makefile中,这将喜欢的东西来完成:
g++ -DGIT_SHA1="`git log -1 | head -n 1`" ...
这是非常有用的,因为二进制的人都知道确切的承诺SHA1这样就可以在段错误的情况下,放弃它。
我怎样才能实现与C进行相同?
我做了窥视一个混帐回购协议的版本和类似用途的一些CMake的模块-他们都在我的仓库在https://github.com/rpavlik/cmake-modules
有关这些功能的好处是,他们会在每次HEAD提交更改时强制重新配置(cmake的的重播)构建之前。 不像execute_process做的事情只有一次,你不需要记住重新cmake的更新哈希定义。
对于这个特定的目的,你至少需要GetGitRevisionDescription.cmake
和GetGitRevisionDescription.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无处不在。
我这样做是在诸如方式产生:
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;
我会以某事物。 像这样在我的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}" )
这将是不错的,捕捉到版本库(从溶液git describe --dirty
),但只有一些关于git的信息已更改触发重新编译。
现有的一些解决方案:
.git/logs/HEAD
。 这仅触发重新编译的时候什么东西在回购的变化,但错过了变化,以获得“-dirty”状态。 -dirty
状态,但触发重新编译所有的时间(基于版本信息文件的更新的时间戳) 一个固定的第三个解决方案是使用CMake的“copy_if_different”命令,这样的版本信息文件的时间戳仅在内容的变化而变化。
在自定义命令的步骤如下:
该代码(从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})
下面的解决方案是基于Git的更新每当你的HEAD日志观察pull
或commit
的东西。 请注意,如德鲁的建议只有当你重建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 ++头文件留给读者作为练习向读者:-)
我不能帮你与CMake的一面,但相对于Git的身边,我会建议考虑看看如何Linux内核和Git项目本身做它,通过GIT-VERSION-GEN脚本,或TIG如何它在它的Makefile ,通过使用git describe
,如果有git仓库本,回落到“ version
” /“ VERSION
” /“ GIT-VERSION-FILE
”而生成和存在于压缩包,最后落回在脚本(或生成文件)的硬编码默认值。
第一部分(使用git describe
),您需要使用标签注释(以及可能的GPG签署)标签的版本。 或使用git describe --tags
也使用轻量级的标签。
这里是我的解决方案,我认为这是相当短而有效的;-)
首先,在源代码树(我命名它需要一个文件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血腥愚蠢的窗口支持>
及>>
运营商;-)
如果CMake的不具有内置的能力做这个替代,那么你可以写一个包装外壳脚本读取一个模板文件,在正确的位置替代如上SHA1哈希(使用sed
等),创建真正的CMake构建文件,然后调用CMake的构建项目。
稍有不同的办法可能是让SHA1替代可选 。 您可以创建一个虚拟散列值的CMake文件,如"NO_OFFICIAL_SHA1_HASH"
。 当开发人员构建自己从他们的工作目录版本,内置的代码将不包括SHA1散列值(仅适用于虚拟值),因为从工作目录的代码甚至没有相应的SHA1哈希值呢。
在另一方面,当正式版本是由您的构建服务器进行,从源从一个中央存储库拉,那么你知道的源代码的SHA1哈希值。 在这一点上,你可以替代在CMake的文件的哈希值,然后运行CMake的。
简单地增加一些代码,只有2个文件: CMakeList.txt
和main.cpp
。
# 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}\"")
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之前不存在的文件,使用它。
对于一个快速和肮脏的,可能不能移植的方式来获得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
,但我没有必要这么远。)