Share via



March 2009

Volume 24 Number 03

 

Team System - Team Build 2008 Customization

By Brian A. Randell | March 200

Contents

Properties
Getting Information about What's Going On
Changes
Custom Tasks

In my November 2008 column on Team Foundation Build 2008, I discussed what Team Build 2008 is, why you care, how to create a build, and how to use the new API to program the Team Build service. In this column, I'm going to cover how you can modify the default behavior of Team Build, extend your builds with custom tasks, and make use of the enhancements added as part of Team Build 2008 SP1.

Before you start customizing your builds, however, it's important to remember that Microsoft built Team Build on top of the Microsoft Build Engine (MSBuild), a core component of the Microsoft .NET Framework 2.0. While I will provide some minor refresher points related to how MSBuild executes a build, you should have a decent understanding to follow along. For a good review, you can read the June 2006 MSDN article on MSBuild, " Compile Apps Your Way With Custom Tasks For The Microsoft Build Engine."

As I've mentioned before, I encourage you to explore Team System customization, and in this case Team Build, in a nonproduction environment first. A good way, of course, is to use one of the Microsoft-provided evaluation virtual machines. I updated the images just before Christmas 2008, so you'll find fresh ones available with SP1 installed as well as other updates. The images are available for both Virtual PC and Hyper-V.

A number of moving parts are involved in executing a build. Figure 1(from the MSDN article " Team Foundation Build Overview") provides a pictorial view of all the pieces. In addition, I described the general steps of what occurs in my previous column. What we're most concerned with here is when MSBuild gets involved. The Team Build service (TFSBuildService.exe) creates an MSBuild process, passing the location of a file named TFSBuild.tbrsp. You'll find the file in the BuildType subfolder under your Build Agent's working directory.

fig01.gif

Figure 1 Overview of Build Components

The Team Build 2008 SP1 service generates TFSBuild.tbrsp dynamically. The file consists of data stored in the Team Build databases, Team Build controlled parameters, and the contents (if any) of your build's TFSBuild.rsp file. Figure 2is an example of a test build I created.

Figure 2 Example of a Test Build

### Begin Team Build Generated Arguments ### /nodeReuse:false /m:1 /nologo /noconsolelogger /dl:BuildLogger,"C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ PrivateAssemblies\Microsoft.TeamFoundation.Build.Server.Logger.dll"; "BuildUri=vstfs:///Build/Build/45; TFSUrl=https://tfsrtm08:8080/; TFSProjectFile=C:\Builds\ExploreTeamBuild\Sysinfo\BuildType\TFSBuild.proj;ServerUICulture=1033; LogFilePerProject=False; "*BuildForwardingLogger,"C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ PrivateAssemblies\Microsoft.TeamFoundation.Build.Server.Logger.dll"; "BuildUri=vstfs:///Build/Build/45;TFSUrl=https://tfsrtm08:8080/; TFSProjectFile=C:\Builds\ExploreTeamBuild\Sysinfo\BuildType\TFSBuild.proj;ServerUICulture=1033;" /fl /flp:logfile=BuildLog.txt;encoding=Unicode; /p:BuildDefinition="Sysinfo" /p:BuildDefinitionId="3" /p:DropLocation="\\localhost\Drops" /p:BuildProjectFolderPath="$/ExploreTeamBuild/TeamBuildTypes/Sysinfo" /p:BuildUri="vstfs:///Build/Build/45" /p:TeamFoundationServerUrl="https://tfsrtm08:8080/" /p:TeamProject="ExploreTeamBuild" /p:SourcesSubdirectory="Sources" /p:BinariesSubdirectory="Binaries" /p:TestResultsSubdirectory="TestResults" /p:SourceGetVersion="C244" /p:LastGoodBuildLabel="Sysinfo_20090104.2@$/ExploreTeamBuild" /p:LastBuildNumber="Sysinfo_20090104.2" /p:LastGoodBuildNumber="Sysinfo_20090104.2" /p:NoCICheckInComment="***NO_CI***" /p:IsDesktopBuild="false" /p:TeamBuildRefPath="C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies" /t:EndToEndIteration TFSBuild.proj ### End Team Build Generated Arguments ### ### Begin Checked In TfsBuild.rsp Arguments ### # This is a response file for MSBuild # Add custom MSBuild command line options in this file ### End Checked In TfsBuild.rsp Arguments ###

