Easily embed version information in software releases

Quite a few projects hardcode metadata in a file (or C/C++ headers), e.g. version.h with version info or defines. This can cause small problems when developers forget to update the version information inside these files. It is a lot easier to keep track of version information if your project uses git and makes use of tagging.

This is one possible versioning scheme used for C/C++ releases. It depends on Make, a build automation tool, but the idea can be easily extended to other build tools such as CMake. Git is heavily used due to the popularity of this source control management system.

The idea is to utilise git tags inside makefiles when building software for release. We can define these tags/other information as a preprocessor macro and use the macro throughout our code. A standard define would look like #define VERSION "1.0.0", however gcc can directly embed defines while compiling, e.g. gcc main.c -DVERSION\="1.0.0\" -o main. This removes the need for a static #define preprocessor macro inside the code.

The following information is obtained & placed in Makefile variables, which are then passed as compiler flags (CFLAGS).

  • Git version, obtained from the latest tag e.g. v1.0.1
  • Specific commit of the latest tag, e.g. 19c31278502e5693f5b530e798d10e10893090d7
  • Latest tag commit date as ISO-8601 valid date, e.g. 2016-09-29T20:13:34+13:00
  • Build date, e.g. 2016-09-29T21:04:55+13:00

C Code example

#include <stdio.h>

int main()
{
 printf("GIT VERSION - %s\n", GIT_VERSION);
 printf("GIT COMMIT  - %s\n", GIT_COMMIT);
 printf("GIT DATE    - %s\n", GIT_DATE);
 printf("BUILD DATE  - %s\n", BUILD_DATE);

 return 0;
}

Example output

GIT VERSION - v1.0.1
GIT COMMIT  - 19c31278502e5693f5b530e798d10e10893090d7
GIT DATE    - 2016-09-29T20:13:34+13:00
BUILD DATE  - 2016-09-29T21:04:55+13:00

The makefile magic that makes this possible is a combination of shell code, git commands & makefile-specific manipulation as shown below:

GIT_VERSION := $(shell git --no-pager describe --tags --always)
GIT_COMMIT  := $(shell git rev-parse --verify HEAD)
GIT_DATE    := $(firstword $(shell git --no-pager show --date=iso-strict --format="%ad" --name-only))
BUILD_DATE  := $(shell date --iso=seconds)

# If working tree is dirty, append dirty flag
ifneq ($(strip $(shell git status --porcelain 2>/dev/null)),)
 GIT_VERSION := $(GIT_VERSION)-D
endif

CFLAGS := -Wall -Wextra -pedantic -ansi -O\
 -DGIT_VERSION=\"$(GIT_VERSION)\"\
 -DGIT_COMMIT=\"$(GIT_COMMIT)\"\
 -DGIT_DATE=\"$(GIT_DATE)\"\
 -DBUILD_DATE=\"$(BUILD_DATE)\"

The full source is available here, and is recommended reading to fully understand how this is performed as the entire Makefile is not described in this post.

Chronological breakdown of version changes

A developer is working on a project that is currently v1.0.1, they decide to make 5 commits on top of v1.0.1 which leads to a version string of v1.0.1-5-g19c3127. After this, some unstaged/uncommitted files are present in the working tree. This leads to the dirty flag being applied for a version string of v1.0.1-5-g19c3127-D. After a period of time, they decide to commit these changes and base a new version v1.0.2 on the most recent commit.

The current head of my “parent” branch is based on v1.0.1, but since it has a few commits on top of that, describe has added the number of additional commits (“5”) and an abbreviated object name for the commit itself (“19c3127”) at the end.

The number of additional commits is the number of commits which would be displayed by “git log v1.0.2..parent”. The hash suffix is “-g” + 7-char abbreviation for the tip commit of parent (which was 19c3127b194453f058079d897d13c4e377f92dc6). The “g” prefix stands for “git” and is used to allow describing the version of a software depending on the source control management (SCM) the software is managed with. This is useful in an environment where people may use different SCMs.

Paraphrased from git describe documentation

Ideally, only stable releases (e.g. v1.0.1, v1.0.2) would be publicly released. This versioning scheme assumes development releases being provided to Q&A testers, which is why the abbreviated SHA-1 commit & dirty flag show up in some version strings.

External Resources