TrialBalance Build System
Notice that I spelled TrialBalance as one word, rather than “Trial Balance“? I’ve decided to standardise on “TrialBalance”, because if people see something like “What is Trial Balance?”, and don’t realise that it’s the name of a product, they’ll think I have poor grammar. Although, I guess this way they could also just assume that I have bad grammar and my “space” key is broken
Tonight I wanted to talk about the build system for TrialBalance. A few weeks ago I decided that since TrialBalance is designed to replicate the development of a real product, I thought it was only fitting that it should also be unit-tested and built the way that I think a real product should be. So on and off for the past few weeks I’ve been working with MSBuild and the Team Foundation Server “Team Build” system to come up with a cool way to manage my build process for TrialBalance.
For a few months now, the source code for TrialBalance has been hosted inside of Readify’s Team Foundation Server. With the help of Chris and Mitch, I’ve also configured it to use the Readify TFS Integrator.
If you haven’t heard of TFS Integrator, it’s a free tool that Readify (mostly Chris) built. In some ways it’s similar to CruiseControl.NET, in that it receives check-in notifications from a Team Foundation Server source code repository, and then triggers a build of your application. It also has some cool dependency replication features which I do use in TrialBalance, but that’s another story for another day.
When you setup a build in TFS, you run through a little wizard which generates the .proj file. The wizard lets you choose which solution you’d like to build, whether to build it in Debug or Release mode (or both). You can also configure FXCop analysis and unit testing. But I wanted a little more than that…
What I wanted, I suppose, was a full release-management system. Here’s the steps I wanted the process to take:
- Get the latest code from TFS
- Get a unique version number for this build
- Replace all AssemblyInfo version numbers with this version number
- Remove all source-control bindings from the code
- Zip the code
- Compile it (in Debug as well as Release mode)
- Run all of the unit tests (debug and release)
-
Upload the zipped source code, release notes and (later) MSI installer to
http://www.trialbalance.net.au/Builds.aspx
And I wanted this process to happen every time I check something in.
The bold items above are the ones TFS build didn’t support “out of the box”. That’s fine, because those are pretty specialised and would probably be unique to every user, so I’m glad Microsoft didn’t waste time implementing them. However, it did mean I had to get my hands dirty!
Team Build files compared to MSBuild files
The first hurdle that I had to get over was “where do I call tasks?”
I was told that TFS generates MSBuild files. However, if you look at the documentation for an MSBuild file, you’ll see that they are supposed to contain a bunch of “<target ../>” elements where everything happens, and this is where you’re supposed to put tasks to execute.Â
Yet, if you look at a generated build file from TFS, you won’t see any mention of the word “target”. However, there are lots of elements in the generated files that were not listed in the documentation link above. At this stage I was feeling very confused, and so I went to the Readify “brains trust” (our tech mailing list) for help. Grant Holliday and Chris Burrows answered my question, by showing me that there’s actually some pretty funky “build-inheritance” going on.
Take a look at this file (if you have Visual Studio installed you should have it):
C:\Program Files\MSBuild\Microsoft\VisualStudio\v8.0\TeamBuild\Microsoft.TeamFoundation.Build.targets
You’ll notice that it’s actually an MSBuild file that fits the description of a real build file. If you go back and look at the generated build file from TFS, you’ll see this:
<Import
Project=“$(MSBuildExtensionsPath)MicrosoftVisualStudiov8.0TeamBuildMicrosoft.TeamFoundation.Build.targets”
/>
As I said before, an MSBuild file can contain multiple targets. Since your TFS build file has automatically included this default build file, you’ve “inherited” its targets. When TFS kicks-off a build, it will actually execute your build file, but instead of executing a target that you define, it will execute the (I believe) “EndToEndIteration” target, which is defined in that base build file instead of your build file.
So, since you don’t have control of the target that TFS is executing, where do you add custom tasks?
The answer, my friend, lies in a bunch of “extension points” that are defined in that base build file. If you have a look inside that file, you’ll see all kinds of empty targets declared. The idea is that you “override” these targets to perform your own custom actions. Here’s a list (thanks to Chris) of some of the targets you can override:
- BeforeClean
- AfterClean
- BeforeGet
- AfterGet
- BeforeLabel
- AfterLabel
- BeforeCompile
- AfterCompile
- BeforeTest
- AfterTest
- BeforeDropBuild
- AfterDropBuild
- BeforeOnBuildBreak
- AfterOnBuildBreak
So to print “Paul Rules!” just before compiling, you’d simply add this to your generated .proj file:
<Target Name=“BeforeCompile”> <Message Text=“Paul Rules!” /> </Target>
Zipping the Code
The first thing I wanted to check was whether I would be able to zip the source code, which turned out to be very easy. While MSBuild doesn’t provide an inbuilt task for zipping files or directories, there are a number of community projects out there that are dedicated to creating additional tasks that you can use in TFS.
I ended up using the Zip task from the “MSBuild Community Tasks Project”. You can find the download for these tasks here:
http://msbuildtasks.tigris.org/
But here’s a question - how do you install and use custom tasks?
If you look at the downloads page for the MSBuild Community Tasks Project, you’ll see that they provide an MSI file to install the tasks. Creating installers for tasks seems to be pretty common, but I had a problem.
Most of the installers for third-party tasks seem to want to install to the $(MSBuildExtensionsPath), which (I think) seems to be somewhere in C:\Program Files\MSBuild. This means that if you download and install a custom task to this directory, you’ll be sharing that task with everyone who uses your machine.
While this may work for some projects, TrialBalance is just one of many projects on the Readify build servers. If I installed version 1.0 of the tasks, and then Mitch installed version 2.0 of the same tasks, he might break my build. Vice-versa, I might install version 1.0 over Mitch’s version 2.0, which would be even worse, because I’m much more scared of Mitch than he is of me
It would also mean that if one day a meteorite flattened Melbourne (where our build machines are located), it would be a terrible event, because unless we had backups of the C:\ drives of our build boxes, we’d have no way of knowing what third-party tasks we installed, and it would be very difficult to recreate a build.
What I ended up doing was creating an “Includes” directory inside my TFS source control repository, under the directory that the TFS build file was stored in (in my case, this was $/TrialBalance/TeamBuildTypes/ContinuousIntegration).
I then downloaded the “Binaries” zip file from the MSBuild Community Tasks Project. I then unzipped the DLL's, PDB's and .Targets files from the zip file into my “Include\” directory.
The next step was to alter the .Targets file. If you open the .Targets file that comes with the MSBuild Community Tasks Project (and many other third-party .Targets files), you’ll see something similar to this:
<MSBuildCommunityTasksPath
Condition=“‘$(MSBuildCommunityTasksPath)’ == ””
>
$(MSBuildExtensionsPath)BlahBlahBlah
</MSBuildCommunityTasksPath>
As I said before, this will be treated as “C:\Prog…”, which isn’t what I wanted. Since I was storing the build files inside of my TFS repository, I needed to make it a relative path. To do this, I simply changed the line above to read:
<MSBuildCommunityTasksPath
Condition=“‘$(MSBuildCommunityTasksPath)’ == ””
>
.
</MSBuildCommunityTasksPath>
Then in my TFSÂ Build .proj file, I simply added:
<Import
Project=“IncludeMSBuild.Community.Tasks.targets“
/>
Then I was finally able to make use of the Zip task. I thought it would be best to zip the code before compiling, so that none of the obj and bin directories would be there. To do this, I overrode the BeforeCompile target:
<Target Name=“BeforeCompile“> <CreateItem Include=“$(SolutionRoot)Application***.*“> <Output TaskParameter=“Include” ItemName=“AllSourceCodeFiles” /> </CreateItem> <Zip Files=“@(AllSourceCodeFiles)” WorkingDirectory=“$(SolutionRoot)Application” ZipFileName=“$(DropLocation)$(BuildNumber)TrialBalance-$(BuildNumber)-Source.zip” /> </Target>
And that’s how you zip source code using MSBuild/Team Build and the MSBuild Community Tasks Project
I’m going to leave it at that for tonight because it’s getting late. Tomorrow I’ll explain how I removed source control bindings, and how I handled generating version numbers.
To summarise, some of the points:
- Team Build provides a number of “extension points” as targets that you override. This is a powerful solution that gives you a lot of flexibility, and saves you from having to implement all the plumbing and TFS UI integration yourself.
- There are a couple of third-party tasks out there that you can “leverage”. Take the time to look around so you don’t reinvent the wheel. The MSBuild Community Tasks Project and UK SDC tasks are probably the most popular ones I’ve seen around.
- You aren’t limited to putting the DLL’s and Targets files for tasks in their default location. I chose to store mine in source control to isolate them from the rest of the projects on our TFS server, and to make it easy to reproduce the exact same build over time.
In the mean time, before you click on to your next RSS feed, take a few moments to head over to the TrialBalance online build page. You’ll find the download for the latest stable build, along with downloads for every other continuous build that has been created since last night. There’s even an RSS feed that you can subscribe to be notified when new builds become available
How does your build process work?
Filed under: Trial Balance

Be sure to read Part II:
http://www.paulstovell.net/Posts/Post.aspx?postId=bfac1a9f-6e88-48f1-870f-6364e278ff3f