Versioning Applications Using nAnt and CruiseControl.NET

During my presentation at DevTeach in Vancouver, I showed a technique for setting up versioning of your applications using nAnt and CruiseControl.NET.  Since then I've had a couple of people ask me if I could post something on this as they missed getting notes on it when they were in Vancouver.  Here we go.

Versioning of .NET applications is done through the values that are in the AssemblyInfo.cs file.  When you create a new project in Visual Studio that file is created for you.  In it there is a bunch of assembly metadata information.  AssemblyVersion, AssemblyCompany, AssemblyTitle and a number of others are in there.  By default the AssemblyVersion value is set to 1.0.0.0.  Regardless of the number of times that you compile the application, the version will stay the same since this value isn't automatically incremented.

If we're working in a Continuous Integration environment we have the capability to incrementally version the assemblies created during each integration phase.  There are two components to accomplishing this.  The first piece of the puzzle is getting CruiseControl.Net to provide an automatically incrementing value.  The second piece is using nAnt to create an customized version of the AssemblyInfo.cs file that can then be compiled into the assembly.

CruiseControl.Net takes care of automatically incrementing a value for you.  By default CCNet is using the Default Labeller which starts at 0 and increments by one for each successful integration.  This could be used to version your application, but in the end your application may have a build label of 843, which doesn't really conform to the industry standard for version format (1.0.0.0).  The Default Labeller has a prefix node that will allow you to prefix the automatically incrementing build number.  If you set the prefix node to 2.0.0. and the last successful build was #569 then the labeller will create a label value 2.0.0.569.

One of the other labeller blocks that I regularly use is the Iteration Labeller.  The Iteration Labeller works much like the Default Labeller.  The Iteration Labeller is most useful if you are working on an iterative release schedule with a fixed and consistent duration for each iteration.  Configuring the Iteration Labeller requires you to set the duration (say 14 if you are releasing every 2 weeks) and the release start date (what was the first day of the first iteration).  Like the Default Labeller, you also provide a prefix, but only for the first two values in the four part build label (2.0. -- always remember the last "." for it to work correctly).  CruiseControl.Net will take care of automatically incrementing the last two values for you.  As the iteration duration passes, the third value in the build label will increment by one (from 2.0.0.x to 2.0.1.x) and the final value in the build label will reset to zero (from 2.0.0.59 to 2.0.1.0).  The great thing about this labeller is that you will be able to look at the version number of an assembly and immediately associate it with an iteration release, and probably the feature set that should have been included in it or the defects that should have been fixed in it.

You don't have to use one of just these two labellers.  There are a number of others that offer different value and may be better suited to your project.

Once you have CCNet configured to create a build label that you're happy with using as a version number, you can move off to nAnt and put that value to use.  When you use the nAnt Task within CCNet, some CCNet values are automatically pushed through to the nAnt script being called.  One of those values is CCNetLabel.  You can use this in nAnt just like any other nAnt property.

One of the nice features in nAnt is the asminfo Task.  With it you can create a customized replacement for the default AssemblyInfo.cs file created by Visual Studio. This may look something like this in your nAnt build script.

    <asminfo output="CommonAssemblyInfo.cs" language="CSharp">
      <imports>
        <import namespace="System" />
        <import namespace="System.Reflection" />
      </imports>
      <attributes>
        <attribute type="AssemblyVersionAttribute" value="${CCNetLabel}" />
        <attribute type="AssemblyCopyrightAttribute" value="Copyright (c) 2008 My Company Inc" />
        <attribute type="AssemblyCompanyAttribute" value="My Company Inc." />
        <attribute type="AssemblyProductAttribute" value="My Application Name" />
      </attributes>
    </asminfo>

