January 7, 2017

Continuous Integration and Deployment Basics for .NET Developers - Part 4

Versioning

There are a number of strategies to versioning your .NET application but basically your versioning strategy are going to come down to one of two options - do you want MSBuild to manage the version number or do you want your build server to have input into the version number assigned to your binaries?

Managed by MSBuild

This is by far the easiest approach, all you have to do is modify a single line in the AssemblyInfo.cs file

[assembly: AssemblyVersion("1.0.0.0")]

is changed to read

[assembly: AssemblyVersion("1.0.*")]

At build time, MSBuild will automatically assign a the build and revision number and in many cases this is good enough.

This has its advantages and disadvantages:
  • The major advantage is that it's simple and neat and doesn't require much effort.
  • The major disadvantage is that there's no control over the version number that is assigned and traceability becomes more complicated - your QA team are going to be your most likely candidates for complaint if you use this approach.
This approach is documented fairly well and these StackOverflow posts cover this technique fairly comprehensively:

http://stackoverflow.com/questions/4562845/how-to-change-assembly-version-number-using-assemblyinfotask
http://stackoverflow.com/questions/356543/can-i-automatically-increment-the-file-build-version-when-using-visual-studio

If you don't require the ability to track a binary's authenticity through your system, then quite probably this is the approach you will want to take. It's minimal overhead to make it happen and will give you versioned binaries.

If you want to manage your version numbering from a centralized location, then you can add a GlobalAssemblyInfo.cs file that is really just an AssemblyInfo.cs to your master project. It can be identical to the AssemblyInfo.cs, so just rename that file. Then go to each of your other projects and remove the AssemblyInfo.cs that those projects contain.
  • Right click the project
  • Add existing item
  • Find your GlobalAssenblyInfo.cs
  • Instead of clicking Add, click the down arrow next to it - choose Add As Link
  • Move the file to the Properties folder
Now all of your projects within your solution will have a globally managed version format. This isn't to be confused with the version number itself which may vary based on the build itself.

Managed by the Build Server

An alternative is to have your build server handle versioning at compile time applying a semantic version to each build artifact that meets industry recommendations such as described at http://www.SemVer.org. In order to do this we will need a pre-build script that the build server can initiate and set the version code in each of the AssemblyInfo.cs files. The simplest way is to change the AssemblyInfo.cs file so the line that reads:

I tend to prefer to be able to track a build back to a specific build in my build system, so I have a PowerShell script that I use to parse out original number and replace it with one generated by my build system. The basic gist of the PowerShell script can be found at:

http://stackoverflow.com/questions/36200323/get-and-replace-assemblyversion-from-assemblyinfo-cs

The concept here is to just wrap it in a function and pass in the build version number as a parameter. I've made some cosmetic adjustments as necessary, but basically this script will when given a path to AssemblyInfo.cs parse out the AssemblyVersion and change it to the version info you specify in the input arguments.

function Set-BuildVersion {

    [CmdletBinding()]
    param([Parameter(Mandatory=$true)][System.String]$AssemblyInfoPath,
          [Parameter(Mandatory=$false)][System.Nullable[Int32]]$Major=$null,
          [Parameter(Mandatory=$false)][System.Nullable[Int32]]$Minor=$null,
          [Parameter(Mandatory=$false)][System.Nullable[Int32]]$Build=$null,
          [Parameter(Mandatory=$false)][System.String]$Meta=$null)

    $pattern = '\[assembly: AssemblyVersion\("(?<Major>\d+)\.(?<Minor>\d+)\.(?<Build>(?:\*|\d+))(?:\.(?<Revision>\d+))?"\)\]'
    (Get-Content $AssemblyInfoPath) | ForEach-Object{
        if($_ -match $pattern){
            # We have found the matching line
            # Edit the version number and put back.
            $fileVersion = $matches
            $major = $fileVersion.Major
            if ($VersionMajor -ne $null) {
                $major = $VersionMajor
            }
            $minor = $fileVersion.Minor
            if ($VersionMinor -ne $null) {
                $minor = $VersionMinor
            }
            $build = $fileVersion.Build
            if ($VersionBuild -ne $null) {
                $build = $VersionBuild
            }
            $newVersion = "{0}.{1}.{2}" -f $major, $minor, $build

            if ($VersionMeta -ne $null) {
                $newVersion = "{0}-{1}" -f $newVersion, $VersionMeta
            }
            '[assembly: AssemblyVersion("{0}")]' -f $newVersion
        } else {
            # Output line as is
            $_
        }
    } | Set-Content $AssemblyInfoPath
}

Get-ChildItem -Path . -Filter AssemblyInfo.cs -Recurse | Set-BuildVersion -VersionBuild $buildVersion


This script should be run in a Pre-Build step triggered by the build server. Because of the nature of versioning, it's something that only needs to happen at build time on the build server when producing artifacts for release. It's not something that necessarily needs to run as part of a local build if you're compiling to run on your local machine for debug purposes. Given that it's a generic script, it can easily be added to a PSGet repository and pulled down by the build server at build time to generate version numbers for your binaries. Its lifecycle can be handled exactly as you would handle publishing any other library.

If you wished to run this as a prebuild step in your local builds, you'd need to add a PowerShell step to your project files that executed the PowerShell script and allowed properties to be either defaulted or passed from the commandline when you execute MSBuild.

More to come...

No comments:

Post a Comment