DevelopingAspirations

Various software development topics.

Sunday, January 20, 2008

Build types, what are they?

You've had this great product idea and thrown together some software along with make files, or perhaps you have convinced someone that your idea is worth funding and now you busy writing code, and there's no time to worry about silly little things you don't need today.

However, attitudes like this have a habit of catching up with you. All too often the problem becomes obvious many months (or more) down the road when your build system is creaking under the load of the unanticipated jobs it is being asked to do.

It is worth while thinking about the different types of builds you are likely to want to perform if your idea turns into a real project or product. Here are some that seem obvious:

  • Developer builds. These are where developers build all or parts of the software in their own sandbox or workspace. These might include changes that have not been checked into Subversion (or whatever revision control system you are using).
  • Daily builds. If you are not doing daily builds you should definitely consider them. At the very least they can quickly tell you that the build is broken, and they facilitate testing of the current code base by the whole team. Some people even run a build for checkin using Subversion's commit hooks, although when commits are happening at a fast and furious pace, this can cause problems.
  • QA/Product builds. You are finally ready to release something to QA, and if all goes well, it might actually be released to customers.
There are other build types that can be considered as well, like special builds, and there can be sub-types, like debug builds, release builds, and so on. However, what I want to write about here is how to support different build types in your make files and include files.

The following snippet of Makefile code shows one way to do this:

ifeq ($(BUILD_TYPE),PRODUCT_BUILD)
ADDED_CFLAGS += -D_PRODUCT_BUILD
ADDED_C++FLAGS += -D_PRODUCT_BUILD
#Set any other product build settings here as well
else
ifeq ($(BUILD_TYPE),DAILY_BUILD)
ADDED_CFLAGS += -D_DAILY_BUILD
ADDED_C++FLAGS += -D_DAILY_BUILD
#Set any other daily build settings here as well
else
#Default to a developer build
ADDED_CFLAGS += -D_DEVELOPER_BUILD
ADDED_C++FLAGS += -D_DEVELOPER_BUILD
endif
endif
With this code in your makefiles, your various build scripts can pass the appropriate strings to make. If your cron job that runs the daily builds you would include something like:

make BUILD_TYPE=DAILY_BUILD

And once you decide to cut a product build, the scripts that handle the process would pass BUILD_TYPE=PRODUCT_BUILD to make as well.

How does this help? Well, your product will probably have a startup banner, perhaps like the following:

MyHotApplication Version 1.1 (svn_change_#)
but at some point it is going to become difficult to keep track of which tests were run with which version and build of your software. Using the build type flags suggested above you can customise the startup banner (and many other things) according to the build type that the application was built with. The following example version.h file suggests one way to do this:

#version.h for MyHotApplication
#
#define VERSION_MAJOR 1
#define VERSION_MINOR 1
#if defined(_PRODUCT_BUILD)
#define VERSION "MyHotApplication V1.1"
#elif defined(_DAILY_BUILD)
#define VERSION "MyHotApplication V1.1(DailyBuild)"
#else
#define VERSION "MyHotApplication V1.1(Developer)"
#endif
Clearly, you wouldn't duplicate all those strings and you would make more use of the various macros available, but since I am pressed for space, I have kept it simple here.

Of course, you probably also want to automatically include revision numbers, but that is another topic.

Putting infrastructure in like this up front costs very little time and will pay enormous dividends over the lifetime of your project.

Any development environment that uses Gnu Make can use this approach (eg, Linux, VxWorks), but the same techniques can be used with other versions of make. They can even be used with nmake and the Microsoft tools, it seems.

Getting subversion revision numbers into your code

Note! I test all the things I post here using Linux and the tools mentioned. Hopefully there are no mistakes.

It is very important during testing, debugging, and even when given an image/program whose provenance you are unsure of, to be able to say what precise revision of the code you are dealing with.

One of the most effective ways of dealing with this is to ensure that your build system passes in the current revision of the code that your system or programs was built with. You can easily find out what revision your current workspace or sandbox contains using a command like svnversion at the top of your workspace:

cd /the/top/of/my/workspace
svnversion -n
1104:1108M
As you can see, svnversion is telling us a number of things about the code in our workspace, since it can print out a lot of information, as the help message for svnversion shows:

4123:4168 mixed revision working copy
4168M modified working copy
4123S switched working copy
4123:4168MS mixed revision, modified, switched working copy
Indeed, it will even print out exported if the current point in your source tree is not under SVN control. (You can obtain more information about Subversion at Version Control with Subversion.)

If you have this information available as a C/C++ preprocessor symbol in your source code, then you can ensure that your system/programs print out the version number it was build from when they start up. This will make your life much easier during testing and while debugging the inevitable problems thrown up by QA as well as, heaven forbid, when bugs are found in the field.

So how do we get this information into our builds? Make comes to our rescue here again. The following piece of make code obtains the current revision number if it is not already defined.

# Grab the current SVN revision number
ifeq ($(svnVersion),)
svnVersion := $(shell svnversion -n .)
export svnVersion
endif
ADDED_CFLAGS += -D_svn_revision=$(svnVersion)
ADDED_C++FLAGS += -D_svn_revision=$(svnVersion)
Now, you might use this in the following way in your include files:

#define STRINGIFY(_str_) #_str_

#define RVN_STRING(_str_) STRINGIFY(_str_)

#define VMJR "1"
#define VMNR "1"
#if _PRODUCT_BUILD
#define BLD_STR ""
#elif _DAILY_BUILD
#define BLD_STR " (Daily Build)"
#else
#define BLD_STR " (Developer Build)"
#endif

#define VREST VMNR BLD_STR" SVN#"RVN_STRING(_svn_revision)
#define VERSION "V"VMJR"."VREST

and then in your system or applications do something like:

printf("%s\n", "MyBigApp " VERSION);
and your application strings will look like:

MyBigApp V1.1 (Developer Build) SVN#218:221M
Clearly, you do not have to use the abbreviated symbols like BLD_STR and VMNR as I did above. Since there are space restrictions with the Blog template I am using I have shortened the symbol names in an effort to prevent example lines from wrapping.

The STRINGIFY trick above is documented in the GCC documentation on Stringification.

As you can see the system was build in a workspace that contained mixed revisions and modified (ie, uncommitted files). If QA or a customer shows you such output from a test you could clearly identify that they do not have an official build, or at least one hopes so.