So we've created the custom AssemblyInfo file that we wanted (in this case called CommonAssemblyInfo.cs).  Now you need to get this included into your project.  In the csc Task in the compilation portion of your nAnt script you will need to make sure to include the generated custom AssemblyInfo file and also exclude the AssemblyInfo files generated automatically by Visual Studio.  If you don't exclude the auto generated AssemblyInfo file you will get a error at this compilation point in the nAnt build script.

    <csc target="library" output="MyApplication.dll" debug="true">
      <sources>
        <include name="C:\working\MyApplicationSolutionFolder\**\*.cs"/>
        <exclude name="C:\working\MyApplicationSolutionFolder\**\AssemblyInfo.cs"/>
        <include name="C:\working\MyApplicationSolutionFolder\CommonAssemblyInfo.cs"/>
      </sources>

That's all great.  You now have your Continuous Integration automatically versioning your application.  There's more that you should probably do though.

I'm a huge proponent of having the developers running the exact same nAnt build script locally on their machines.  If the developers are doing this I know that the application is repeatedly being executed, as if it were going to production, before it's even getting to the integration server.  Developers will be executing the build script through nAnt, not CruiseControl.NET.  This will cause problems with the versioning information I showed above.  Without CCNet kicking off the build process, none of the values that it pushes into nAnt will be available.  For the purpose of this post, that means that we will not have the CCNetLabel when we run the build script from nAnt by itself.  This will actually cause an error when we try to run the build script.  To avoid that happening we need to make use of the property::exists nAnt Function and a temporary property.

    <property name="version" value="0.0.0.0"/>
    <if test="${property::exists('CCNetLabel')}">
      <property name="version" value="${CCNetLabel}"/>
    </if>
    <asminfo output="CommonAssemblyInfo.cs" language="CSharp">
      <imports>
        <import namespace="System" />
        <import namespace="System.Reflection" />
      </imports>
      <attributes>
        <attribute type="AssemblyVersionAttribute" value="${version}" />
        <attribute type="AssemblyCopyrightAttribute" value="Copyright (c) 2008 My Company Inc." />
        <attribute type="AssemblyCompanyAttribute" value="My Company Inc." />
        <attribute type="AssemblyProductAttribute" value="My Application Name" />
      </attributes>
    </asminfo>

What that block of xml prior to the asminfo task is doing is to ensure that there is a version number for the application even if the CCNetLabel is not available (the developer is running the build script on their local machine).  The first step is to setup the temporary property 'version' and setting it to a default value.  The next thing we setup is the 'if' block that checks to see if the CCNetLabel property is available.  When it is (if the build script is running because CCNet kicked it off), the 'version' property is reassigned the value that was passed in using the CCNetLabel property.  The final step is to use the 'version' property when we create the custom CommonAssemblyInfo.cs file.

One of the things that I like about using the 0.0.0.0 default value is that you will always know that you have a rouge compile and deploy if you find a file with that version in your production or test environment.  Like using the Iteration Labeller, using a default value provides you with a quick point of reference based on the metadata associated with your application.

posted @ Friday, January 04, 2008 9:12 PM

Print

Comments on this entry:

# re: Versioning Applications Using nAnt and CruiseControl.NET

Left by alberto at 1/8/2008 11:42 AM
Gravatar
Nice post. It's going to be very useful to me. Just a stupid question. How do you map between your source code and the corresponding built version? In my case, using SVN, I'm considering tagging the versions after the generated label, maybe just for the versions to be deployed (not every correct build).

# re: Versioning Applications Using nAnt and CruiseControl.NET

Left by Surendra at 1/10/2008 1:05 PM
Gravatar
How do I default the CCNetLabel to start at for example 500. I am using the default labeller and recently moved the build machine. So the previous machine had builds until 499. Now I want to start at 500 instead of 1 and then continue using the default labeller.

# re: Versioning Applications Using nAnt and CruiseControl.NET

