Compile-time git version info using CMake

Compile-time git version info using CMake

Frank Vanbever
05/09/2024

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:

execute_process(COMMAND git log --pretty=format:'%h' -n 1
  OUTPUT_VARIABLE GIT_REV
  ERROR_QUIET
)

if("${GIT_REV}" STREQUAL "")
  set(GIT_REV "N/A")
  set(GIT_DIFF "")
  set(GIT_TAG "N/A")
  set(GIT_BRANCH "N/A")
else()
  execute_process(
    COMMAND bash -c "git diff --quiet --exit-code || echo -dirty"
    OUTPUT_VARIABLE GIT_DIFF)
  execute_process(
    COMMAND git describe --exact-match --tags OUTPUT_VARIABLE GIT_TAG ERROR_QUIET)
  execute_process(
    COMMAND git rev-parse --abbrev-ref HEAD OUTPUT_VARIABLE GIT_BRANCH)

  string(STRIP "${GIT_REV}" GIT_REV)
  string(SUBSTRING "${GIT_REV}" 1 7 GIT_REV)
  string(STRIP "${GIT_DIFF}" GIT_DIFF)
  string(STRIP "${GIT_TAG}" GIT_TAG)
  string(STRIP "${GIT_BRANCH}" GIT_BRANCH)
endif()

configure_file(
  "${SRC_DIR}/version.h.in"
  "${BIN_DIR}/version.h"
)

We collect the following information:

  • GIT_REV is the current abbreviated commit hash
  • GIT_DIFF will contain the string -dirty if the tree from which it was built is dirty
  • GIT_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.

#ifndef VERSION_H
#define VERSION_H

#define GIT_REV "@GIT_REV@@GIT_DIFF@"
#define GIT_TAG "@GIT_TAG@"
#define GIT_BRANCH "@GIT_BRANCH@"

#endif /* VERSION_H */

Triggering the generation of this file is in my opinion most easily accomplished by adding a target for it:

add_custom_target(gen-version-h
  COMMAND "${CMAKE_COMMAND}"
  "-D" "SRC_DIR=${PROJECT_SOURCE_DIR}/src"
  "-D" "BIN_DIR=${CMAKE_CURRENT_BINARY_DIR}"
  "-P" "${PROJECT_SOURCE_DIR}/support/version.cmake"
  COMMENT "Generating git version file"
)

add_dependencies(my-application gen-version-h)
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:

GIT_REV is 62b4399
GIT_TAG is foo
GIT_BRANCH is main

Presentations

Drop the docs and embrace the model with Gaphor Fosdem '24 - Frank Van Bever 20 March, 2024 Read more
How to update your Yocto layer for embedded systems? ER '23 -Charles-Antoine Couret 28 September, 2023 Read more
Tracking vulnerabilities with Buildroot & Yocto EOSS23 conference - Arnout Vandecapelle 12 July, 2023 Read more
Lua for the lazy C developer Fosdem '23 - Frank Van Bever 5 February, 2023 Read more
Exploring a Swedish smart home hub Fosdem '23 - Hannah Kiekens 4 February, 2023 Read more
prplMesh An Open-source Implementation of the Wi-Fi Alliance® Multi-AP (Arnout Vandecappelle) 25 October, 2018 Read more

 

News