Coding with Titans

so breaking things happens constantly, but never on purpose

TeamCity – advanced application version patcher

I am a really big fan of JetBrain’s TeamCity product. I use it a lot at work and also for my hobby projects. But unfortunately I found it lacks one important, yet very basic feature – proper (and easy) application version management. Someone might say, that this is not totally true, there is a build-in AssemblyInfo Patcher. Yes, OK, although this one is extremely limited and can mostly be used for very simple projects, created by Visual Studio New Project Wizard and never modified.

What are my special requirements then?

Well, in my world I really wish to:

#1. Make TeamCity fully responsible for assigning versions to all the software components.

Let’s say the hardcoded version inside the application and its libraries is “0.1.”, so the builds created on developers’ machines are quickly discoverable. Just in case someone totally accidentally with all the good reasons in mind tries to install it on customer’s machine. All official public builds should at least be greater than “1.0.” though.

#2. Each code branch should have a different build version.

For example the current application version build on ‘master’ branch is ‘1.7.’, while artifacts of ‘develop’ branch should be ‘1.5.’, comparing to feature branches, could be even lower, alike ‘0.9.*’.

This plays nicely with the #1 requirement stated above, as since all versioning management is handled by TeamCity, there should never be a merge conflicts between any of those branches and versions, nor any other manual source-code plumbing required.

#3. It should be possible to set BuildNumer to default, what C# compiler does.

BuildNumber is third part (number after second dot) of the version string. By default, if left as a star (1.0.*) C# compiler will put there today’s date, counted as days elapsed since 1st January 2000. It is extremely useful information, what I would love to preserve. This value automatically increases each day, and at any time can help find, how old any public release package used by customer was.

#4. It should be possible to also store the version info inside regular text file.

For a Web (or Web API) project, it’s much easier to store the current version inside a static file (as it never changes until the new release, so can be server-side cached) and let the client apps (that actually communicate with mentioned server part) download at startup to check, if there are any updates etc. Also it might play a label role, if resides next to compiled binaries.

Implementation

This is the very quick way of saying, how I’ve chosen to complete this task:

I designed a PowerShell script, launched as the very-first work item, just after the sources being downloaded from repository and NuGet packages updated.

TeamCity.steps

It’s flexible enough (as accepting lots of startup parameters from TeamCity) to locate the AssemblyInfo.cs file (or other variants), read current version stored there, update it according to wishes and project spec. Then it stores newly generated version back into the original file, optionally also into a text file and most importantly sends this new info back to TeamCity. Version is then generated in one place, simply based on given inputs and then spread to all required locations.

TeamCity.run.ps1

Of course to avoid any influences, repository checkout is configured to always load full sources from remote, so any local changes done previously during this process are reversed and never included into the current run.

Check the code at my gist and have fun using it!

Sample calls

PS> .\update-buildnumber.ps1 -ProjectPath 'src/ToolsApp' -BuildRevision 15

##teamcity[buildNumber '1.0.0.15']

PS> .\update-buildnumber.ps1 -ProjectPath 'src/Apps/DevToolsApp' -BuildNumber 0 -BuildRevision 21

##teamcity[buildNumber '1.0.6162.21']

PS> .\update-buildnumber.ps1 -ProjectPath 'src/Apps/DevToolsApp' -BuildNumber 0 -BuildRevision 29 -SkipAssemblyFileVersionUpdate $True

##teamcity[buildNumber '1.0.6162.29']