Left by Donald Belcham at 1/10/2008 5:42 PM
Gravatar
@alberto -- I've never tried to do conditional labeling on one CCNet Project. The only way that I've been able to implement this is to have two CCNet Projects. One will be used for the continuous integration and doesn't label anything. The other project is only run on a force build when you want to create a release. It is the project that is configured to do the labeling.

# re: Versioning Applications Using nAnt and CruiseControl.NET

Left by Donald Belcham at 1/10/2008 5:53 PM
Gravatar
@Surenda -- What you want to do is find a file that is named with a "<projectname>.state" format. In that file (just open it with notepad) there is an element named Label. If you change that I think that to have 500 in place of whatever your current build number is, you will be good to go.

# re: Versioning Applications Using nAnt and CruiseControl.NET

Left by alberto at 1/11/2008 2:37 AM
Gravatar
I think I didn't explain myself very well. My concern is being able to checkout from my source a certain build (e.g. 2.1.2.0) to fix a certain bug, for example. How do you approach that?

I see two alternatives:
- Use SVN revision number in assembly/ccnet label number. The problem with this is, I'm not sure if just using a release number is enough. I mean, are tags there for a reason apart from using a name to identify a revision number?)

- Tag the builds in SVN named with the assembly label number. The problem is, not every build is a release build, so I don't think I really need/want to tag every single passing build.

# re: Versioning Applications Using nAnt and CruiseControl.NET

Left by Nirav at 2/27/2008 12:05 PM
Gravatar
I am planning to implement shipping (external) and non-shipping builds (internal for development). I want to increase the build no for only external builds. How can I implement this using NAnt and CCNET?

# re: Versioning Applications Using nAnt and CruiseControl.NET

Left by Bil Simser at 4/30/2008 9:52 AM
Gravatar
This is a good approach and pretty much what I have in my projects (did you copy me Donald!).

However there's a missing step here that I would like to see how you resolve.

It's all fine to have NAnt generate an AssemblyInfo.cs for you but if CC.NET is doing the builds, where does this file go? The missing step is that the CommonAssemblyInfo.cs file doesn't go back into source control. It's just created on the build server and never goes anywhere.

There's a couple of ideas here. One is to use the svn feature to label the build in subversion. This will create a tag with the build number so you can pull that tag out at any time. However again the problem is that the assembly doesn't contain the right version info.

The only way I can see this working is either a) nant re-commits the commonassemblyinfo.cs file back into subversion after it regenerates it but then that means the developers would have a copy of it when they pull the latest down and that would defeat the purpose of differenciating a developer build (0.0.0.0) with a server build (n.n.n.CCNetBuild#).

The other option is to have CC.NET do a mock deploy of the compiled assemblies somewhere in a versioned tree. This is what I'm looking at. After the assemblyinfo is generated the assemblies are built then xcopied to a location (d:\projects\projectname\version). From there someone can deploy the files to a server or client location. Another option I'm looking at is building an MSI in NAnt (via NCIS) and tagging it with the version number and coping it somewhere to be deployed.

Email me so we can maybe take this offline and discuss as I'm very passionate about automating the world in a simplified manner.

# re: Versioning Applications Using nAnt and CruiseControl.NET

Left by Altug at 5/23/2008 5:57 AM
Gravatar
I have been doing it your way but I would like to use the solution file to build. Something like:
<target name="compile" depends="init">
<echo message="Build Directory is ${build.dir}" />
<exec program="${framework::get-framework-directory(framework::get-target-framework())}\msbuild.exe"
commandline="${solution.file} /t:Clean /p:Configuration=${project.config} /v:q"
workingdir="." />
<exec program="${framework::get-framework-directory(framework::get-target-framework())}\msbuild.exe"
commandline="${solution.file} /t:Rebuild /p:Configuration=${project.config} /v:q"
workingdir="." />
</target>

How can I use the generated AssemblyInfo.cf file with my projects in my solution?

Your comment:



 (will not be displayed)


 
 
 
Please add 2 and 7 and type the answer here:
 

Live Comment Preview: