Monday, September 13, 2010

A Handy Versioning Trick

As I've been setting myself up so that I can develop code at home again - something I couldn't do before because Symantec automatically owned everything I did making personal projects impossible even if I had the emotional energy left after the kinds of high pressure we were basically always under. One of the things that has involved has been setting up all the normal process stuff you really want to have in place these days for everything but the simplest work.

Fortunately there are lots of good tools around to start using now; for source control there's Mercurial which I've been happily using for a while along with BitBucket as a hosted repository which allows private storage (and has reasonable prices) and comes with the usual accoutrements like a wiki and bug-tracker. Joel Spolsky's Kiln+FogBugz is also excellent but the pricing bump from the free tier to paid is a little steep by comparison (which isn't a complaint, it's worth it) and I may use that for side stuff too - the main drawback there is that they don't appear to have OpenID yet.

Building code is where things get more interesting. Thanks to some sterling work by Anuradha Dissanayake who set up a fantastic CM system for Ghost, I've long been a fan of continuous build/continuous integration systems and there's some good automation out there. It's amusing how almost all the main CI tools you can get are Java-based, but fortunately here since most of what they are doing is coordination work - polling the version-control system for work and launching scripts - this is not the problem it would normally be. In fact, they are one of the few kinds of thing which really benefit from Java's famously over-sold "run anywhere" because they don't have classic UIs, aren't innately very complex, but can derive a lot of benefit from dynamic class loading for extensions.

On top of that, some of the CI systems are actually pretty good. Hudson and Teamcity both seem up to the job - I'm going with Hudson to start because TeamCity's supposed great feature is IDE integration which is actually pretty useless to me, and if I ever need to get into writing any Hudson extensions for myself I can. Although really, the only thing it doesn't have right out of the box is support for Windows slave instances on Amazon EC2, only UNIX (well, OpenSolaris or Linux) slave instances, but that's hardly a deal-breaker.

However, one interesting thing I've been looking at today is the irritation of managing build numbers; most of the CI systems have something-or-other to try and do for this, and there are all kinds of obscure work-arounds to install .NET extensions to get build numbers into MSBuild, but it's certainly not exactly seamless and it's nice if developer builds in Visual Studio (even plain old Express) can do this too.

Turns out it's actually pretty easy nowadays, thanks to some long overdue improvements in Visual Studio 2010. The old project and solution build system for C++ code was, frankly, hideous, and so part of the solution has been that Visual Studio now uses the same unified build tool for C++ as has been used in the .NET world, MSBuild. And fortunately, to go with that MSBuild has been helped a lot with the .NET 4.0 release that came with VS2010 in terms of filling in a lot of the basic missing features.

Probably the single neatest thing in MSBuild 4.0 is property functions, which allow build scripts to call into arbitrary parts of the .NET framework and get results as strings which can be used to parameterize the build actions, without having to go to the tedium of writing and building .NET assemblies to extend the build system. With these plus the general improvements to the built-in MSBuild "Tasks" elements, a regular vcxproj file (typically an otherwise empty project of "Utility" type) in Visual Studio can contain something like this inside the outermost <Project> element:

<Target Name="Build">
  <PropertyGroup>
    <Name>$(OutDir)\ver_def.h</Name>
    <Major>1</Major>
    <Minor>0</Minor>
    <Now>$([System.DateTime]::UtcNow)</Now>
    <Year>$([System.DateTime]::Parse($(Now)).ToString("yy"))</Year>
    <Day>$([System.DateTime]::Parse($(Now)).DayOfYear.ToString())</Day>
    <Time>$([System.DateTime]::Parse($(Now)).ToString("HHmm"))</Time>
  </PropertyGroup>
  <MakeDir Directories="$(OutDir)" />
  <WriteLinesToFile File="$(Name)" Lines="#define VER_MAJOR $(Major)" Overwrite="True" />
  <WriteLinesToFile File="$(Name)" Lines="#define VER_MINOR $(Minor)" Overwrite="False" />
  <WriteLinesToFile File="$(Name)" Lines="#define VER_BUILD $(Year)$(Day)" Overwrite="False" />
  <WriteLinesToFile File="$(Name)" Lines="#define VER_REV ;  $(Time)" Overwrite="False" />
</Target>


This is just the simplest kind of example, using hours and minutes as the last number part; one of the tricks to watch for is that each component of the total version number is 16-bit and so limited to 65535. That's why I used the day-of-year after the year (so this won't overflow until 2065) and why another popular build number format is to use half the number of seconds elapsed in the day instead of hours or minutes (unfortunately there are slightly too many seconds per day to use it raw, so we need to divide it down a little):

    <Seconds>$([System.DateTime]::Parse($(Now)).TimeOfDay.TotalSeconds)</Seconds>
    <Half>$([System.Convert]::ToInt32($([MSBuild]::Divide($(Seconds),2))))</Half>


In practice the hours-and-minutes time is more than good enough, since if you're doing your builds through an automated system they aren't going to be scheduled more than one per minute anyway, and the result is slightly easier to interpret. You can also choose to use "Now" instead of "UtcNow" in the example above for simplicity if you aren't particularly worried about the effects of timezones.

If you do that, then there are some nice benefits: Hudson has a plug-in which will post build results to a Google Calendar, so it's simplicity itself to correlate any given binary with the build that produced it just by looking it up in a browser. Of course that's not particularly hard with UTC or seconds-per-day either, but if you're a one-man-band it's your choice to make it easier on yourself.

Note that by overriding the "Build" target in the XML inserted into the vcxproj file, we don't get the normal inherited behaviour for the "Build" target (which comes from some of the imported boilerplate) of doing minimal builds, so this action always runs which is what we want. In a minimal vcxproj file you will still want to have some of the regular IDE crud present just to stop the IDE property editors going haywire, and one advantage of that is that you will get properties like $(OutDir) and friends set the right way if you use property sheets to ensure those are consistent across all your projects (as you definitely should).

Another thing to consider is that this is just one way of using the above data; you can also pick it up in other MSBuild projects and tasks in your overall build for e.g. publishing the build data to a version-dependent location and all kinds of other steps, or write it out in other formats for other tools (the above example is just the first thing I did because I happen to work mostly in C++, the language which has only just joined the MSBuild family in Visual Studio).

And now with this in place I can start to get on with some simple coding.

No comments:

Post a Comment