Add version info to your builds in TeamCity
Published on Monday, July 12, 2021 4:00:00 AM UTC in Programming & Tools
To properly identify what build of your application you're actually looking at, for example to double-check what's been deployed on a machine, version numbers are a good utility. Manually setting or incrementing versions is error-prone though. Also, from a technical point of view it would be much more desirable to get more details about the exact source version used, like branch and commit hash (for Git). The latter cannot be known before comitting your work, so manually entering this as part of your developer workflow is not an option anyway. A much better way is to let your CI pipeline do this automatically. I use TeamCity for this, and document the required details in this article - mostly as a reminder to myself, should I ever need to look it up again.
In TeamCity, you have dozens of variables (parameters) at hand that contain all sorts of information about the environment and details on the current build. You can use these to pass them around, for example to dotnet during your builds, but unfortunately most of these parameters need to be optimized, trimmed or parsed, to achieve what you need. The basic idea for a workflow is:
- Parse and prepare all required values into the version info string you want
- Put that info in another, custom parameter for TeamCity
- Use that custom parameter in a following build step to be passed on to dotnet or msbuild
For the first step, PowerShell is a great tool that also nicely integrates with TeamCity.
Creating a version string
Add a build step to your build configuration, and use something like (adjust for your environment as needed):
- Runner type: PowerShell
- Platform: x86
- Edition: Desktop
- Script: Source Code
The actual script then looks like this:
# get branch without clutter and invalid chars
$branch = "%teamcity.build.branch%"
$cleanBranch = $branch.split("/")[-1]
# get short commit hash
$commitHash = "%build.vcs.number%"
$shortHash = $commitHash.substring(0,7)
# get build number
$buildNumber = "%build.number%"
$version = "$($cleanBranch)-$($shortHash)-Build-$($buildNumber)"
Write-Host "Determined informational version as: $version"
Write-Host "##teamcity[setParameter name='env.MyCustomVersion' value='$version']"
Some interesting details of this are:
- We're only building the so-called version suffix. The idea is that the prefix of the version is coming from your code, in the format of 1.2.3, and adjusted manually when your application is updated. You can of course adapt this to your particular needs, this is just what works best for me.
%teamcity.build.branch%often contains something like
refs/heads/master. This is not only unnecessary clutter I don't want in a version string, the slashes are also formally not allowed in a version string (only alpha-numeric strings + dashes, underscores and dots are allowed). I split the branch name and only take the last fragment.
%build.vcs.number%contains (for Git) the full commit hash, so I manually trim it to the default 7 characters. You can change that e.g. if you prefer the 8 characters used by some services, or if you want to keep the full hash.
- I also add the build number from TeamCity, so I can easily find the logs for the build I'm currently looking at. Depending on the format of the build number YMMV.
- You can drop all sorts of commands to TeamCity by writing
##teamcityto stdout, which is what I did here to set a custom parameter named "env.MyCustomVersion". The prefix determines what kind the parameter is, you can read about the different types in the official documentation. An environment variable works well for me.
One thing to note is that you need to pre-define your custom parameter also in the "Parameters" section of your build configuration, or you won't be able to run the builds.
Using the version
Once this build step is complete, you can use the custom parameter in following build steps, for example in your "dotnet publish" logic. The reason I'm using the version suffix mostly is because TeamCity has built-in support for it in .NET build steps:
If you want to set the full version instead, that's also possible of course. You then need to pass the details directly to msbuild using the "Command Line Parameters" options of the build step, using the well-known "/p:" option of msbuild/dotnet CLI.
The result is that the entry assembly of your application then will have the version details in the "Product version" field on the details tab of the file properties:
The prefix (here:
1.0.0) is coming from the respective element in your project file, or the default value like in my example if it isn't set at all.
Programmatically using the version
The version is actually written (besides other places) to the
InformationalVersion custom attribute of the respective assembly. Because of that, you can easily retrieve it in code using logic like:
var assembly = Assembly.GetEntryAssembly();
var informationalVersion = assembly?
.InformationalVersion ?? "[Not set]";
Make sure you don't set the
InformationalVersion element in your
.csproj file. Apparently the command-line argument passed into the dotnet CLI does not take precedence; my experience is that the programmatically set version is not overwritten.
The version then can be used to be displayed somewhere in an info screen if your app has a UI, or simply passed on to service registrations or similar centralized management systems in distributed environments, to keep track of the deployed system landscape.