Giving Your Firmware Build a Version

Every software product needs a version number. In the early stages of development it may seem excessive - everything is changing rapidly and there are no customers receiving releases. Eventually the software makes it out into the wild and the need to differentiate between releases becomes important.

In this guide, I describe the details I like to store in each build, how I capture these details, and how I make them available to the firmware.

What to Capture

Here are the details I like to log for each build:

  • Time/date of build
  • Machine that built it
  • Version (git tag number)
  • Number of commits away from the last tag
  • Commit hash
  • Dirty or clean build (dirty = local changes that have not been committed)

With this set of information, I get a full picture of the build's history and can answer questions such as:

  • Was the build created by the server, or locally by Joe Smith?
  • Is it a clean build, or are there changes that weren't committed?
  • Is it a tagged build, or are there extra commits since the last build?

Timestamp

The easiest way to get the timestamp is to run the date shell command:

$ date
Thu Oct 27 10:27:40 PDT 2016

To include the string in a Makefile:

-DVERSION_BUILD_DATE=\""$(shell date)"\"

Build Machine

For build machine details, I utilize the whoami and hostname commands:

$ whoami
jenkins
$ hostname
buildbot

I take these two values and combine them into a single string:

jenkins@buildbot

To include the string in a Makefile:

-DVERSION_BUILD_MACHINE=\""$(shell echo `whoami`@`hostname`)"\"

Version

Conveniently, you can get all of the version information directly from git:

$ git describe --long --dirty --tags
 0.1.64-2-gb27efef

Above, 0.1.64 is the latest version tag, 2 indicates that I am two commits ahead of the last versioned build, and the commit hash is at the end.

The simplest approach is to simply pass the entire git describe output into your version string, but I think it makes your version more confusing to customers, vendors, and CMs, especially if you just want to display "0.1.64" alone as a marketing version. I store the following values separately:

  • BUILD_TAG (0.1.64)
  • COMMIT (gb27efef)
  • COMMITS_PAST (2)
  • DIRTY_FLAG (+ if dirty, empty if clean)

Here's some example Makefile code to make these values available:

version := $(subst -, ,$(shell git describe --long --dirty --tags))
COMMIT := $(strip $(word 3, $(version)))
COMMITS_PAST := $(strip $(word 2, $(version)))
DIRTY := $(strip $(word 4, $(version)))
ifneq ($(COMMITS_PAST), 0)
    BUILD_INFO_COMMITS := "."$(COMMITS_PAST)
endif
ifneq ($(DIRTY),)
    BUILD_INFO_DIRTY :="+"
endif

export BUILD_TAG :=$(strip $(word 1, $(version)))
export BUILD_INFO := $(COMMIT)$(BUILD_INFO_COMMITS)$(BUILD_INFO_DIRTY)

This will result in:

Build version: 0.1.64
Build info: gb27efef.2

If the build was dirty, you'd see this instead:

Build info: gb27efef.2+

Getting Version into Source

Now that we have all the information we want to include for each build available in MAKE variables, we can pass this information to the compiler using compiler defintions.

In your makefile, create a set of definitions for your version library and add them to your CFLAGS:

CFLAGS += -DVERSION_BUILD_DATE=\""$(shell date)"\" \
          -DVERSION_BUILD_MACHINE=\""$(shell echo `whoami`@`hostname`)"\" \
          -DVERSION_TAG=\"$(BUILD_TAG\" \
          -DVERSION_BUILD=\"$(BUILD_INFO)\"

Then, in your source code, simply refer to these definitions as you normally would:

void version_print()
{
    printf("%s %s %s (%s)\n", VERSION_BUILD_DATE, VERSION_BUILD_MACHINE, VERSION_TAG, VERSION BUILD);
}

And voila! you have a version available.

So how do I generate my version automatically?

If you're at the stage where you need version builds, you should also be using a CI server such as Jenkins to build your software. These automated build tools will usually give each build a unique version, and you can utilize it in your build process.

I create two parameteric values for my builds:

  • MAJOR_VERSION
  • MINOR_VERSION

When combined with the Jenkins BUILD_NUMBER variable, you can form your typical version triple by combing them:

${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_NUMBER}

Resulting in a string like:

1.2.48

The versioning strategy outlined in the article above is anchored to git tags. Prior to running the build, I make a temporary local tag:

git tag -a -f -m "Candidate build: ${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_NUMBER}" ${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_NUMBER}
make rtos
git tag -d ${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_NUMBER}

By creating an annotated tag before the build, git describe will work as expected and return a clean "0.1.64" instead of "0.1.63-8". After the build succeeds, this tag will be pushed to the host and available for the next build:

git tag -a -f -m "Successful nightly build ${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_NUMBER}" ${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_NUMBER}
git push origin ${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_NUMBER}

Is there a simpler approach without git tags?

You can pass the ${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_NUMBER} variables into your make command and utilize those definitions directly:

make rtos BUILD_TAG=${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_NUMBER}

And:

CFLAGS += -DVERSION_TAG=\"$(BUILD_TAG)\"

I always recommend pushing a tag for successful builds. Having each build tagged allows for easier debugging and investigation.