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

Solution 1:

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.

Solution 2:

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;

Solution 3:

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:

  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 -dirty state.
  3. Use a custom command to rebuild the version information every time a build is run. This catches changes resulting in the -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:

  1. Collect the git information to a temporary file
  2. Use copy_if_different to 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

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})

Solution 4:

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

execute_process(
    COMMAND git describe
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    OUTPUT_VARIABLE VERSION )

string( REGEX MATCH "-g.*$" VERSION_SHA1 ${VERSION} )
string( REGEX REPLACE "[-g]" "" VERSION_SHA1 ${VERSION_SHA1} )

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