Saturday, January 7, 2012

Versioning .NET assemblies

Providing meaningful version numbers to PE binaries (.EXEs and .DLLs) is a very good way to make sure that you can always find the correct source code that was used to build. Note that this should be used in conjunction with a symbol server, which is so trivially easy to set up in TFS 2010 that everyone should be doing it.

This is my currently recommended way for using the various versions available in .NET assemblies. Note that it is subject to change as new and better ideas come along!


The AssemblyVersion.cs file

Set up a single C# file that you will include in all your assemblies that need to be given the same version number. I currently give all the DLLs in a single releasable product the same version number so future releases of an assembly will have a new version number, regardless of whether any of the code in that DLL has actually changed. I find this the simplest way to ensure consistency of the product through the full SDLC. In this C# file, which could be called AssemblyVersion.cs, your continuous integration (CI) server will generate something like the following code:

using System.Reflection;

[assembly: AssemblyVersion("1.0.2.23456")]
[assembly: AssemblyInformationalVersion("1.0.1beta1 ($/Project/Main;223456)")]

You will need to put this file in a common location and include it as a linked file in each of your projects. This common file replaces the versioning information that is usually in AssemblyInfo.cs, so you will need to remove those attributes from each project that you include AssemblyVersion.cs in.


AssemblyVersion.cs in development and production

I like to make version number obviously different when building on developer machines, so I typically set the version to 0.0.0.0 or - in case Visual Studio Code Analysis complains - 0.0.0.1. The following AssemblyVersion.cs file would be checked into source control:

using System.Reflection;

[assembly: AssemblyVersion("0.0.0.0")]
[assembly: AssemblyInformationalVersion("development")]
And the following PowerShell script would be launched from within TFS, Jenkins, Cruise Control, or your CI server of choice to modify AssemblyVersion.cs.
param(
        [parameter(mandatory=$true)][int]$MajorVersion,
        [parameter(mandatory=$true)][int]$MinorVersion,
        [parameter(mandatory=$true)][int]$SourceRevision,
        [parameter(mandatory=$true)][string]$SourceBranch
)

#validate source parameters
if($SourceRevision -lt 0 -or $SourceRevision -gt 6553565535) {
    throw "Invalid SourceRevision."
}
if($MajorVersion -lt 0 -or $MajorVersion -gt 65535 -or $MinorVersion -lt 0 -or $MinorVersion -gt 65535) {
    throw "Invalid major / minor version."
}

$scriptFolder = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$versionFile = resolve-path (join-path $scriptFolder 'AssemblyVersion.cs') # script must be in the same folder as AssemblyVersion.cs

if(-not (test-path $versionFile)) {
    throw "Unable to find version file."
}

#calculate Build/Revision version component
if($SourceRevision -le 65535) {
    $rev1 = 0
    $rev2 = $SourceRevision
} 
else {
    $verStr = $SourceRevision.ToString()
    $rev2 = [int][string]::Join('', $verStr[-5..-1])
    if($rev2 -gt 65535) {
        $rev2 = [int][string]::Join('', $verStr[-4..-1])
    } 
    $rev1 = [int]$verStr.SubString(0, $verStr.length - $rev2.ToString().length)
}

$assemblyVersion = "$MajorVersion.$MinorVersion.$rev1.$rev2"
$humanVersion = $SourceBranch + ':' + $SourceRevision

Write-Host "Setting assembly version to $assemblyVersion and human readable version to '$humanVersion'."

(type $versionFile) -replace '0.0.0.0',$assemblyVersion -replace 'development',$humanVersion | Out-File -Encoding UTF8 $versionFile

AssemblyVersionAttribute

The Assembly version will by default also be set as the AssemblyFileVersion if the latter is not explicitly specified. One important piece of information I want to include is the revision number of the source code that was used to make the build. Each component in the AssemblyVersion is limited to 65535 (16 bit). Since it won't take long for 65535 revisions to be committed when checking in early and often, I spill over into the build number in a human readable fashion, so version 1.0 built from revision 223456 would be 1.0.2.23456.


AssemblyInformationalVersionAttribute

The AssemblyInformationalVersion attribute is a very useful free text string that will appear in the Product Version field when looking at the details of the DLL in Windows Explorer. I sometimes use a semantic version as the first part of that string, and always include an unambiguous reference to the source code that was used to build it i.e. branch and revision details for whatever source control system is in use.

0 comments:

Post a Comment