Web developer from Sydney Australia. Currently using asp.net, mvc where possible.

Tuesday, October 19, 2010

Mercurial Revision No to Version your AssemblyInfo - MsBuild Series

By now you already have a build file setup and running, if not please see my previous post.

This post is going to focus on getting our version number automatically updated during the build process. We are going to use the AssemblyInfo task from the “MSBuild.Community.Tasks” library and the HgVersion task from the “MSBuild.Mercurial library.

The HgVersion task will get the revision number from our Mercurial source control repository so we can include it in our assemblyInfo file.

The AssemblyInfo task will create our AssemblyInfo file before the compile task is executed.

If you are using TFS or Subversion you will need to substitute the Mercurial task for the equivalent task for these version control systems. The MsBuild.Community.Tasks contains a TFS and Subversion Version tasks for this purpose.

1) Getting the revision number from Mercurial.

Firstly download and copy the MSBuild.Mercurial from http://msbuildhg.codeplex.com/releases/view/47779 and copy the following two files below to “[Project Root]\Tools\MSBuild.Mercurial” diretory:
  • MSBuild.Mercurial.dll
  • MSBuild.Mercurial.tasks
With these files in place we can now use the HgVersion Task to retrieve the version number from Hg and store it into a property.
<PropertyGroup>
    <MSBuildMercurialPath>.</MSBuildMercurialPath>
</PropertyGroup>
<Import Project="Tools\MSBuild.Mercurial\MSBuild.Mercurial.Tasks" />
<Target Name="Hg-Revision">
    <HgVersion LocalPath="$(MSBuildProjectDirectory)" Timeout="5000">
        <Output TaskParameter="Revision" PropertyName="Revision" />
    </HgVersion>
    <Message Text="Last revision from HG: $(Revision)"/>
</Target>
Firstly we set “MsBuildMercurialPath” to the current directory. Then we import the Mercurial Tasks from our Tools directory.

Next we define a new target called “Hg-Revision” which calls the HgVersion task. The HgVersion task will set the revision number from the repository in the "LocalPath" to a property called “Revision”.

Lets test it out by running: build /t:hg-revision

Output from hg-revision task

2) Creating our Assembly Info File

Now we are going to create a task to automatically generate an assembly info file.

We need the MSBuild.Community.Tasks library from here: http://msbuildtasks.tigris.org/

Again I will copy the necessary files under the tools folder.
  • Tools\MSBuildCommunityTasks\MSBuild.Community.Tasks
  • Tools\MSBuildCommunityTasks\MSBuild.Community.Tasks.dll
(NB: Copying these assemblies under tools folder makes the solution much easier to setup on other developer machines and build servers.)
<!-- Create an Assembly Info File -->
<PropertyGroup>
    <Major>1</Major>
    <Minor>0</Minor>
    <Build>0</Build>
    <Revision>0</Revision>
    <MSBuildCommunityTasksPath>.</MSBuildCommunityTasksPath>
</PropertyGroup>

<Import Project=".\Tools\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />

<Target Name="SolutionInfo">
    <Message Text="Creating Version File: $(Major).$(Minor).$(Build).$(Revision)"/>
    <AssemblyInfo
        CodeLanguage="CS"
        OutputFile=".\Source\SolutionInfo.cs"
        AssemblyTitle="Jobping.StickyBseak"
        AssemblyDescription="StickyBeak logs requests to your website"
        AssemblyCompany="http://www.jobping.com/"
        AssemblyProduct="Jobping.StickyBeak"
        AssemblyCopyright="Copyright © whohive"    
        ComVisible="false"
        CLSCompliant="true"
        Guid="1d4d1a0d-52f3-49d4-b2f8-3ca642f05cfe"
        AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)"
        AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)"
    />
</Target> 
Once this is in place we can now generate our AssemblyInfo.cs file. I have called the output filename SolutionInfo.cs as I like to use the same file for each project in the solution (I’ll show you how to do this soon).

