Chivinou [ʃivinu]

Adventures of a Belgian programmer exiled to Missouri
  • rss
  • Home
  • Soft Gallery
    • The Bean Mud Client
    • autosvnbackup.sh
    • Saleslogix
  • Contact Me
  • Welcome

Automatic Assembly Version Number

Nicolas Galler | August 6, 2008

Under Properties\AssemblyInfo.cs you can set the version number - this shows up under the file's details in Windows:

image

There is a nifty msbuild task that was distributed by Microsoft a while back called "AssemblyInfoTask" (I think it came with one of the Team Foundation Server releases) that can automatically set it to the current date which is very handy to quickly find out which version of a particular DLL is at a customer site.  However there are a few issues with the strategy:

  • because the version number is a 16-bit number it has some issues with assemblies built after 2007 (there is a work around for it - as you can see I only have month and day)
  • it updates AssemblyInfo.cs (no easy way to customize that) which is usually under version control... so this causes a lot of unnecessary commits and conflicts (trivial to resolve, but still annoying)
  • since it updates AssemblyInfo.cs every time it runs and not just when the version number needs to change it will cause the assembly to be rebuilt completely every time which makes the build process slower.

Alternatively there is a task in the "MSBuild Community Task" that can be used to generate a VersionInfo.cs file - just as good as AssemblyInfo but because this only contains the version it does not need to be checked into source control alleviating the 2nd issue above (at this point you comment out the AssemblyVersion attribute from the AssemblyInfo.cs file).

Another one from MSBuild Community Task is "SvnInfo" - it can be used to retrieve the Subversion revision number (there are other, equivalent tasks for other version control system).  Using this is just as good if not better than the date so we avoid the 1st issue (though this will come back when my SVN number gets above 65536!  But I have a long way to go for that).

