In a Makefile this would be done with something like:
g++ -DGIT_SHA1=\"`git log -1 | head -n 1`\" ...
This is very useful, because the bina
I can't help you with the CMake side, but with respect to Git side I would recommend taking a look how Linux kernel and Git project itself does it, via GIT-VERSION-GEN script, or how tig does it in its Makefile, by using git describe
if 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).
The first part (using git describe
) requires that you tag releases using annotated (and possibly GPG signed) tags. Or use git describe --tags
to use also lightweight tags.
The following solution is based on the observation that Git updates the HEAD log whenever you pull
or commit
something. Note that e.g. Drew's suggestion above will update the Git information only if you rebuild the CMake cache manually after every commit
.
I use a CMake "custom command" that generates a one-line header file ${SRCDIR}/gitrevision.hh
where ${SRCDIR}
is the root of your source tree. It will be re-made only when a new commit is made. Here is the necessary CMake magic with some comments:
# 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.hh
will look like this:
#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 %H
instead of %h
will print the full SHA1 digest. See the Git manual for details.
Making gitrevision.hh
a fully-fledged C++ header file with include guards etc. is left as an exercise to the reader :-)
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:
#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 exactly one empty newline at the end so that value can be appended.
And now this code goes in respective CMakeLists.txt
file:
# --- 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.in
is copied in the build tree as git-rev.h
and git revision is appended at its end.
So all you need to do next is include git-rev.h
in one of your files and do whatever you want with the GIT_REV
macro, which yields current git revision hash as a string value.
The nice thing about this solution is that the git-rev.h
is recreated each time you build the associated target, so you don't have to run cmake
over and over again.
It also should be pretty portable - no non-portable external tools were used and even the bloody stupid windows cmd supports the >
and >>
operators ;-)
Simply adding some code to only 2 files: CMakeList.txt
and 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
}
In CMakeList.txt
, the CMake commandexecute_process()
is used to call command git log -1 --format=%h
that 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_HASH
to the value of 4f34ee8
just 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.o
and in the compiled binaries a.out
.
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.
I'd use sth. like this in my 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}" )
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.
Some of the existing solutions:
execute_process
. This only gets the git information at configure time, and can miss changes to the repository..git/logs/HEAD
. This only triggers recompilation when something in the repo changes, but misses the changes to get the -dirty
state.-dirty
state, but triggers a recompilation all the time (based on the updated timestamp of the version information file)One fix to the third solution is to use the CMake copy_if_different
command, so the timestamp on the version information file only changes if the contents change.
The steps in the custom command are:
copy_if_different
to copy the temporary file to the real filemake
The code (borrowing heavily from kralyk's solution):
# 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})