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 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
.
In 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
:
#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
:
#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;
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:
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_DIR
is 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.
You can then make this target a dependency of any other target using
add_dependencies(your_program git_revision.h)
Each time your_program
is 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
#define GIT_REVISION "<SHA-1 of the current git revision>"
So you can #include git_revision.h
from some source code file and use it that way. Note that the header is created at literally every build, 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 is a problem for you, then don't use this. (It's probably possible to hack up a workaround using add_custom_command
but I haven't needed it so far.)
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
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.
For this specific purpose, you'd need at least the GetGitRevisionDescription.cmake
and GetGitRevisionDescription.cmake.in
files. Then, in your main CMakeLists.txt
file, you'd have something like this
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:
#define GIT_SHA1 "@GIT_SHA1@"
const char g_GIT_SHA1[] = GIT_SHA1;
GitSHA1.h:
extern const char g_GIT_SHA1[];
Add this to your CMakeLists.txt
(assuming you have a list of source files in 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.
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.
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.
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.