Versioning Code in TFS – Revised


This is a revised and combined post covering the two previous posts, Versioning Code in TFS Part 1 & Part 2. If you haven’t read those two posts, then this revised post is all you need to read. If you have read the two previous posts, then although similar, I recommend you read this revised post as it contains important corrections, updates and clarifications. Note that this post mostly relates to TFS 2008, though the concepts implied are not tied to a specific version of TFS.

____________________________________________________________________________________

The Problem

I’ve lost count of the number of blog posts, forum questions and internal emails that I have seen regarding problems with versioning assemblies in TFS.

I believe the root cause of the problem is that many users have tried to migrate their existing versioning logic and code to work with TFS, blinded by the wealth of functionality that TFS has brought to the development process.

The Truth
  • TFS provides the basic fundamentals of iterative versioning out the box.

Some Basics
What is Versioning?

Versioning refers to the process whereby code is compiled and ‘stamped’ with a version number that can distinguish it from previous or future builds of that code.

What is a version number?

There are two version numbers in .Net, the AssemblyVersion and the AssemblyFileVersion. Both follow the same format:

  • <major version>.<minor version>.<build number>.<revision>

My first piece of advice is that you don’t keep the two in sync. The AssemblyVersion should rather be of the format:

  • <major version>.<minor version>.0.0

The reason for this is that strong name signatures include the AssemblyVersion, which means that if you release an updated assembly, the code that references it will need to be updated too (unless you go the publisher policy route, but let’s try keeping things simple).

The AssemblyFileVersion is the attribute that should be updated with every build you perform, and therein lays the problem. How?

I would estimate that in 99% of builds, the format of the build number will be

  • <Static major version>.<Static minor version>.< Calculated build number>.<Iterative revision>

The Static parts are easy and they will most likely equate to those used in the AssemblyVersion. The calculated part is really up to you. It could be a date format e.g. mmdd or possibly the number of days since a given milestone date etc. The iterative part is where the trouble lies.

All versioning tasks need to provide the same essential logic. They need to calculate your build number and provide a unique incremental value for every build. To ensure that the incremental value is unique, most versioning solutions check out a file, alter it and check the file back in.

Lots of people including myself first used the AssemblyInfo task (which has now been included in the MSBuild Extension Pack), and encountered numerous painful ‘cannot determine the workspace’ errors, or you were likely to be caught by check-in policies or duplicate attributes. All issues can be resolved, but too many people have had a hard time using this task for versioning. It’s a great task, but not for versioning.

If you managed to get around the AssemblyInfo task issues, or your own problems with creating a task that checked out a file, incremented the value and then checked it in again, well done… and welcome to continuous merging issues! TFS 2005 / 2008 don’t support multiple pending merge entries for a single file, so you have to perform continuous merging. This isn’t a big issue with versioning tasks, however the problem with continuously checking out and in the versioning file(s) is that you get loads of noise on the pending merges table and if you do merge multiple version files, you will likely have to manually resolve the final version.

My second piece of advice is don’t check your versioning files in and out during the build process. As discussed above, it creates noise and merge hassles. As long as you’re versioning logic can be related to the TFS Build Number, I see no benefit in having this source control operation take place.

The Solution

TFS provides a build number for every build it initiates. The format is

  • <build name>_<year><month><day>.<revision>

e.g.

  • MSSDCDaily_20080402.1

To accomplish versioning, use the <revision> part of the build number. TFS will provide an incremented revision for every build, whether the previous build succeeded or not. All you need to do is provide a task that takes the build number as input, then emits your version number as output. The logic within the task is up to you.

Because your build is based on the build number, you can easily locate the label associated with the build.

In summary
  • Don’t increment the AssemblyVersion
  • Don’t perform source control operations for versioning
  • Do increment the AssemblyFileVersion
  • Do base your build number iteration on the TFS Build Number
  • Do base your build number calculation on the TFS Build Number
Implementing the Solution

