Automating version numbering for automated builds

Introduction

Now that you have created your automated build as well as your solution build, each time that a developer checks in code you may want to create a better system for your versioning of dll’s enabling you to correlate a built library to your source control, for each and every build that your build server does. This article assumes that you are using C# for your current project and that you have some knowledge of MsBuild.

Configuring your solution (optional)

In order to have a central place to change the version number for all of the assemblies, create a shared folder in the root of your solution.

Create a new file in that folder, and call it GlobalAssemblyInfo.cs

In Visual Studio add a new folder, and call it the same as the physical folder on disk (to avoid confusion)

Add existing item, and select your empty GlobalAssemblyInfo.cs file.

Place AssemblyInfo code into this file that you have common across all projects.

Example:

using System.Reflection;

using System.Runtime.CompilerServices;

using System.Runtime.InteropServices;

[assembly: AssemblyCompany(“Company”)]

[assembly: AssemblyCopyright(“Copyright © Company 2012. All Rights Reserved.”)]

[assembly: AssemblyTrademark(“Trademark”)]

[assembly: AssemblyProduct(“Product”)]

[assembly: ComVisible(false)]

[assembly: AssemblyVersion(“1.0.0.0”)]

On each project that you have, you need to add the global assembly info file, as a linked file. This will work in conjunction with the existing AssemblyInfo file. So you can still have project specific settings in your AssemblyInfo.

Right click on the project and Add Existing Item, go to the location of your GlobalAssemblyInfo, but DON’T double click the file just select it by clicking once on the file. You will notice there is a small arrow next to the Add button.

Click on the little down arrow, and then click on Add As Link

If everything worked out you should see something similar to this, notice the little short cut icon on the GlobalAssemblyInfo.cs

It works just as well but I personally prefer things to be a bit neater, and so I just move the file into the ‘Properties’ section of my project.

If you do this for all the projects in your solution, you can update the version number in one place, namely the GlobalAssemblyInfo.cs, and all of the version numbers will be correct.

Keep in mind that the AssemblyInfo information will override the GlobalAssemblyInfo so make sure that you remove all of the properties that you set in your GlobalAssemblyInfo file from the AssemblyInfo file.

Automatically update the GlobalAssemblyInfo

What we want to do is update the version number within the GlobalAssemblyInfo file, before we do the build so that built libraries will have the correct version number. MsBuild has a plethora of options around manipulating, messaging and controlling files. What I have found to be the simplest is the MsBuild Extension Pack

Create your MsBuild file that you will use to set the version, and do the build. (Create an empty text file, and give it an extension of .msbuild)

Before we start we need to make sure that all of the msbuild extensions that we are going to use are available.

<?xml
version=1.0
encoding=utf-8?>

<Project
ToolsVersion=4.0
DefaultTargets=BuildAndVersion
xmlns=http://schemas.microsoft.com/developer/msbuild/2003>

    <Import
Project=$(MSBuildExtensionsPath)\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks/>

Notes:

  • DefaultTargets=”BuildAndVersion” means that if you run this msbuild file without any parameters it will execute the target called “BuildAndVersion”
  • ToolsVersion=”4.0″ means it needs msbuild.exe that is part of the .net framework 4 normally located at %systemroot%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe
  • Import statements is almost like “using” in C# that .tasks file contains all the information that msbuild needs to execute those extra tasks (normally there is a dll of some sort involved)
  • I use the $(MSBuildExtensionsPath) so that there is no hardcoded reference to these extensions, and that if somebody has their computer installed differently, or the build server has everything installed on the D drive the build system will know how to get it

Properties

Properties are almost like variables, it is a mechanism to store values, that you can then later on use in your tasks.

    <PropertyGroup>

        <Major>1</Major>

        <Minor>0</Minor>

        <Feature>0</Feature>

        <VersionNumber></VersionNumber>

        <FileVersionNumber></FileVersionNumber>

        <CCNetLabel
Condition=‘$(CCNetLabel)’ == ”>65</CCNetLabel>

        <BaseDir>$(MSBuildProjectDirectory)\..</BaseDir>

<BuildDir>$(BaseDir)\Builds</BuildDir>

    </PropertyGroup>

Notes:

  • $(MSBuildProjectDirectory) is an like an environment variable, and will be the directory were you are running the MsBuild file from.
  • You can then use $(BaseDir) again as a variable in another property.
  • You can also have conditions, so if this file is run from CruiseControl.net the CruiseControl.net system will set the CCNetLabel to what the build label currently is (it increments it by 1 each time it runs).
    • This condition means, that if CCNetLabel is equal to empty, then set the value of it to 65.

Create a target

It is almost like a method in code, this will set the properties that we created to the correct version number

    <Target
Name=SetVersionNumber>

        <CreateProperty
Value=$(Major).$(Minor).$(Feature).$(CCNetLabel)>

            <Output
