OdeToCode IC Logo

Custom Build Numbers in Team Build

Friday, September 29, 2006

The Team Build service in Team Foundation Server includes the current date in the build number by default. To me, the string looks like the random gibberish of a disk head crash.

FooBarBuild_20060928.1
FooBarBuild_20060928.2

I know some people are fond of including the date in a build label, but it's a turn off for me. Build labels have a tendency to show up in many places, and a friendly number is easier on the eyes.

FooBar_2.5.1
FooBar_2.5.2

FooBar_2.5.176

Fortunately, it's easier to change Team Build with a custom MSBuild task. There are some examples of how to do this out there, but none that generate a truly friendly name. Ideally, the task will start with a base name like "FooBar_2.5" and just generate an extra identity digit. One of the properties in play during a team build is LastBuildNumber, which we can inspect during the task and use to generate the build number we want. The code would look something like this:

using System;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;

namespace TfsBuildUtilities
{
  
public class GetBuildNumber : Task
   {
      
public override bool Execute()
      {
        
bool result = true;

        
try
         {
            ValidateProperties();

            
string buildNumber = GetLastBuildNumber();
            _buildNumber = BaseBuildName +
"." + buildNumber;
         }
        
catch (Exception e)
         {
            result =
false;

            BuildErrorEventArgs eventArgs;
            eventArgs =
new BuildErrorEventArgs
                (
                    
"",
                    
"",
                    BuildEngine.ProjectFileOfTaskNode,
                    BuildEngine.LineNumberOfTaskNode,
                    BuildEngine.ColumnNumberOfTaskNode,
                    0, 0,
                    
"GetBuildNumber failed: " + e.Message,
                    
"", ""
                );

            BuildEngine.LogErrorEvent(eventArgs);

            
throw;
         }

        
return result;
      }

      
private string GetLastBuildNumber()
      {
        
string buildNumber = null;

        
// if there is no last build, or it looks as if the
         // last build was not using our name,
         // we will reset to ".1";
         if (String.IsNullOrEmpty(LastBuildNumber) ||
            !LastBuildNumber.StartsWith(BaseBuildName))
         {
            buildNumber =
"1";
         }
        
// otherwise we need to parse out the last number and increment it
         else
         {
            
string[] parts = LastBuildNumber.Split('.');
            
int number = 1;
            
bool parseResult = Int32.TryParse(
                  parts[parts.Length - 1],
                  
out number
               );
            
            
if (parseResult)
            {
               number++;
               buildNumber = number.ToString();
            }
         }

        
if (String.IsNullOrEmpty(buildNumber))
         {
            
throw new InvalidOperationException(
                  
"Could not generate a valid build number"
               );
         }

        
return buildNumber;
      }

      
private void ValidateProperties()
      {
        
if (String.IsNullOrEmpty(BaseBuildName))
         {
            
throw new ArgumentException("BaseBuildName is null");
         }
      }

      [Output]
      
public string BuildNumber
      {
        
get { return _buildNumber; }
        
set { _buildNumber = value; }
      }
      
private string _buildNumber = String.Empty;

      [Required]
      
public string BaseBuildName
      {
        
get { return _baseBuildName; }
        
set { _baseBuildName = value; }
      }
      
private string _baseBuildName;

      
public string LastBuildNumber
      {
        
get { return _lastBuildNumber; }
        
set { _lastBuildNumber = value; }
      }
      
private string _lastBuildNumber;
   }
}

Then, register the task in TFSBuild.proj file. I like to deploy the assembly into the MSBuild extensions path to use from multiple projects.

<UsingTask
 TaskName="TfsBuildUtilities.GetBuildNumber"  
 AssemblyFile=
"$(MSBuildExtensionsPath)\MyDir\TfsBuildUtilities.dll"/>

Finally, in the same .proj file, override the build number target to spit a new build label into the system.

<Target Name = "BuildNumberOverrideTarget" >    
  <GetBuildNumber BaseBuildName=
"FooBar_2.5"
                  LastBuildNumber=
"$(LastBuildNumber)">
    <Output TaskParameter=
"BuildNumber"
             PropertyName=
"BuildNumber"/>      
  </GetBuildNumber>
</Target>

TFS is making the CM job easy…