If you don’t want to write your own task to implement versioning, the MSBuild Extension Pack provides various tasks to assist you with versioning.

AssemblyInfo Task [MSBuild.ExtensionPack.Framework.AssemblyInfo]

The original AssemblyInfo task has been added to the MSBuild Extension Pack and received various small tweaks. As mentioned, I’m not a fan of using this for versioning, but if you must, then below is an abbreviated sample:

<Project DefaultTargets=”DesktopBuild” xmlns=”http://schemas.microsoft.com/developer/msbuild/2003″ ToolsVersion=”3.5″>
    <Import Project=”$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets” />
    <!– ADD THIS LINE –>
    <Import Project=”$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.VersionNumber.targets”/>

    <!– ADD AND CONFIGURE THIS PROPERTY GROUP –>
    <PropertyGroup>
        <!– ASSEMBLY VERSION PROPERTIES.–>
        <AssemblyMajorVersion>1</AssemblyMajorVersion>
        <AssemblyFileMajorVersion>0</AssemblyFileMajorVersion>
        <!– TF.EXE –>
        <TF>&quot;$(TeamBuildRefPath)\..\tf.exe&quot;</TF>
        <!– ASSEMBLYINFO FILE SPEC –>
        <AssemblyInfoSpec>AssemblyInfo.cs</AssemblyInfoSpec>
    </PropertyGroup>

    <!– SET THIS TO NON-EXISTENT FILE TO FORCE REBUILD. –>
    <ItemGroup>
        <IntermediateAssembly Include=”$(SolutionRoot)\foobar.dll”/>
    </ItemGroup>

    <!– OVERRIDE AFTERGET–>
    <Target Name=”AfterGet” Condition=”‘$(IsDesktopBuild)’!=’true'”>

        <!– SET THE ASSEMBLYINFOFILES ITEMS DYNAMICALLY –>
        <CreateItem Include=”$(SolutionRoot)\**\$(AssemblyInfoSpec)”>
            <Output ItemName=”AssemblyInfoFiles” TaskParameter=”Include” />
        </CreateItem>

        <Exec WorkingDirectory=”$(SolutionRoot)” Command=”$(TF) checkout /recursive &quot;@(AssemblyInfoFiles)&quot;”/>
    </Target>

    <!– OVERRIDE AFTERCOMPILE–>
    <Target Name=”AfterCompile” Condition=”‘$(IsDesktopBuild)’!=’true'”>
        <Exec WorkingDirectory=”$(SolutionRoot)” Command=”$(TF) checkin /comment:&quot;Auto-Build: Version Update&quot; /noprompt /override:&quot;Auto-Build: Version Update&quot; /recursive &quot;@(AssemblyInfoFiles)&quot;” />
    </Target>

    <!– IN CASE OF BUILD FAILURE, THE AFTERCOMPILE TARGET IS NOT EXECUTED. UNDO THE CHANGES –>
    <Target Name=”BeforeOnBuildBreak” Condition=”‘$(IsDesktopBuild)’!=’true'”>
        <Exec WorkingDirectory=”$(SolutionRoot)” Command=”$(TF) undo /noprompt /recursive &quot;@(AssemblyInfoFiles)&quot;” />
    </Target>
</Project>

TFSVersion Task [MSBuild.ExtensionPack.VisualStudio.TfsVersion]

The TFSVersion task provides the more lightweight versioning that I have described and is what I use on all projects. Please Note: The output of GetVersion should not be used to change the $(BuildNumber).

