Tracing binaries back to archived sources is a necessity as a programmer. When writing software there is generally a process for releasing software where the binaries and accompanying source code are archived. Using version control it is fairly easy to tag some specific snapshot of the source code so it can be linked (or include) the resulting binary. However this formal release system doesn’t work as well during development. Releases are often lengthy processes not conducive to rapid development and debug.
One system I have been using since the early 2000s is an automated build number. Each time the source code is compiled, a build number is incremented. As an embedded developer the software usually cannot be directly identified, but since the build number is increased each compile it is generated to be unique (even if two builds are otherwise identical). This build number is accessible from the outside world and thus pinpoints exactly what software a controller is running. In addition I archived every binary ever produced so that if a problem was found on a unit under test I could duplicate the setup. That was important for a small company where software was constantly changing and tests constantly being run. Devices in the field were better controlled, but development was not.
When I started using version control more I kept the build number concept. With Subversion I was able to link in an ID related to the version in the repository. It required software first be fully checked into version control before compiling binaries but worked well enough. There was no good way to extend this functionality to Git. So I fell back on the build number concept.
Recently I discovered what I think is the best incarnation of the build number. I tend to use make files for compiling, and this section of code takes care of build numbers.
BUILD_NUMBER_HEADER = buildNumber.h BUILD_NUMBER_FILE = buildNumber.txt … $(BUILD_NUMBER_FILE): $(cFiles) $(filter-out ./$(BUILD_NUMBER_HEADER), $(hFiles)) $(sFiles) @read lastNumber < $(BUILD_NUMBER_FILE); \ newNumber=$$(($$lastNumber + 1)); \ echo "$$newNumber" > $(BUILD_NUMBER_FILE) # Create the build number header file. # Increments the build number and places this in the header. $(BUILD_NUMBER_HEADER): $(BUILD_NUMBER_FILE) @read buildNumber < $(BUILD_NUMBER_FILE); \ export BUILD_DATE=`stat --format=%y $(BUILD_NUMBER_FILE) | cut -c 1-23`; \ echo "#ifndef _BUILDNUMBER_H_" > $(BUILD_NUMBER_HEADER); \ echo "#define _BUILDNUMBER_H_" >> $(BUILD_NUMBER_HEADER); \ echo "" >> $(BUILD_NUMBER_HEADER); \ echo "#define BUILD_NUMBER $$buildNumber" >> $(BUILD_NUMBER_HEADER); \ echo "#define BUILD_DATE \"$$BUILD_DATE\"" >> $(BUILD_NUMBER_HEADER); \ echo "" >> $(BUILD_NUMBER_HEADER); \ echo "#endif // _BUILDNUMBER_H_" >> $(BUILD_NUMBER_HEADER); \ echo "" >> $(BUILD_NUMBER_HEADER);
There is a file called buildNumber.txt which contains a number that is incremented when the source code changes. There is a header file included by something in the project called buildNumber.h. This contains two defines that have the current build number, and a time stamp of when the compilation took place. The header file is auto-generated. It can be left out of the main branch of the repository, but should be included in branches for tagged releases. The build number file must be checked into the repository for both trunk and branches.
What is going on in the make file two rules. The first is to regenerate the build number text file. It depends on all the source code (less the build header). Thus, should any source file change, the build header must be recompiled. The second rule is for the build header file. It must be regenerated if the build number text file changes. So any change to source code causes a new build header file to be created. As long as the dependencies for the build header are properly handled this system will always ensure a unique build number when the software changes. However, unlike my previous system, the build number will not change if the software has not changed. This allows source code in a repository to be exactly reproduced. As long as source code was checked in, binaries can be traced back to source code and reproduced from source code.
Generally I use a universal Makefile for my projects. It simply compiles all C files and computes dependencies automatically. There was some configuration parameters such as the name of the resulting executable, but for the most part the Makefile can be dropped into any new project and with minimal change compile any project.