There was still one issue which is the file would always be generated thus preventing the incremental build from shortening the build process as it is supposed to (in C or Java we can somewhat get away with that since the objects are built on a file-by-file basis but in C# they are always built into assemblies).

Therefore I created a very very simple task (don't laugh, it is my first msbuild custom task!) that generates the file but replaces it only if the number is updated:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Build.Utilities;
using System.IO;

namespace SSSWorld.MsBuild
{
    /// <summary>
    /// Task used to generate a VersionInfo file with the AssemblyVersion and AssemblyFileVersion
    /// attributes.
    /// Contains logic to avoid overwriting the file if it exists and the content is identical.
    /// </summary>
    public class GenerateVersionInfo : Task
    {
        public UInt16 MajorNumber { get; set; }
        public UInt16 MinorNumber { get; set; }
        public UInt16 RevisionNumber { get; set; }
        public UInt16 BuildNumber { get; set; }
        public String OutputFileName { get; set; }

        public GenerateVersionInfo()
        {
            MajorNumber = 0;
            MinorNumber = 0;
            RevisionNumber = 0;
            BuildNumber = 0;
            OutputFileName = null;
        }

        public override bool Execute()
        {
            String code = GenerateCode();
            String existingCode = null;
            if (File.Exists(OutputFileName))
                existingCode = File.ReadAllText(OutputFileName);
            if (!code.Equals(existingCode))
            {
                File.WriteAllText(OutputFileName, code);
                Log.LogMessage("Generated VersionInfo file {0}", OutputFileName);
            }
            else
            {
                Log.LogMessage("VersionInfo file {0} is up to date", OutputFileName);
            }

            return true;
        }

        private String GenerateCode()
        {
            StringBuilder buf = new StringBuilder();

            buf.AppendLine("// Code generated automatically by SSSWorld.MsBuild.GenerateVersionInfo")
                .Append("[assembly: System.Reflection.AssemblyVersion(\"")
                .Append(MajorNumber).Append(".")
                .Append(MinorNumber).Append(".")
                .Append(RevisionNumber).Append(".")
                .Append(BuildNumber == 0 ? "*" : BuildNumber.ToString())
                .AppendLine("\")]")
                .Append("[assembly: System.Reflection.AssemblyFileVersion(\"")
                .Append(MajorNumber).Append(".")
                .Append(MinorNumber).Append(".")
                .Append(RevisionNumber).Append(".")
                .Append(BuildNumber)
                .AppendLine("\")]");
            return buf.ToString();
        }
    }
}

And I adapted the AssemblyInfoTask target file to call on this task:

<?xml version="1.0" encoding="utf-8"?>
<!-- This targets file includes all the necessary information to automatically increment build numbers as part of
     a regular build process. To use it simply include it in your project file after any other includes. The typical
     include line looks like this:
     
     <Import Project="...\SSSWorld\msbuild\AssemblyVersion.target"/>
     
     and make sure you have a Properties\VersionInfo.cs file in your project (it will be overwritten by this task
     and should NOT be checked in version control)
  -->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask AssemblyFile="SSSWorld.MsBuild.dll" TaskName="SSSWorld.MsBuild.GenerateVersionInfo"/>

  <!-- These properties can be overridden to specify the major/minor version number -->
  <PropertyGroup>
    <MajorNumber>1</MajorNumber>
    <MinorNumber>0</MinorNumber>
    <!-- Default will be to get the SVN revision number as revision number -->
    <RevisionNumber>0</RevisionNumber>
  </PropertyGroup>

  <!-- Re-define CoreCompileDependsOn to ensure the assemblyinfo files are updated before compilation. -->
  <PropertyGroup>
    <CoreCompileDependsOn>
      $(CoreCompileDependsOn);
      GenerateVersionInfoFile
    </CoreCompileDependsOn>
  </PropertyGroup>

  <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>    

  <Target Name="GenerateVersionInfoFile">
    <SvnInfo LocalPath="." Condition="$(RevisionNumber) == 0">
      <Output TaskParameter="Revision" PropertyName="RevisionNumber"/>
    </SvnInfo>
    <GenerateVersionInfo OutputFileName="Properties\VersionInfo.cs"
      MajorNumber="$(MajorNumber)"
      MinorNumber="$(MinorNumber)"
      RevisionNumber="$(RevisionNumber)"/>
  </Target>
</Project>

What then needs to be done to get the version number into any project is to:

  • Remove AssemblyVersion and AssemblyFileVersion attribute from AssemblyInfo.cs
  • Add a VersionInfo.cs under the Properties folder of the project (does not have to contain anything as it will be overwritten automatically - and make sure it is NOT checked into source control)
  • Manually edit the project file and add the import line... Make sure the DLL is in the same directoy as the target file.

Optionally the following command can also be run to prevent the "unsafe project" warning dialog (replace with actual path of target file):

reg add HKLM\Software\Microsoft\VisualStudio\9.0\MSBuild\SafeImports /v AssemblyVersion /t REG_SZ /d "E:\Projects\SSSWorld\msbuild\AssemblyVersion.target"

Note that the key name is HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\9.0\MSBuild\SafeImports on Windows64.

Instead of using SvnInfo to get the revision number it can be obtained using the <Time> task, also part of the MSBuild Community Task.  Just make sure that the number is below 65536 as it needs to fit in 16 bits.

It is very simple but I have uploaded it to http://code.msdn.microsoft.com/AssemblyVersion in case it is useful to anybody else.  It was interesting at least to see how easy it was to create and use an msbuild custom task - I was relieved to find that it was not necessary to sign the assembly and install it to the GAC, and that you can use it right away.

Categories
Uncategorized
Comments rss
Comments rss
Trackback
Trackback

« Web Mailmerge on Vista A multi-select picklist for MS CRM »

Leave a comment

You can use these tags : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

About Me

I am a Saleslogix and .NET developer in Missouri.

Post Calendar

August 2008
M T W T F S S
« Jul   Sep »
 123
45678910
11121314151617
18192021222324
25262728293031

Archives

  • November 2008
  • September 2008
  • August 2008
  • July 2008
  • June 2008
  • May 2008
  • April 2008
  • March 2008
  • February 2008
  • January 2008
  • December 2007
  • November 2007
  • October 2007
  • September 2007
  • August 2007
  • July 2007
  • June 2007
  • May 2007
  • April 2007
  • March 2007
  • February 2007
  • January 2007
  • December 2006
  • November 2006
  • September 2006
  • January 2006
  • April 2005

Categories

  • Experiments
  • Interesting
  • MSCRM
  • Programming
  • Rant
  • Saleslogix
  • Tricks
  • Uncategorized

Meta

  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org

Most Useful Posts (in my very subjective opinion)

  • Unit Testing SLX - 7.2.2 Update
  • Enabling log4net logging in SLX 72
  • SLX EntityBoundSmartPart lifecycle
rss Comments rss valid xhtml 1.1 design by jide powered by Wordpress get firefox