<Project DefaultTargets=”DesktopBuild” xmlns=”http://schemas.microsoft.com/developer/msbuild/2003″ ToolsVersion=”3.5″>
    <Import Project=”$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets” />
    <!– ADD THIS LINE –>
    <Import Project=”$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.tasks”/>

    <!– DEFINE WHICH FILES TO VERSION –>
    <ItemGroup>
        <FilesToVersion Include=”$(SolutionRoot)\Source\Common\VersionInfo.cs” />
    </ItemGroup>

    <!– OVERRIDE BUILDNUMBEROVERRIDETARGET –>
    <Target Name=”BuildNumberOverrideTarget” Condition=”‘$(IsDesktopBuild)’!=’true'”>
        <MSBuild.ExtensionPack.VisualStudio.TfsVersion TaskAction=”GetVersion” UseUtcDate=”true” BuildName=”$(BuildDefinition)” TfsBuildNumber=”$(BuildNumber)” VersionFormat=”Elapsed” StartDate=”1 Jan 2009″ VersionTemplateFormat=”0.0.1000.0″ Major=”3″ Minor=”1″>
            <Output TaskParameter=”Version” PropertyName=”CodeBuildNumber” />
        </MSBuild.ExtensionPack.VisualStudio.TfsVersion>
    </Target>

    <!– OVERRIDE AFTERGET –>
    <Target Name=”AfterGet” Condition=”‘$(IsDesktopBuild)’!=’true’ “>
        <MSBuild.ExtensionPack.VisualStudio.TfsVersion TaskAction=”SetVersion” Files=”%(FilesToVersion.Identity)” Version=”$(CodeBuildNumber)”/>
    </Target>
</Project>

SQLVersion Task [MSBuild.ExtensionPack.SqlServer.SqlVersion]

The SqlVersion task provides the ability to manage multiple build versions in a simple database table. Please Note: The output of GetVersion should not be used to change the $(BuildNumber).

<Project DefaultTargets=”DesktopBuild” xmlns=”http://schemas.microsoft.com/developer/msbuild/2003″ ToolsVersion=”3.5″>
    <Import Project=”$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets” />
    <!– ADD THIS LINE –>
    <Import Project=”$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.tasks”/>

    <!– DEFINE WHICH FILES TO VERSION –>
    <ItemGroup>
        <FilesToVersion Include=”$(SolutionRoot)\Source\Common\VersionInfo.cs” />
    </ItemGroup>

    <!– OVERRIDE BUILDNUMBEROVERRIDETARGET –>
    <Target Name=”BuildNumberOverrideTarget”>
        <PropertyGroup>
            <FieldToIncrement Condition=” ‘$(IncrementalBuild)’==’true’ “>4</FieldToIncrement>
            <FieldToIncrement Condition=” ‘$(IncrementalBuild)’!=’true’ “>3</FieldToIncrement>
        </PropertyGroup>

        <!– Get the latest build number and increment as necessary –>
        <MSBuild.ExtensionPack.SqlServer.SqlVersion Taskaction=”GetVersion” BuildName=”V9 Production Build” FieldToIncrement=”$(FieldToIncrement)” DatabaseName=”Mike”>
            <Output TaskParameter=”Major” PropertyName=”BuildMajor” />
            <Output TaskParameter=”Minor” PropertyName=”BuildMinor” />
            <Output TaskParameter=”Build” PropertyName=”BuildBuild” />
            <Output TaskParameter=”Revision” PropertyName=”BuildRevision” />
        </MSBuild.ExtensionPack.SqlServer.SqlVersion>
        <PropertyGroup>
            <CodeBuildNumber>$(BuildMajor).$(BuildMinor).$(BuildBuild).$(BuildRevision)</CodeBuildNumber>
        </PropertyGroup>
    </Target>

    <!– OVERRIDE AFTERGET –>
    <Target Name=”AfterGet” Condition=”‘$(IsDesktopBuild)’!=’true’ “>
        <MSBuild.ExtensionPack.VisualStudio.TfsVersion TaskAction=”SetVersion” Files=”%(FilesToVersion.Identity)” Version=”$(CodeBuildNumber)”/>
    </Target>
</Project>

Version 3.5.5.0 of the MSBuild Extension Pack will provide an additional file based versioning task, but the above three, especially the TFSVersion task should meet most needs.

I’ve disabled comments on this blog as the spam filters on live spaces are not sufficient. If you have any feedback, please use the feedback URL found in the MSBuild Extension Pack help.

Mike

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s