If you create a build under Team Build 2005, it stores much of the build data in a set of files under version control at a fixed location. The most interesting of these files is TFSBuild.proj, an XML file that adheres to the MSBuild project file schema. When you create a build using Team Foundation Server 2008, Team Build 2008 stores most of the information you provide inside the TFS databases. However, you customize the build process the same way regardless of version: you edit the TFSBuild.proj file and write custom MSBuild tasks (if necessary).

In Team Build 2008 SP1, you can access the TFSBuild.proj file by right-clicking on a build definition's name in the Team Explorer window and then selecting View Build Configuration. In versions prior to SP1, you open the Source Control Explorer and manually navigate to the location you specified when running the build definition wizard.

The build definition stored in the database defines what should be built and when. The TFSBuild.proj file defines how Team Build should execute the build. You'll recall from my earlier column that you can create a new TFSBuild.proj file as you create a new build definition, or you can point to an existing file already under version control. For MSBuild to do its magic, in conjunction with various Team Build assemblies, it needs to know what to build and in what order, as well as about any additional tasks. If you examine Figure 2, you'll note that Team Build passes TFSBuild.proj as the name of the project file that MSBuild should process. MSBuild treats all the /p arguments as global properties.

If you examine a Team Build 2008 TFSBuild.proj file for a build that builds a single solution (both debug and release configuration types) and does not run any tests, you'll find a number of sections. This XML file is very similar to a Visual Basic or C# project file. The XML file defines build properties, artifacts to include as part of the build process, and operations to perform. The file groups properties together into <PropertyGroup> elements. It tracks artifacts like solutions in <ItemGroup> elements. In addition, it describes operations that it should execute together via <Target> elements.

Targets have a Name attribute, and they group tasks together in a particular order. You define tasks in CLR-dependent assemblies by creating public classes that either implement ITask or inherit from the abstract base class Task (or any other derived types, such as ToolTask). ITask, Task, and ToolTask live in the Microsoft.Build.Utilities.dll assembly. In your TFSBuild.proj file, after the XML declaration, you'll find the root <Project> element required for MSBuild:

<Project DefaultTargets="DesktopBuild" xmlns="https://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">

The DefaultTargets attribute specifies that MSBuild should use the DesktopBuild target. However, this only occurs if you execute the build using MSBuild directly. If you return to Figure 2, you'll see that Team Build tells MSBuild to consider the EndToEndIteration target as its main entry point. Thus, MSBuild ignores the DefaultTargets attribute when started by Team Build.

After the <Project> element, you'll find an <Import> element. The <Import> element tells MSBuild to use another project file in addition to the one being processed. Team Build points the <Import> element to the version-specific Team Build targets file:

<!-- Do not edit this --> <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets" />

This targets file is the heart and soul of how Team Build does its magic. Careful examination of this file will provide you with deep insight into the Team Build process as well as how MSBuild works. A word of warning: you should not edit this targets file. Doing so would affect all the builds on a particular build agent (but only that agent). In addition, if Microsoft provides a service pack, hotfix, or update to Team Build, your changes would be lost. Finally, it's not a Microsoft-supported operation. You should use the built-in mechanisms to adjust Team Build's behavior instead—something you're going to learn in this column.

As part of its preprocessing, MSBuild creates a tree of targets to execute. The order in which a project file defines targets is irrelevant. MSBuild executes targets based upon the entry point target and any DependsOn attributes assuming no errors occur. In addition, MSBuild will only execute one named version of a target. It uses the last version of a target defined. Team Build takes advantage of this by defining a number of empty targets in the Team Build targets file. Most of these targets have a prefix of Before or After in their names. MSBuild processes the targets defined in the Team Build targets file first because of the <Import> element.

You, in turn, can override one of these targets by creating a target with the same name in the TFSBuild.proj file. However, a few additional items also affect target ordering. First, the root <Project> element supports an InitialTargets element, which can contain a semicolon-delimited list of one or more targets to execute before the DefaultTargets or those specified on the command line. Second, a target can specify that it depends on one or more targets by using the DependsOnTargets attribute. MSBuild executes the targets listed in a DependsOnTargets attribute in the order in which they are declared. Many of the Team Build targets do just that. Third, a target can specify a condition to its execution. For example, many of the Team Build targets execute only if the build is not a desktop build. Therefore, when you run a Team Build, the initial set of targets that executes is first CanonicalizePaths and then CheckSettingsForEndToEndIteration.

MSBuild executes CanonicalizePaths first because the targets file defines it as the InitialTargets item. CheckSettingsForEndToEnd­Iteration is next because it's the first target in the list of Depends­OnTargets specified for EndToEndIteration, the target specified by Team Build in MSBuild's command line. Note that MSBuild only executes targets that are in the chain from EndToEndIteration.

