Modify Build Failure Work Item in TFS 2010 Build

The default behaviour in TFS Team Build (all versions) is to create a bug work item when a build fails. This main benefit of this is that you get a work item for something that needs to be done, namely to fix the build!. When the developer responsible for the build failure has fixed the problem, he/she can associated that check-in with the work item that was created from the previous build failure.

In TFS 2005/2008 you could modify the information in the created work item by changing some predefined properties in the TFSBuild.proj file:

 

    <!--  WorkItemType
     The type of the work item created on a build failure. 
     -->
    <WorkItemType>Bug</WorkItemType>

    <!--  WorkItemFieldValues
     Fields and values of the work item created on a build failure.
     
     Note: Use reference names for fields if you want the build to be resistant to field name 
     changes. Reference names are language independent while friendly names are changed depending 
     on the installed language. For example, "System.Reason" is the reference name for the "Reason" 
     field.
     -->
    <WorkItemFieldValues>System.Reason=Build Failure;System.Description=Start the build using Team Build</WorkItemFieldValues>

    <!--  WorkItemTitle
     Title of the work item created on build failure.
     -->
    <WorkItemTitle>Build failure in build:</WorkItemTitle>

    <!--  DescriptionText
     History comment of the work item created on a build failure. 
     -->
    <DescriptionText>This work item was created by Team Build on a build failure.</DescriptionText>

    <!--  BuildLogText
     Additional comment text for the work item created on a build failure.
     -->
    <BuildlogText>The build log file is at:</BuildlogText>

    <!--  ErrorWarningLogText
     Additional comment text for the work item created on a build failure. 
     This text will only be added if there were errors or warnings.
     -->
    <ErrorWarningLogText>The errors/warnings log file is at:</ErrorWarningLogText>

 

In TFS 2010, with Windows Workflow, you change this by modifying the properties on the OpenWorkItem activity. The hardest part of this is to actually find where this activity is located in the build process workflow. If you open the build definition in XAML you can just search for OpenWorkItem. If you use the designer you need to click your way down to the Catch section of the Try to Compile the Project sequence:

image

To change the default values of the created work item, select the Created Work Item activity and look at the Properties window:

image

Note the CustomFields property which is a dictionary with key (work item field name) and value. If you add custom fields to your work item you can add a value for it here by adding a new entry in the dictionary.

Creating a Build Definition using the TFS 2010 API

*** UPDATE 2010-08-17 ** Several people have asked me for a complete sample application, so I have put this together and it is available here:
http://cid-ee034c9f620cd58d.office.live.com/self.aspx/BlogSamples/CreateTFSBuildDefinition.zip

****

In this post I will show how to create a new build definition in TFS 2010 using the TFS API. When creating a build definition manually, using Team Explorer, the necessary steps are lined
out in the New Build Definition Wizard:

 

image

 

So, lets see how the code looks like, using the same order. To start off, we need to connect to TFS and get a reference to the IBuildServer object:

