TrialBalance Build System - Part III
Over the past couple of days I’ve been writing about the build system for TrialBalance. In part one, I talked about the initial learning curve for Team Build, and explained how I used the Zip task from the MSBuild Community Tasks Project. In part two, I explained how I automated the removal of source control bindings thanks to a task by Chris Burrows (which you can download here). Â
To recap, here’s the list of things I wanted to do. The ones in bold were things that aren’t inbuilt in TFS and I had to do myself, and the ones with a line through them are what I’ve already blogged about.
- 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
Tonight I want to talk about something that was quite fun for me - versioning. While the topic of versioning is very large, I’m only going to concentrate here on how I generate a version number, and how I stamp the build with the version number.
When it comes to controlling version numbers on assemblies in .NET, most of us would be pretty familiar with the AssemblyVersionAttribute. By default, Visual Studio places this in an AssemblyInfo.cs file and it looks something like this:
[assembly: AssemblyVersion(“1.0.*”)]
The asterisk tells Visual Studio to auto-generate the remaining sections of the build number. You can read more about how build numbers are generated in this code project article.
The problems with generated build numbers, for me, were:
- They can often be different for each project in a solution - I wanted them to be the same.
- They can’t be “extracted”. For example, if I wanted the version number in my Zip file (e.g.,
TrialBalance-1.0.1234.4567.zip), it would be difficult (though possible with reflection) to retrieve this number from the compiled projects.
My plan for generating version numbers looks like this:
The Web Service
The web service is hosted on the TrialBalance website. I created a custom MSBuild task that will contact the web service, and ask it for a unique build number.
The build number is maintained in a SQL Server 2005 database. I simply have a BuildSettings table, and a couple of stored procedures:
create schema [TrialBalanceBuildSystem]
go
create table [TrialBalanceBuildSystem].[BuildSettings] (
[SettingName] varchar(50) not null
constraint [PK_Settings_SettingName] primary key clustered,
[SettingValue] varchar(50) not null
)
go
create procedure [TrialBalanceBuildSystem].[GetNextVersionNumber] (
@nextMajorVersion smallint out,
@nextMinorVersion smallint out,
@nextBuildVersion smallint out,
@nextRevisionVersion smallint out
)
as
begin
— The major and minor build numbers come from the
— Settings table (use 1.0 asdefault)
declare @nextMajorVersionString varchar(50)
declare @nextMinorVersionString varchar(50)
execute [TrialBalanceBuildSystem].[GetSetting]
‘Build.NextMajorVersion’, @nextMajorVersionString out, ‘1′
execute [TrialBalanceBuildSystem].[GetSetting]
‘Build.NextMinorVersion’, @nextMinorVersionString out, ‘0′
set @nextMajorVersion = convert(smallint, @nextMajorVersionString)
set @nextMinorVersion = convert(smallint, @nextMinorVersionString)
— The build number is based on the number of days
— since Jan 01 2000 (as per Microsofts recommendation)
set @nextBuildVersion = convert(smallint,
datediff(day, ‘2000-01-01′, getdate()))
— And the revision will be unique
declare @nextRevisionVersionString varchar(50)
execute [TrialBalanceBuildSystem].[GetSetting]
‘Build.NextRevisionVersion’, @nextRevisionVersionString out, ‘0′
set @nextRevisionVersion = convert(smallint,
@nextRevisionVersionString) + 1
execute [TrialBalanceBuildSystem].[SaveSetting]
‘Build.NextRevisionVersion’, @nextRevisionVersion
end
go
Note: GetSetting and SaveSetting are simply stored procedures that get and set settings in the Settings table.
You’ll recall that version numbers in .NET are made up of the following components:
- Major
- Minor
- Build
- Revision
My major and minor build numbers are also stored in the Settings table. They default to ‘1.0′, but if I login to the website I can change these. By changing the major and minor number on a web page, the new numbers are automatically used in the next build. Easy
The build number is just based on the number of days since Jan 01, 2000. The revision number is a number that just keeps on incrementing, forever and ever and ever. This is a little different to auto generated revision numbers in Visual Studio, which are based on the number of seconds in the day.
Doing this means that I know my version numbers will always be sequential, and by getting them all from one place I can control what the next number should be.
My web service looks like this:
[WebService(Namespace = “http://trialbalance.net.au/”)] publicclassBuildService : WebService { [WebMethod(EnableSession=true)] publicstringGetNextBuildNumber(string username, string password) { Authenticate(username, password); return DataProvider.Instance.GetNextBuildNumber(); } privatevoid Authenticate(string username, string password) { if (!FormsAuthentication.Authenticate(username, password)) { thrownew AuthenticationException(); } } }
Note: The DataProvider.Instance.GetNextVersionNumber call just executes the stored procedure and returns a string containing the next version number.
Because the web service is on a remote site which I don’t have much control over (it’s a shared host), I decided to use Forms Authentication to check that the caller is trusted (after all, I don’t want just anyone using up my version numbers :)).
If you were doing something similar on a LAN or corporate network, I’d suggest you use Windows Authentication and simply have the client pass the DefaultCredentials to the call. This saves having to store usernames and passwords in configuration files (both on the server, and on the client).
The MSBuild Task
To call the web service, I created a custom MSBuild task. This was the first time I’d created a custom MSBuild task, but it was surprisingly quick and easy.
First I created a new DLL project in Visual Studio, and added the following references:
-
Microsoft.Build.Engine -
Microsoft.Build.Framework -
Microsoft.Build.Tasks -
Microsoft.Build.Utilities
You should find all of these in your .NET Framework directory (i.e., C:\Windows\Microsoft.NET\Framework\v2.0.50727), although they should also be in the Global Assembly Cache so you shouldn’t have to browse for them.
My task class then looked like this:
public class GetNextVersionNumberTask : Task {
privatestring _webServiceUrl =
“http://www.trialbalance.net.au/Services/UploadBuildService.asmx”;
private Version _nextVersion = new Version(“0.0.0.0″);
privatestring _username;
privatestring _password;
publicstring WebServiceUrl {
get { return _webServiceUrl; }
set { _webServiceUrl = value; }
}
[Required]
publicstring Username {
get { return _username; }
set { _username = value; }
}
[Required]
publicstring Password {
get { return _password; }
set { _password = value; }
}
[Output]
publicstring NextVersion {
get { return _nextVersion.ToString(); }
}
[Output]
publicstring NextMajor {
get { return _nextVersion.Major.ToString(); }
}
[Output]
publicstring NextMinor {
get { return _nextVersion.Minor.ToString(); }
}
[Output]
publicstring NextBuild {
get { return _nextVersion.Build.ToString(); }
}
[Output]
publicstring NextRevision {
get { return _nextVersion.Revision.ToString(); }
}
publicoverridebool Execute() {
BuildServiceProxy.BuildService service =
new BuildServiceProxy.BuildService();
service.Url = this.WebServiceUrl;
string versionNumber = service.GetNextBuildNumber(this.Username,
this.Password);
_nextVersion = new Version(versionNumber);
returntrue;
}
}
Note: BuildServiceProxy is what I named the web service reference when I added it to the project via “Add Web Reference…”.
Allow me to explain how some of this works.
When I was first planning how this system would work, the question I kept asking myself was “How do I return a value from a task?“. If you imagine calling a task via XML:
<Echo Message=“Hello world!” />
Sure, you can pass parameters in via attributes, but how do you pass values out?
The answer comes down to output parameters. If you scroll up, you’ll see a number of properties (such as NextRevision) which are decorated with the [Output] attribute. After the task is executed you can read these values, which I’ll demonstrate in a moment.
I modified the .proj file for my build by adding:
<UsingTask AssemblyFile=“IncludeUploadBuildGetNextVersionNumberTask.dll” TaskName=“PaulStovell.GetNextVersionNumberTask” />
Then, I added a custom property to the build file to use to store the version number:
<PropertyGroup>
<CustomVersionNumber>0.0.0.0</CustomVersionNumber>
</PropertyGroup>
Then I called the task as part of my BeforeCompile target override (you’ll remember those from my first post):
<GetNextBuildVersionTask
Username=“MSBuildPublisher”
Password=“password”>
<Output TaskParameter=“NextVersion”PropertyName=“CustomVersionNumber” />
</GetNextBuildVersionTask>
The way you handle return values in MSBuild is by putting one or more <Output .../> elements inside a task call. The TaskParameter attribute says which property of the task should be read (you’ll recall that “NextVersion” was a property on the custom task I created), and the PropertyName attribute defines which property to store it in.
Updating the AssemblyInfo.cs files
The last thing I had to do was to update the AssemblyInfo files in my projects as part of the build, so that the project would be built using my custom version number.
There were two ways I could have done this:
- By using the
FileUpdatetask in the MSBuild Community Tasks Project to do a “replace” on the existing AssemblyInfo files. - By using the
AssemblyInfotask, also in the MSBuild Community Tasks Project.
The AssemblyInfo task would have required me to define the properties of my AssemblyInfo.cs files inside of my MSBuild project, and I would have had to do a separate one for each project.
Instead, I opted for the lazy route, and decided to take the first option - the FileUpdate task. Again, I did all of this inside the BeforeCompile task.
First I needed to make a list of all the AssemblyInfo.cs files in the solution:
<CreateItem
Include=“$(SolutionRoot)Application**AssemblyInfo.cs“>
<Output TaskParameter=“Include” ItemName=“AssemblyInfoFiles“ />
</CreateItem>
Then I passed those files as an argument to the FileUpdate task:
<FileUpdate
Regex=“(d+).(d+).(d+).(d+)”
ReplacementText=“$(CustomVersionNumber)”
Files=“@(AssemblyInfoFiles)”
/>
When I checked in my modified project file and tried to run it, I got an error during the build process. After a bit of investigation I discovered why: because the source code had been recently gotten from TFS, the files were all marked as read-only. Aha!
To make the files writeable, I made use of the Attrib task, also in the MSBuild Community Tasks Project. Again, this was quite easy. Before calling the FileUpdate task, I inserted this:
<Attrib Files=“@(AssemblyInfoFiles)” ReadOnly=“false” />
And we’re done.
In my next post I’ll extend the web service to upload the source code and build details to the TrialBalance builds page.
Filed under: Trial Balance

Hi Srdjan,
The problem is I wanted to get the entire version number from one place. I could probably have gotten the revision number from TFS (which is a good idea now that you mention it), but I’d still want to get the rest of the version number (major, minor, build) from somewhere else.
My only concern with using the revision number from TFS is that changeset numbers increase for the whole TFS respository, not just a particular project. This means if I did a checkin and the revision was 101, then later I did a checkin the number could be 107. This means the version numbers that I release wouldn’t follow a set pattern (they’d increase, but the step of the increase wouldn’t be uniform).
If this isn’t important to you then having the revision number of the version stamp based on the revision from source control is a really good idea. I’m sure it would be quite possible to retrieve from TFS.
Thanks for the idea,
Paul
Nice…
I use pretty much the same process, using nant
and svn, but have no need for database/web service
interaction for fetching version numbers. I simply
fetch latest revision from svn and append it to
major and minor version, then update AssemblyInfo files.
Main nant script has argument
that defines major and minor version of the build.
Yes, major.minor numbers do have to be updated
(only in one place) manualy from time to time, but that
doesn’t seem to be a problem since they tend to change less frequently…
My question is: did you go trough db/web service steps
as a learning/exploring exercise or there is an issue with fetching revision number from TFS?
Heya Paul, you need to escape the full stops in that regex, e.g.: (d+)\.(d+)\.(d+)\.(d+)
Cheers
Aaah - and the digit matcher, e.g. \d