At this point, you know enough to get started. In part one of this column, I created a Team Project, MSDNMag, with a custom workspace. I'll continue to use this Team Project for the examples here. I've checked in all of my sample solutions under $MSDNMag/Main/src/TeamSystem/C10/. My custom workspace named MSDNMag maps the trunk of my Team Project $MSDNMag to C:\Work\MSDNMag to make it easy to work with the solutions as well as the build files. Finally, as I mentioned earlier, I'm doing all of this in the Team System 2008 SP1 version of the Microsoft-provided virtual machine.

When you first start working on Team Build customizations, you need to watch two things. One, you'll be editing XML files to create your customizations. Second, debugging today tends to be all about printf and parsing log files. When you consider customizing your build to adjust its behavior, there are three general ways to proceed. First, change the value of well-known properties. Second, invoke predefined tasks. Third, write your own custom task.

Both MSBuild and Team Build expose a number of properties that you can use to define conditions that affect target execution. For example, Team Build exposes an IsDesktopBuild property that it uses to determine if certain targets should execute. MSBuild exposes properties related to the platform, compiler options, processor architecture, and more. In addition, Team Build has a number of properties that you can change to turn on or off features. In fact, code analysis is one of the items you can adjust.

If you examine your TFSBuild.proj file, you'll find a bit of XML like the following code. By changing the value of the <RunCodeAnalysis> property, you change how MSBuild evalutes code analysis settings on your projects:

<!-- CODE ANALYSIS Set this property to enable/disable running code analysis. Valid values for this property are Default, Always and Never. Default—Perform code analysis as per the individual project settings Always —Always perform code analysis irrespective of project settings Never —Never perform code analysis irrespective of project settings --> <RunCodeAnalysis>Never</RunCodeAnalysis>

Team Build also uses properties to control the running of tests. This involves multiple XML fragments. First, you set the <Run­Test> property to true. Then you either use test lists or have Team Build scan for tests to run. If you want to use test lists, you need to define a <MetaDataFile> property with one or more <TestList> items, as you can see here:

<!--Set this flag to enable/disable running tests as a post-compilation build step.--> <RunTest>true</RunTest> <MetaDataFile Include= "$(BuildProjectFolderPath)/../../Main/src/SysInfo/SysInfo.vsmdi"> <TestList>All Unit Tests</TestList> </MetaDataFile>

Here's one more example of properties in action. During normal build execution, Team Build labels all the artifacts retrieved from version control. If you don't want this to happen, you just need to set the SkipLabel property to true. You can do this a number of ways. First, if you only want to do it for a particular run of your build, you can pass /p:SkipLabel=true in the MSBuild command-line arguments (see Figure 3). Note, however, that if you only pass SkipLabel, you'll get a build error when Team Build attempts to generate the list of changesets and update work items. To avoid the error, you want to add SkipGetChangesetsAndUpdateWork­Items=true. You'll find your command-line arguments visible in the Summary section of the build report.

fig03.gif

Figure 3 Passing Command-Line Arguments to MSBuild

Beyond the command line, you can use the same syntax, but instead of using the dialog box, edit the TFSBuild.rsp file that you'll find side-by-side with your TFSBuild.proj file in version control. Finally, you can add the properties to your TFSBuild.proj file. You'll want to check out your TFSBuild.proj file from version control. If you double-click on the file, it will open in Visual Studio's XML editor.

You can add each property using XML opening and closing element tags with true for the element's text to an existing property group, or you can create your own property group—even going so far as to add a condition parameter so that you can turn it on and off via the command line as shown here. Now you can pass /p:QuickBuild=True to do a quick build:

<PropertyGroup Condition="$(QuickBuild)=='True'"> <SkipLabel>true</SkipLabel> <SkipGetChangesetsAndUpdateWorkItems>true</SkipGetChangesetsAndUpdateWorkItems> </PropertyGroup>

Getting Information about What's Going On

As you start to futz with your build and figure out what's really going on, you'll often want to output additional status information. Before you do, remember that by default Team Build generates a detailed log. You can adjust the verbosity of this log by using the fileLoggerParameters (flp) argument (Team Build 2005 used the /v argument). In Microsoft .NET Framework 3.5, the default logging changes from normal verbosity to diagnostic. Thus, if you want smaller log files, you'll want to add /flp:verbosity=normal to your TFSBuild.rsp file.

