Posted on :: Tags: , , , , ,

In programming, versioning your code files is of immense importance. Most files need to be constantly updated, renamed, and merged. You also need backups, as everyone learns after losing work due to various computer problems.

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

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

While developing the C library for dervaze, I wanted to add descriptive versioning information, as the library will also contain wordlists, and more words will be added over time.

In CMake, it’s possible to set versioning information and supply it through compiler options. It’s also possible to replace strings formatted as @CHANGE_THIS@ in source files. To supply version information to the executable, you can 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 is named 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 the build by using ${PROJECT_BINARY_DIR}/version.h as the second argument, but in my experience, keeping such a file in the source directory is sometimes needed by build tools. When you keep it in the source directory, it’s better to ignore the generated version.h by adding it to .gitignore.