TfsTeamProjectCollection server = newTfsTeamProjectCollection(newUri(“http://<tfs>:<port>/tfs”));
server.EnsureAuthenticated();
IBuildServer buildServer = (IBuildServer) server.GetService(typeof (IBuildServer));

General
First we create a IBuildDefinition object for the team project and set a name and description for it:

var buildDefinition = buildServer.CreateBuildDefinition(teamProject);
buildDefinition.Name = “TestBuild”;
buildDefinition.Description = “description here…”;

Trigger
Next up, we set the trigger type. For this one, we set it to individual which corresponds to the Continuous Integration – Build each check-in trigger option

buildDefinition.ContinuousIntegrationType = ContinuousIntegrationType.Individual;

Workspace
For the workspace mappings, we create two mappings here, where one is a cloak. Note the user of $(SourceDir) variable, which is expanded by Team Build into the sources directory when running the build.

buildDefinition.Workspace.AddMapping(“$/Path/project.sln”, “$(SourceDir)”, WorkspaceMappingType.Map);
buildDefinition.Workspace.AddMapping(“$/OtherPath/”, “”, WorkspaceMappingType.Cloak);

Build Defaults
In the build defaults, we set the build controller and the drop location. To get a build controller, we can (for example) use the GetBuildController method to get an existing build controller by name:

buildDefinition.BuildController = buildServer.GetBuildController(buildController);
buildDefinition.DefaultDropLocation = @\SERVERDropTestBuild;

Process
So far, this wasy easy. Now we get to the tricky part. TFS 2010 Build is based on Windows Workflow 4.0. The build process is defined in a separate .XAML file called a Build Process Template. By default, every new team team project containtwo build process templates called DefaultTemplate and UpgradeTemplate. In this sample, we want to create a build definition using the default template. We use te QueryProcessTemplates method to get a reference to the default for the current team project

 
//Get default template
var defaultTemplate = buildServer.QueryProcessTemplates(teamProject).Where(p => p.TemplateType == ProcessTemplateType.Default).First();
buildDefinition.Process = defaultTemplate;

 

There are several build process templates that can be set for the default build process template. Only one of these are required, the ProjectsToBuild parameters which contains the solution(s) and configuration(s) that should be built. To set this info, we use the ProcessParameters property of thhe IBuildDefinition interface. The format of this property is actually just a serialized dictionary (IDictionary<string, object>) that maps a key (parameter name) to a value which can be any kind of object. This is rather messy, but fortunately, there is a helper class called WorkflowHelpers inthe Microsoft.TeamFoundation.Build.Workflow namespace, that simplifies working with this persistence format a bit. The following code shows how to set the BuildSettings information for a build definition:

//Set process parameters
varprocess = WorkflowHelpers.DeserializeProcessParameters(buildDefinition.ProcessParameters);

//Set BuildSettings properties
BuildSettings settings = newBuildSettings();
settings.ProjectsToBuild = newStringList(“$/pathToProject/project.sln”);
settings.PlatformConfigurations = newPlatformConfigurationList();
settings.PlatformConfigurations.Add(newPlatformConfiguration(“Any CPU”, “Debug”));
process.Add(“BuildSettings”, settings);

buildDefinition.ProcessParameters = WorkflowHelpers.SerializeProcessParameters(process);

The other build process parameters of a build definition can be set using the same approach

 

Retention  Policy
This one is easy, we just clear the default settings and set our own:

buildDefinition.RetentionPolicyList.Clear();
buildDefinition.AddRetentionPolicy(BuildReason.Triggered, BuildStatus.Succeeded, 10, DeleteOptions.All);
buildDefinition.AddRetentionPolicy(BuildReason.Triggered, BuildStatus.Failed, 10, DeleteOptions.All);
buildDefinition.AddRetentionPolicy(BuildReason.Triggered, BuildStatus.Stopped, 1, DeleteOptions.All);
buildDefinition.AddRetentionPolicy(BuildReason.Triggered, BuildStatus.PartiallySucceeded, 10, DeleteOptions.All);

Save It!
And we’re done, lets save the build definition:

buildDefinition.Save();

That’s it!

 

 

Getting TF215097 error after modifying a build process template in TFS Team Build 2010

When embracing Team Build 2010, you typically want to define several different build process templates for different scenarios. Common examples here are CI builds, QA builds and release builds. For example, in a contiuous build you often have no interest in publishing to the symbol store, you might or might not want to associate changesets and work items etc. The build server is often heavily occupied as it is, so you don’t want to have it doing more that necessary. Try to define a set of build process templates that are used across your company. In previous versions of TFS Team Build, there was no easy way to do this. But in TFS 2010 it is very easy so there is no excuse to not do it! 🙂

 

I ran into a scenario today where I had an existing build definition that was based on our release build process template. In this template, we have defined several different build process parameters that control the release build. These are placed into its own sectionin the Build Process Parameters editor. This is done using the ProcessParameterMetadataCollection element, I will explain how this works in a future post.

 

image

I won’t go into details on these parametes, the issue for this blog post is what happens when you modify a build process template so that it is no longer compatible with the build definition, i.e. a breaking change. In this case, I removed a parameter that was no longer necessary. After merging the new build process template to one of the projects and queued a new release build, I got this error:

 

TF215097: An error occurred while initializing a build for build definition <Build Definition Name>:
The values provided for the root activity’s arguments did not satisfy the root activity’s requirements: ‘DynamicActivity’:
The following keys from the input dictionary do not map to arguments and must be removed: <Parameter Name>. 
Please note that argument names are case sensitive. Parameter name: rootArgumentValues

<Parameter Name> was the parameter that I removed so it was pretty easy to understand why the error had occurred. However, it is not entirely obvious how to fix the problem. When open the build definition everything looks OK, the removed build process parameter is not there, and I can open the build process template without any validation warnings.

The problem here is that all settings specific to a particular build definition is stored in the TFS database. In TFS 2005, everything that was related to a build was stored in TFS source control in files (TFSBuild.proj, WorkspaceMapping.xml..). In TFS 2008, many of these settings were moved into the database. Still, lots of things were stored in TFSBuild.proj, such as the solution and configuration to build, wether to execute tests or not. In TFS 2010, all settings for a build definition is stored in the database. If we look inside the database we can see what this looks like. The table tbl_BuildDefinition contains all information for a build definition. One of the columns is called ProcessParameters and contains a serialized representation of a Dictionary that is the underlying object where these settings are stoded. Here is an example:

 

<Dictionary x:TypeArguments="x:String, x:Object" xmlns="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:mtbwa="clr-namespace:Microsoft.TeamFoundation.Build.Workflow.Activities;assembly=Microsoft.TeamFoundation.Build.Workflow" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">   

<mtbwa:BuildSettings x:Key="BuildSettings" ProjectsToBuild="$/PathToProject.sln">     
    <mtbwa:BuildSettings.PlatformConfigurations>       
        <mtbwa:PlatformConfigurationList Capacity="4">         
        <mtbwa:PlatformConfiguration Configuration="Release" Platform="Any CPU" />       
        </mtbwa:PlatformConfigurationList>     
    </mtbwa:BuildSettings.PlatformConfigurations>   
</mtbwa:BuildSettings>   
<mtbwa:AgentSettings x:Key="AgentSettings" Tags="Agent1" />   
<x:Boolean x:Key="DisableTests">True</x:Boolean>   
<x:String x:Key="ReleaseRepositorySolution">ERP</x:String>   
<x:Int32 x:Key="Major">2</x:Int32>   
<x:Int32 x:Key="Minor">3</x:Int32> 
</Dictionary>

Here we can see that it is really only the non-default values that are persisted into the databasen. So, the problem in my case was that I removed one of the parameteres from the build process template, but the parameter and its value still existed in the build definition database. The solution to the problem is to refresh the build definition and save it. In the process tab, there is a Refresh button that will reload the build definition and the process template and synchronize them:

 

image

After refreshing the build definition and saving it, the build was running successfully again.

Implementing Release Notes in TFS Team Build 2010

In TFS Team Build (all versions), each build is associated with changesets and work items. To determine which changesets that should be associated with the current build, Team Build finds the label of the “Last Good Build” an then aggregates all changesets up unitl the label for the current build. Basically this means that if your build is failing, every changeset that is checked in will be accumulated in this list until the build is successful.

All well, but there uis a dimension missing here, regarding to releases. Often you can run several release builds until you actually deploy the result of the build to a test or production system. When you do this, wouldn’t it be nice to be able to send the customer a nice release note that contain all work items and changeset since the previously deployed version?

At our company, we have developed a Release Repository, which basically is a siple web site with a SQL database as storage. Every time we run a Release Build, the resulting installers, zip-files, sql scripts etc, gets pushed into the release repositor together with the relevant build information. This information contains things such as start time, who triggered the build etc. Also, it contains the associated changesets and work items.

When deploying the MSI’s for a new version, we mark the build as Deployed in the release repository. The depoyed status is stored in the release repository database, but it could also have been implemented by setting the Build Quality for that build to Deployed.

When generating the release notes, the web site simple runs through each release build back to the previous build that was marked as Deplyed, and aggregates the work items and changesets:

Here is a sample screenshot on how this looks for a sample build/application

image

The web site is available both for us and also for the customers and testers, which means that they can easily get the latest version of a particular application and at the same time see what changes are included in this version.
There is a lot going on in the Release Build Process that drives this in our TFS 2010 server, but in this post I will show how you can access and read the changeset and work item information in a custom activity.

Since Team Build associates changesets and work items for each build, this information is (partially) available inside the build process template. The Associate Changesets and Work Items for non-Shelveset Builds activity (located inside the Try  Compile, Test, and Associate Changesets and Work Items activity) defines and populates a variable called associatedWorkItems

 

image

You can see that this variable is an IList containing instances of the Changeset class (from the Microsoft.TeamFoundation.VersionControl.Client namespace). Now, if you want to access this variable later on in the build process template, you need to declare a new variable in the corresponding scope and the assign the value to this variable. In this sample, I declared a variable called assocChangesets in the RunAgent sequence, which basically covers the whol compile, test and drop part of the build process:

 

image

Now, you need to assign the value from the AssociatedChangesets to this variable. This is done using the Assign workflow activity:

 

image

Now you can add a custom activity any where inside the RunAgent sequence and use this variable. NB: Of course your activity must place somewhere after the variable has been poplated. To finish off, here is code snippet that shows how you can read the changeset and work item information from the variable.

 

First you add an InArgumet on your activity where you can pass i the variable that we defined.

        [RequiredArgument]
        public InArgument<IList<Changeset>> AssociatedChangesets { get; set; }

Then you can traverse all the changesets in the list, and for each changeset use the WorkItems property to get the work items that were associated in that changeset:

            foreach (Changeset ch in associatedChangesets)
            {
                // Add change
                theChangesets.Add(
                    new AssociatedChangeset(ch.ChangesetId,
                                            ch.ArtifactUri,
                                            ch.Committer,
                                            ch.Comment,
                                            ch.ChangesetId));

                foreach (var wi in ch.WorkItems)
                {
                        theWorkItems.Add(
                            new AssociatedWorkItem(wi["System.AssignedTo"].ToString(),
                                                   wi.Id,
                                                   wi["System.State"].ToString(),
                                                   wi.Title,
                                                   wi.Type.Name,
                                                   wi.Id,
                                                   wi.Uri));

                }
            }

NB: AssociatedChangeset and AssociatedWorkItem are custom classes that we use internally for storing this information that is eventually pushed to the release repository.