TaskParameter=Value
PropertyName=VersionNumber />

        </CreateProperty>

        <CreateProperty
Value=$([System.DateTime]::Now.ToString(`yyyy.MM.dd.HHmm`))>

            <Output
TaskParameter=Value
PropertyName=FileVersionNumber />

        </CreateProperty>

    </Target>

Notes:

  • If you would like to see how you can see file version numbers in windows explorer go see this <Another short little article I wrote because it took the goodness away from this article> about it.
  • So the first CreateProperty, creates a property called VersionNumber (in this case just setting the property that we have created) and concatenates the string.
  • The second CreateProperty is something extra that we add on to our assemblies here at FrameworkOne and it gets the time and formats it. You need something like ILSpy to see it.

Update the GlobalAssemblyInfo File

    <Target
Name=UpdateAssemblyInfoFiles
DependsOnTargets=SetVersionNumber>

        <ItemGroup>

            <AssemblyInfoFiles
Include=..\Shared\GlobalAssemblyInfo.cs/>

        </ItemGroup>

        

        <MSBuild.ExtensionPack.Framework.AssemblyInfo

            AssemblyInfoFiles=@(AssemblyInfoFiles)

            AssemblyVersion=$(VersionNumber)

            AssemblyFileVersion=$(FileVersionNumber) />

    </Target>

Notes:

  • Before we can run this target we depend on the “SetVersionNumber” so MsBuild will automatically first run SetVersionNumber if we ask it to run UpdateAssemblyInfoFiles.
  • We create an “array” type variable. If you did not create a GlobalAssemblyInfo file you could list all of your AssemblyInfo files here.
  • Then we execute the AssmeblyInfo task on the MSBuild.ExtensionPack and give it parameters.
  • Notice the @ sign in front of the array, if you would like to know more about the difference between @,%,$ read this article (also listed at the bottom of this post).

Building solution

So now we have done all of the work required and we can just go ahead and build our solution

<Target
Name=BuildSolution
DependsOnTargets=UpdateAssemblyInfoFiles>

<MSBuild
Projects=$(BaseDir)\YourSolutionFile.sln


Targets=Clean;Build


Properties=Configuration=Release

/>

</Target>

Notes:

  • This is our DefaultTarget so all the required steps will first be executed before this runs.
  • We explicitly call Targets, Clean;Build so that we do a clean first (this is what Visual Studio does behind the scenes if you right click solution and say clean), and then a build.
  • We set the Configuration to Release so that it builds your release mode libraries.

That should result in your build having proper version numbers, all that is left to do is placing this in your automated build system, and executing MsBuild with this build file.

The completed MsBuild file

<?xml
version=1.0
encoding=utf-8?>

<Project
ToolsVersion=4.0
DefaultTargets=BuildSolution
xmlns=http://schemas.microsoft.com/developer/msbuild/2003>

    <Import
Project=$(MSBuildExtensionsPath)\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks/>

    <PropertyGroup>

        <Major>1</Major>

        <Minor>0</Minor>

        <Feature>0</Feature>

        <VersionNumber></VersionNumber>

        <FileVersionNumber></FileVersionNumber>

        <CCNetLabel
Condition=‘$(CCNetLabel)’ == ”>65</CCNetLabel>

<BaseDir>$(MSBuildProjectDirectory)\..</BaseDir>

    </PropertyGroup>

    <Target
Name=SetVersionNumber>

        <CreateProperty
Value=$(Major).$(Minor).$(Feature).$(CCNetLabel)>

            <Output
TaskParameter=Value
PropertyName=VersionNumber />

        </CreateProperty>

        <CreateProperty
Value=$([System.DateTime]::Now.ToString(`yyyy.MM.dd.HHmm`))>

            <Output
TaskParameter=Value
PropertyName=FileVersionNumber />

        </CreateProperty>

    </Target>

    <Target
Name=UpdateAssemblyInfoFiles
DependsOnTargets=SetVersionNumber>

        <ItemGroup>

            <AssemblyInfoFiles
Include=..\Shared\GlobalAssemblyInfo.cs/>

        </ItemGroup>

        

        <MSBuild.ExtensionPack.Framework.AssemblyInfo

            AssemblyInfoFiles=@(AssemblyInfoFiles)

            AssemblyVersion=$(VersionNumber)

            AssemblyFileVersion=$(FileVersionNumber) />

    </Target>

<Target
Name=BuildSolution
DependsOnTargets=UpdateAssemblyInfoFiles>

<MSBuild
Projects=$(BaseDir)\YourSolutionFile.sln


Targets=Clean;Build


Properties=Configuration=Release

/>

</Target>

</Project>

Resources

http://rationalgeek.com/blog/msbuild-propertygroup-itemgroup-item-metadata-and-crazy-syntax/

http://msbuildextensionpack.codeplex.com/

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s