Search This Blog

Monday, March 30, 2009

Versioning DLLs in C#

Often I design and implement .NET projects never really worrying about their versions until sometime in the future someone comes back and says, "I have this problem, can you fix it", and then you end up thinking that you have provided a fix, just not sure which version the fix was in. Problems also arise when you are trying to find a threshold point of when an interface in a particular DLL changes so that other files using it are not supported.  Welcome to the age old problem of DLL versioning.  Mind you, at this point in time I am talking about singular DLL versioning and not versioning at a broader scale (product based).

I am not saying this is the best way or the only way, all I am saying is this is how I have addressed the problem in the past.

After surfing around and finding various versioning tools/articles like:
All the above versioning schemes are great although I chose to go with AssemblyInfoTask.  There are various (albeit more elegant ways) of using this simple tool but let us examine how I used it.

Step 1:
Well obviously download and install the tool from here.  The help file within the installer comes in very handy and it does explain how you can go about changing the version number according to whatever scheme you like.

Step 2:
Edit your project file to include the new versioning task in it.  This can be done by unloading your project in Visual Studio...

then you can right click on the unloaded project and go "Edit <ProjectName>".  This will open up your project as an XML file in the VS editor.  Most commonly your last Import statement will read the following:

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Add another line below it which tells your project to also invoke the AssemblyInfoTask targets, your project file will then look like:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<
Import Project="$(MSBuildExtensionsPath)\Microsoft\AssemblyInfoTask\Microsoft.VersionNumber.Targets" />
Save the XML file and reload your project now.

Step 3: 
Build the your project and you can see the DLL version skip every time you do a build according to the rules you defined in your AssemblyInfo target file.


Above you see that the file version is 1.0.327.0.  According to my target file I have set up the build number as the current date and the revision number simply increments every time I do a build.  I chose M-DD format as that allows me to go all year without changing my revision scheme.  But wait…this does create a problem when you cycle around a year end.  I thought of this and decided that if we do cycle around a year end I can simply change the minor versioning scheme.  This may not always be feasible depending on company regulations and also depends on your release cycle.  Well all I am saying is, use a scheme which works for you.  Depending on your project and it’s release cycle your versioning scheme will differ too.

Step 4: 
Great, so now you have a fully functioning versioning scheme.  Note that the above versioning propagates through to the DLL via the AssemblyInfo.cs file.

What happens if you use some sort of source control in which your files are locked?

A nice little error will be produced informing you that AssemblyInfo.cs file cannot be edited because it is read-only.  This happened for me as I was using Visual Source Safe, not sure exactly how other source control mechanisms react.  Anyways there is a simple solution: Simply check out AssemblyInfo.cs file.  

Checking out AssemblyInfo.cs

This will work well and good if you have only one project in your entire solution, although here are the problems you’ll come across:
  • What happens if there are 20 projects in your solution, will you be checking out each and every AssemblyInfo.cs file?

  • What happens if there are multiple developers working on different aspects of the solution, who decides when to check in such an AssemblyInfo.cs file?

  • A developer may do multiple builds every day, does that mean we are changing the revision every time?

Simply checking out the AssemblyInfo.cs file won’t work…what we need is some intelligent way of deciding when to kick off this AssemblyInfoTask target.  This is when conditional logic comes in handy.  Simply change your project XML file (we all know how to edit them now) to look like this:

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<
Import Project="$(MSBuildExtensionsPath)\Microsoft\AssemblyInfoTask\Microsoft.VersionNumber.Targets" Condition=" '$(Configuration)' == 'SpecialRelease' " />

All the above statement says is that we only want to invoke our AssemblyInfoTask target when our configuration is “SpecialRelease”.  What is SpecialRelease, it is an arbitrary string.  What is $(Configuration), it is the build configuration you use.  You may already know that every generic C# project comes with two configurations already added for you: Debug and Release.

image

We could have simply used release but I thought that as developers we may use “Release” for some special test cases which may not occur on a “Debug” build.  Anyways, so what we do now is add a new configuration called “SpecialRelease”.  Easily done by clicking on “Configuration Manager…” and the following window opens up:

image

There are two aspects to the above window: Solution Configurations and configurations for each project shown below.  Visual Studio comes in really handy in this case…we can add an entire new solution configuration and automatically propagate it down to every project within our solution.

  1. image  Click new

  2.  image Type in “SpecialRelease” and copy settings from “Release” (because we are only changing the version numbers on every release or whatever you want).  Make sure the check box which says “Create new project configurations” is checked.

  3. Voila! Your configuration manager will now look like:

    image

One more quirk!!!!!!

Doing the above will result in a build error:

image

This is because simply adding new configurations does not guarantee that we have done all the necessary steps to cater for this new configuration.  We edit our project XML again and add a block of XML (After the last PropertyGroup): 

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'SpecialRelease|AnyCPU' ">
<OutputPath>bin\ReleaseQA\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>

You’ll find that doing the above, setting your configuration as “SpecialRelease” and building your solution will still not work.  This is because $(Configuration) will be coming in as an empty string unfortunately.  I haven’t fully researched understood this issue but I did manage a workaround…use MSBuild.exe to build your solution.  Using MSBuild is really trivial, this is how you use it for the above mentioned “ClassLibrary1” solution:

MSbuild.exe “$(SolutionDir)\$(SolutionName)” /t:build /p:Configuration="SpecialRelease"

MSBuild.exe can be found in “C:\WINDOWS\Microsoft.NET\Framework\v3.5\”.  And also remember that there are different versions of MSBuild.exe depending on your .NET version so make sure you build your solution with the correct version of MSBuild.  More help on MSBuild here.

You can do this easily using automated build tools which allow you to checkout groups of files (AssemblyInfo.cs) and check them in afterwards.

1 comment: