Compile-time git version info using CMake
I recently found myself wanting to get some insight into what exactly went into
a given binary that we deployed on an embedded system. As a first step I wanted
to get the commit hash for the HEAD
of the branch from which it was built.
After a bit of searching around I stumbled upon this blog post by Matthew Keeter
which I took as a starting point. Instead of creating the file from the cmake
script directly I instead opted to have a template file that would be filled in
at configure time.
This is the modified cmake script:
0 execute_process(COMMAND git log --pretty=format:'%h' -n 1
1 OUTPUT_VARIABLE GIT_REV
2 ERROR_QUIET
3 )
4
5 if("${GIT_REV}" STREQUAL "")
6 set(GIT_REV "N/A")
7 set(GIT_DIFF "")
8 set(GIT_TAG "N/A")
9 set(GIT_BRANCH "N/A")
10 else()
11 execute_process(
12 COMMAND bash -c "git diff --quiet --exit-code || echo -dirty"
13 OUTPUT_VARIABLE GIT_DIFF)
14 execute_process(
15 COMMAND git describe --exact-match --tags OUTPUT_VARIABLE GIT_TAG ERROR_QUIET)
16 execute_process(
17 COMMAND git rev-parse --abbrev-ref HEAD OUTPUT_VARIABLE GIT_BRANCH)
18
19 string(STRIP "${GIT_REV}" GIT_REV)
20 string(SUBSTRING "${GIT_REV}" 1 7 GIT_REV)
21 string(STRIP "${GIT_DIFF}" GIT_DIFF)
22 string(STRIP "${GIT_TAG}" GIT_TAG)
23 string(STRIP "${GIT_BRANCH}" GIT_BRANCH)
24 endif()
25
26 configure_file(
27 "${SRC_DIR}/version.h.in"
28 "${BIN_DIR}/version.h"
29 )
We collect the following information:
GIT_REV
is the current abbreviated commit hashGIT_DIFF
will contain the string-dirty
if the tree from which it was built is dirtyGIT_TAG
will contain the tag only if the current commit has a tag associated with it.GIT_BRANCH
will contain the name of the current branch
The template for the version header file is fairly straightforward.
0#ifndef VERSION_H
1#define VERSION_H
2
3#define GIT_REV "@GIT_REV@@GIT_DIFF@"
4#define GIT_TAG "@GIT_TAG@"
5#define GIT_BRANCH "@GIT_BRANCH@"
6
7#endif /* VERSION_H */
Triggering the generation of this file is in my opinion most easily accomplished by adding a target for it:
0 add_custom_target(gen-version-h
1 COMMAND "${CMAKE_COMMAND}"
2 "-D" "SRC_DIR=${PROJECT_SOURCE_DIR}/src"
3 "-D" "BIN_DIR=${CMAKE_CURRENT_BINARY_DIR}"
4 "-P" "${PROJECT_SOURCE_DIR}/support/version.cmake"
5 COMMENT "Generating git version file"
6 )
7
8 add_dependencies(my-application gen-version-h)
9 include_directories(${CMAKE_CURRENT_BINARY_DIR})
Because the new cmake
process runs in a different context it does not have all
the same variables defined that we have available while building the source
code. To know where the source resides and where the configured file should be
installed we need to pass in respective paths. This is what SRC_DIR
and
BIN_DIR
accomplish.
Finally we need to make sure that the compiler will be able to find the file which we do by including the current binary directory in the search path.
The default behavior of this form of add_custom_target
, without an output file,
is that it will always be considered out of date, hence it'll be invoked every
time we compile.
This way we get up-to-date information about the provenance of every build we make of the software.
I've created a simple demo application and put it on Github. It outputs the following when you compile it:
0GIT_REV is 62b4399
1GIT_TAG is foo
2GIT_BRANCH is main