Enhancing the Build in TFS 2012

TFS has the ability to run automatic builds on the build server, this ensures that your project and solutions build and all their dependencies are near by and functional. I’ve looked all over the internet and found various samples, but none that work with Visual Studio 2012 or in the way I wanted the versioning to work. In this post I pulled together everything I have found whist looking over the Internet, I have tried to keep the same process and names as many of the samples I have found while searching for the solution.

So what we want to do is to change the standard build process to enable us to do automatic versioning of assemblies.  To do this what we need to do is insert some steps in the process just after the source has been retrieved from TFS.  We want the workflow that goes something like this:

Get Latest –> Update AssemblyInfo files with new version –> Build the solution

We need to remember that that when team build gets the code from TFS the files are in read only mode, so we’re need to change the read only flag off, change the files, and then turn it back on.  

What we are not going to do is check the updated assembly info files back into source control.  This means all developer builds will by default have a version number of 1.0.0.0 and build server builds will have an incrementing build number.  This is a useful way to know if someone has tried to deploy code built on their machine instead of code built by the server.

The version number will take the format of 

Major build (Application major version)

Minor release (Application minor release)

Date (month day) (Date of the build)

Incrementing build number (This number will change on every build)

We should end up with a format looking something like this:

3.1.1207.24353

Building the solution

Why do we need a solution, several reasons, we need to be able to build our custom assembly and more importantly we need to be able to edit, change the custom workflow template and then have the ability to compile the workflow to ensure it is built correctly.

1.  Open VS2012 and create a solution with two C# Class LIbrary projects in it.  One for the custom build tasks we’re going to create, and one to hold the process template we’re going to change.  I have called it CustomBuildActivities and CustomBuildProcess, I’ve opted for both standard Class Libraries, as I wanted to go over what references we need to add in order to build our workflow.

 

 

 

 

 

 

 

 

2,  Now go copy the DefaultTemplate.xaml file, which you should find in the Source Control Explorer under the BuildProcessTemplates folder, rename it to something you like and include it in your project.  I have called it ExtendedBuildProcess.xaml.  Once it’s included in the solution change the Build Action to XamlAppDef. 

 

 

 

 

 

 

 

 

 

 

 

At this point if you try compiling you’ll get a lot of missing references.

  • System.Activities
  • System.Xaml
  • System.Activities.Presentation
  • PresentationFramework
  • WindowsBase
  • System.Drawing
  • Microsoft.TeamFoundation.Common
  • Microsoft.TeamFoundation.Build.Client
  • Microsoft.TeamFoundation.VersionControl.Client
  • Microsoft.TeamFoundation.VersionControl.Common
  • Microsoft.TeamFoundation.WorkItemTracking.Client
  • %Program Files%\Microsoft Visual Studio 11.0\Common7\IDE\ReferenceAssemblies\v2.0\
    Microsoft.TeamFoundation.Build.Workflow.dll
  • %Program Files%\Microsoft Visual Studio 11.0\Common7\IDE\PrivateAssemblies\
    Microsoft.TeamFoundation.TestImpact.BuildIntegration.dll
  • %WinDir%\assembly\GAC_MSIL\Microsoft.TeamFoundation.TestImpact.Client\11.0.0.0__b03f5f7f11d50a3a\
    Microsoft.TeamFoundation.TestImpact.Client.dll

Quite a lot I know, but I wanted to take you though everything that is needed.

Creating our Custom Activity

The first custom activity we are going to build will be the one we use to toggles the read only flags on AssemblyInfo files.  In your CustomBuildActivities project add a new Class called SetReadOnlyFlag.  We’ll use this to toggle the read only bits for our AssemblyInfo files later on.

We’ll need to add a few references first

  • System.Activities
  • Microsoft.TeamFoundation.Build.Client
  • Microsoft.TeamFoundation.VersionControl.Client
  • System.IO

 

using System.Activities;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using System.IO;
 
namespace CustomBuildActivities
{
 [BuildActivity(HostEnvironmentOption.Agent)]
 public sealed class SetReadOnlyFlag : CodeActivity
 {
  [RequiredArgument]
  public InArgument<string> FileMask { get; set; }
 
  [RequiredArgument]
  public InArgument<bool> ReadOnlyFlagValue { get; set; }
 
  [RequiredArgument]
  public InArgument<Workspace> Workspace { get; set; }
 