Let's test this by running: build /t:solutionInfo

Output from SolutionInfo

As you can see we are using the hard-coded version number of 1.0.0.0 for the moment.

Now lets hook our SolutionInfo.cs file into our visual studio project and have it included in the build.
  1. Firstly go through your project and delete any existing AssemblyInfo files you may have under the “Properties” folder.
  2. For each project in your solution right click and select “Add Existing Item” from the context menu.
  3. Navigate to the SolutionInfo.cs file generated and add as a link. (See below on how to add as a link)
Add SolutionInfo as a link

3) Combining the Hg-Revision and SolutionInfo Tasks

So far our SolutionInfo task only uses a hard-coded revision number of  "0". What we really want is to have this revision number linked to the source control revision number. This is really easy to achieve as follows.
<Target Name="VersionSolutionInfo" DependsOnTargets="Hg-Revision;SolutionInfo">
    <Message Text="Get Revision, Generate SolutionInfo"/>
</Target>
So we have just combined the two items into a new task called VersionSolutionInfo. This task depends on the Hg-Revision and SolutionInfo tasks, so it will first call Hg-Revision to get the lastest revision number into our Revision property then when SolutionInfo is called it will use this revision number.

Lets execute it as follows: build /t:VersionSolutionInfo

Output from VersionSolutionInfo

As you can see, the revision number 10 is retrieved from mercurial and then the solutionInfo is generated. If you inspect the SolutionInfo.cs file you will find that the version number is now 1.0.0.10

The final step is to change our default build task to depend on VersionSolutionInfo so we can have everything done in 1 step.
<Target Name="Build" DependsOnTargets="Clean;VersionSolutionInfo,Compile">
    <Message Text="Clean, VersionSolutionInfo, Compile"/>
</Target>
NB: you can have any number of files containing the assembly meta data such as company, version etc. For example we could split it as follows:
  • SolutionInfo.cs – contains static information and is checked into source control
  • SolutionInfo.geneated.cs – just contains version information (not checked in)
You can organise it anyway you like.

Update: You need to TAG your repository with the full version number so you can relate the release's version no to a changeset across all repositories. I'll post a build target to do the tag automatically at some point. Thanks to @jdhard for bringing this up.

Below I have provided two sample MsBuild files. Firstly we have our main build file that handles : clean & compile from the first post it this series. The second file contains the targets used in this post. I have split out the items so it's easy to isolate the Tasks for this post.

Resources:
This is the second post in the MsBuild series.

Shout it kick it on DotNetKicks.com

4 comments:

jdhardy said...

Mercurial's revision number probably isn't the best thing to use, since it can change from one machine to the next as it's not part of the repo - it's just a shorthand for working from the command line.

The changeset ID (the hash) is truly unique, but doesn't really fit into the .NET versioning scheme.

Mark Kemper said...

@jdhardy Yes, good point. I should have mentioned that you need to tag your repository with the version number after a release (a build target for that is coming in a post soon).

We can still use Mercurials revision no since each release has more commits then the last release.

I will add an update to mention the tagging of the repository.

Thanks.

Sayed Hashimi said...

If you want to use MSBuild Community tasks from a location other than under %ProgramFiles32% you should specify the property MSBuildCommunityTasksPath to point to that path. If you look inside the MSBuild.Community.Tasks.targets file you will see that it uses this property which defaults to: $(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.dll.

So in your usage it is still loading the assemblies under %ProgramFiles32%. If you hand this to a dev who doesn't have the community tasks installed it will not work.

Mark Kemper said...

@Sayed Hashimi Thanks for your comments.

The MSBuildCommunityTasksPath is defined in the main build file. I have separated out the different components to make it easier to read.

If you take a look at the main build file at the end of the post you can see where MsBuildCommunityTasksPath is defined.

Also, I don't have MsBuildCommunityTasks installed on my machine. I prefer to download the source and build it myself. I find reading through the source of these items and finding out how they work to be very helpful.