Adding version information to executables in CMake projects

Posted on Cum 16 Şubat 2018 in new • 2 min read

In programming, versioning your code files are of immense importance. Most of the files needs to be updated, renamed, merged constantly. You also need backups, as one learns through losing work due to various computer problems.

Another problem that we face is establishing a connection between an executable file or library to its code. We normally don't add executable files to the version control, as they are produced from code files. A solution to this is writing version information to About page or something similar.

When I was using Subversion some 15 years ago, I'd create hooks to change the code files for necessary versioning info, but this is not a recommended way in Git because of its distributed nature. I never tried but it probably creates more problems than it solves. The recommended way is to use build system's facilities to retrieve the versioning info and add it to necessary places.

While developing the C library for dervaze, I wanted to add a descriptive versioning info as the library will also contain the word lists and more words will be added in time.

In CMake, it's possible to set versioning info and supply this through compiler options. It's also possible to replace strings formed as @CHANGE_THIS@ in files. In order to supply version information to the executable, it's possible to use these facilities:

set (DERVAZE_VERSION_MAJOR 1)
set (DERVAZE_VERSION_MINOR 0)
string(TIMESTAMP DERVAZE_TIMESTAMP "%y%m%d%H%M%S")
# current branch
execute_process(
  COMMAND git rev-parse --abbrev-ref HEAD
  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
  OUTPUT_VARIABLE DERVAZE_GIT_BRANCH
  OUTPUT_STRIP_TRAILING_WHITESPACE
)

# abbreviated commit hash
execute_process(
  COMMAND git log -1 --format=%h
  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
  OUTPUT_VARIABLE DERVAZE_GIT_COMMIT_HASH
  OUTPUT_STRIP_TRAILING_WHITESPACE
)

This information can be supplied to the C files by creating a header file that will be used as a template.

#define DERVAZE_VERSION_MAJOR       "@DERVAZE_VERSION_MAJOR@"
#define DERVAZE_VERSION_MINOR       "@DERVAZE_VERSION_MINOR@"
#define DERVAZE_TIMESTAMP           "@DERVAZE_TIMESTAMP@"
#define DERVAZE_LIB_GIT_BRANCH      "@DERVAZE_GIT_BRANCH@"
#define DERVAZE_LIB_GIT_COMMIT_HASH "@DERVAZE_GIT_COMMIT_HASH@" 

Suppose this file named as version.h.in, the following command creates the actual version.h for each build.

# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/version.h.in"
  "${PROJECT_SOURCE_DIR}/version.h"
  )

It's also possible to write this file only during build, by using ${PROJECT_BINARY_DIR}\version.h as the second line, but in my experience keeping such a file in the source directory is needed by the build tools. When you keep it in the source directory, it's better to ignore this produced version.h by adding it to .gitignore.