  protected override void Execute(CodeActivityContext context)
  {
   var fileMask = context.GetValue(FileMask);
   var workspace = context.GetValue(Workspace);
   var readOnlyFlagValue = context.GetValue(ReadOnlyFlagValue);
 
   foreach (var folder in workspace.Folders)
   {
    foreach (var file in Directory.GetFiles(folder.LocalItem, fileMask, SearchOption.AllDirectories))
    { 
     var attributes = File.GetAttributes(file);
     if (readOnlyFlagValue)
      File.SetAttributes(file, attributes | FileAttributes.ReadOnly);
     else
      File.SetAttributes(file,attributes & ~FileAttributes.ReadOnly);
    }
   }
  }
 }
}

This is the first Custom Activity done

Update Assembly VersionInfo Custom Activity

This Custom Activity is to change all the Version numbers to the format we are after, e.g.  3.1.1207.32454

using System;
using System.Activities;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.TeamFoundation.Build.Client;

namespace CustomBuildActivities
{
    [BuildActivity(HostEnvironmentOption.Agent)]
    public sealed class UpdateAssemblyVersionInfo : CodeActivity
    {
        [RequiredArgument]
        public InArgument<string> AssemblyInfoFileMask { get; set; }

        [RequiredArgument]
        public InArgument<string> SourcesDirectory { get; set; }

        [RequiredArgument]
        public InArgument<int> Major { get; set; }

        [RequiredArgument]
        public InArgument<int> Minor { get; set; }

        protected override void Execute(CodeActivityContext context)
        {
            var sourcesDirectory = context.GetValue(SourcesDirectory);
            var major = context.GetValue(Major);
            var minor = context.GetValue(Minor);
            var assemblyInfoFileMask = context.GetValue(AssemblyInfoFileMask);
            var buildDate = int.Parse(string.Format("{0}{1}", DateTime.Now.Month, DateTime.Now.Day));
            var newVersion = new Version(major, minor, buildDate);

            foreach (var file in Directory.EnumerateFiles(sourcesDirectory, assemblyInfoFileMask, SearchOption.AllDirectories))
            {
                var text = File.ReadAllText(file);
                // we want to find 'AssemblyVersion("1.0.0.0")' etc
                foreach (var attribute in new[] { "AssemblyVersion", "AssemblyFileVersion" })
                {
                    var regex = new Regex(attribute + @"\(""\d+\.\d+\.\d+\.\d+""\)");
                    var match = regex.Match(text);
                    if (match.Success) text = regex.Replace(text, attribute + "(\"" + newVersion + "\")");
                }
            }
        }
    }
}

Okay that is the coding done, your project should build without and errors, warnings or message, perfect and clean project.

Now the fun really starts

Updating the Extended Build Process Workflow

Before we start can you make sure the project builds without an errors.

 

 

 

 

When you open your ExtendedBuildProcess.xaml on the toolbar you should find the two new CustomBuildActivites (SetReadOnlyFlag and UpdateAssemblyVersionInfo)

Now comes the tricky bit finding in the workflow where to drop the Custom Activities.  You can search the Workflow manually or use the Find option and look for “Get Workspace”, you should end up in the section of the work flow that looks something like this:

 

 

 

 

 

 

 

 

 

 

 

Now add a sequence activity after the get workspace activity:

 

 

 

 

 

 

Now add your two new Custom Activities to the Sequence activity, with the SetReadOnlyFlag going either side of the UpdateAssemblyVersionInfo.

 

 

 

 

 

 

 

 

Set Activity Properties

For each of the new Customer Activities we are going to have to set the required parameters

 

  

 

 

 

Select the Readonly Activity and then look at the properties

 

 

 

 

 

Before we set the properties we’ll need to set the Arguments that are coming in to the Workflow, so go to the Arguments tab at the bottom of the workflow.

 

 

 

 

Click in the Create Argument box and add a new argument called AssemblyInfoMask and give it a default value of “AssemblyInfo.*”

Create a further two arguments AssemblyMajor and AssemblyMinor setting them both to Int32 with 1 and 2 default values.

 

You don’t have to do this process, next but when you do it will appear in the Build process in the correct section.  Go to the metadata argument a few lines above and click the ellipsis […] button.  You will see a window appear where we can set values that will let Visual Studio know how to display this argument to the end user.  Do this for all three arguments. 

 

 

 

 

 

 

 

 

 

 

 

Okay now we have set the argument we can set the properties of the Activity.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Okay that should be it.  So build the solution just to make sure everything is DONE!

Next just follow the steps to store your new custom activities and then add them in to your build process and you’re done.

I would not have been able to complete and understand this process without the help of the following two web pages. 

Customize Team Build 2010 by Ewald Hofman

How To: Versioning Builds With TFS 2010 by Richard Banks