If you don't find what you're looking for in the detailed log, you can start writing your own messages to the log file. In order to log a message, you'll need to define a target. Within the target, you can use the built-in Message task. For example, to write out the string "My Custom Message" as well as the value of the SkipLabel property, add the following XML to your TFSBuild.proj file just before the closing </Project> element:

<Target Name="MyMessageLogger"> <Message Text="My Custom Message" /> <Message Text="SkipLabel Status= $(SkipLabel)" /> </Target>

However, if you do this and run your build, nothing happens. Recall that Team Build calls EndToEndIteration as its entry point. MSBuild only executes targets in the chain of execution of the target. If you want to get your task to execute, you need to hook your targets into this chain. One simple way would be to add your target to the Team Build targets file. However, you wouldn't do that because, remember, you've been warned "Don't do that!" Turns out you have two very reasonable alternatives that Microsoft supports and even encourages. The other is to modify a target's DependsOn property to insert a custom target into the dependency chain.

As mentioned earlier, the Team Build targets file defines a number of empty targets. Microsoft created those targets expressly so you could override them. You'll find a list in the MSDN article " Customizable Team Foundation Build Targets."

The team build process has more than 20 points where you can easily get Team Build to execute your custom operations. The first target you can override is BeforeEndToEndIteration. This is effectively the fourth target that MSBuild executes. To get the previous XML fragment to run, you need to change the Name attribute from MyMessageLogger to BeforeEndToEndIteration. Going one step further, if you want your message to appear in the Team Build report, you can use the Team Build-provided BuildStep task instead of the Message task, as in shown here:

<Target Name="BeforeEndToEndIteration"> <BuildStep TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)" Name="MyBuildStep" Message="BeforeEndToEndIteration override is executing." Status="Succeeded" > </BuildStep> </Target>

Not all of the changes made to Team Build by Microsoft when updating from 2005 to 2008 were greeted with songs of praise. I, for one, frowned on this change: by default, Team Build 2008 marks a build successful if it does not encounter an error during the build process. This ignores the notion of a partially successful build. Team Build sets a build's status to this state if compilation succeeds but something else, such as a unit test, fails. In contrast, Team Build 2005 fails a build if a test fails.

Needless to say, this change is not what everyone wanted. To get around this behavior in Team Build 2008 without SP1, you can use a technique posted by Aaron Hallberg from Microsoft to fail builds when one or more tests fails. Figure 4lists the XML you need to add to your TFSBuild.proj file. This target first gets the TestSuccess property. It then changes the CompilationStatus property to Failed if TestSuccess equals false. Thankfully, Microsoft listened to feedback. A property was added in Team Build 2008 SP1 that address this issue. Simply set <TreatTestFailureAsBuildFailure> to true, and you're good to go.

Figure 4 XML that Must Be Added to the TFSBuild.proj File

<Target Name="AfterTest"> <!-- Refresh the build properties. --> <GetBuildProperties TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)" Condition=" '$(IsDesktopBuild)' != 'true' "> <Output TaskParameter="TestSuccess" PropertyName="TestSuccess" /> </GetBuildProperties> <!-- Set CompilationStatus to Failed if TestSuccess is false. --> <SetBuildProperties TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)" CompilationStatus="Failed" Condition=" '$(IsDesktopBuild)' != 'true' and '$(TestSuccess)' != 'true' "> </Target>

Custom Tasks

So far, you've changed the way MSBuild and Team Build operate by changing XML in TFSBuild.proj. But what if you want to deploy a Web project to IIS after a build is complete? What if you want to package up your binaries into an MSI file? One solution is to create a custom task, but there's a bounty of MSBuild and Team Build task libraries already available on the Internet for your use, and most of them provide full source code.

Two major libraries you should start with are on CodePlex. There's the SDC Taskswith over 300 tasks. In addition, there's the MSBuild Extension Packwith over 250 tasks. Each library has a wide range of tasks and both provide source. Using these libraries requires that you install the assemblies on your build agent machines and then add the appropriate XML fragment to your TFSBuild.proj file.

Microsoft created Team Build 2008 to be extremely flexible. Getting the most from it starts with understanding how it works and interacts with MSBuild. With that knowledge and a bit of XML editing, you can easily change its behavior. With the help of the community, you can go much further and add completely new features to Team Build. Finally, if you can't find the task to do exactly what you want, you can create your own custom task.

Send your questions and comments for Brian to mmvsts@microsoft.com.

Brian A. Randell is a Senior Consultant with MCW Technologies LLC. He spends his time speaking, teaching, and writing about Microsoft technologies. Brian is the author of Pluralsight's Applied Team System course and is a Microsoft MVP.