Team Build is a build framework constructed upon MSBuild
and MSTest
and
integrated with Visual Studio and Team Foundation Server (TFS). At a
minimum, a Team Build build consists of:
A build agent is a service installed and running on a Windows
computer that
executes builds on request. In Visual Studio we can create a list of
these build agents, giving them each a name and providing connection
details such as the computer name, port and protocol. We pick the
desired build agent from this list in Visual Studio when defining or
invoking a Build Definition. Figure 1 shows the Visual Studio 2008
dialog for registering a build agent.

Figure 1: Registering a Build Agent
with Visual Studio 2008
A build definition defines the when, where and what of a particular
build execution. They are defined in Visual Studio 2008 using a
six-page dialog. Figure 2 shows that each build definition needs a name
and may have
a description. In addition, the definition can be enabled or disabled
via a checkbox. Disabling a build definition simply prevents it from
being used to kick-off a build.

Figure 2: Setting the name and
description of a build description
A build definition defines a TFS workspace. For those not familiar
with them, a TFS workspace is simply a mapping between TFS Source
Control
folders and working directories. The workspace defined in the build
definition is passed to the build agent when the build definition is
used to execute a build. The agent uses the workspace definition to
determine which files to copy from TFS Source Control
and into which working directories to copy them. Figure 3 shows the
workspace definition page in the Build Definition dialog.

Figure 3: Configuring the workspace
for the build definition
In addition to the workspace, the build definition defines a number
of other
settings. These settings include:

Figure 4: Setting the location of the
project file to build

Figure 5: Setting what build results
to keep

Figure 6: Setting the default Build
Agent for the build and default output location for the build results

Figure 7: Setting when the build is
run
Conceptually, build defintions are a great idea. Unfortunately,
their implementation misses a number of opportunies.
Most modern small
team projects want to run incremental builds whenever a new set of
files are checked into source control, and a thorough, full build
each night or at the end of each week. These two builds are likely to
differ only in the matter of
a few property values that control whether the build is incremental or
full and whether or not to execute certain optional targets. Ideally,
the team would define two build definitions, one with a check-in
trigger and one with a daily trigger, both pointing at the same build
project file. The defintions would store and pass to the build agent
the relevant property
values that indicate whether the build is to be incremental or not.
Unfortunately, property values can only be specified when a build is
queued for execution interactively. Automatically triggered build
definition have nowhere to specify build property values.
Another way to achieve reuse of the same build project file for
differnt builds would be to specify a different starting target in the
build definition. This approach does not require build definitions to
use property values to differentiate between the two builds.
Unfortunately, build definitions always invoke the EndToEndIteration
target. Build definitions have nowehere to specify an alternative
target to excute.
The obvious solution is to create two build project files in the
same source control folder, factoring out the stuff that is common
between the two into a third file that the other two reference. The two
project files only need to set the handful of relevant properties to
the values that cause the correct build to be executed.
Everything else can go in the third file that both the others import.
Unfortunately, a build definition only specifies the source folder
in
which the project file lives. The project file must be called TFSBuild.proj. Therefore, the two
project files in the same directory is not possible.
The only solution is two build definitons, each pointing to a
different source control folder containing slightly varying project
files. The common stuff can still be factored out into a separate file
but it now requires a place to live. It becomes easier to simply
duplicate the project file and tweak a few properties. This might be ok
for a fairly standard build but for a highly customised build, it could
rapidly lead to a maintenance headache and possibly unexpected build
failures when one project file is updated and the other is not.
Given these short-comings, the implementation of build definitions
seems at best to indicate
old-fashioned, structured design thinking, rather than a more object or
component-oriented thought process; low-level design is
driven more by the question, "How do we execute a build process?", than
by the question, "How do we get a project to build itself?". The
focus is on executing a function rather than adding intelligent
behaviour to a project object or component. Alternatively, these poor
low-level design decisions could simply indicate a lack of experience
with or
knowledge of typical build scenerios on the part of the developers.
Nevertheless, however frustrating, these limitiations are not
show-stoppers. Work
arounds are relatively straight-forward albeit irritating to have to
use.
The TFSBuild.proj sets the values of properties and items used by the targets in the TeamFoundation.Build.targets file. The targets file is completely generic and has no content that is specific to any particular project. The TFSBuild.proj file compliments the targets file by supplying all the values specific to the project such as solutions to build, the sets of tests to execute, and a myriad of flags to control the behavior of the build.
The targets file is typically located within the MSBuild installation in the <Drive>:\Program Files\MSBuild\Microsoft\VisualStudio\TeamBuild directory and forms the heart of the framework. At a very high level, the target file provides a set of targets to do the following:
Comparing this to my theoretical list of build steps inAdventures in Software Builds, this looks like a reasonable
foundation to build upon. Nevertheless, the lists have a few
immediately obvious
differences. Team Build looks like it does a considerable amount of
initialization work, the first four steps and step six are all
initialization steps. It also looks like Team Build provides little post-build activity such as api
document generation, success/failure notification, and static analysis.
The most significant difference, however, is the apparent lack of
iteration of a subset of the build ssteps for each component;
equivalents for most of the steps from my component build list can be
found in
the flat list above.
To understand these differences better we need to expand upon the Team Build target list above. That list, in fact, is a very big simplification. Some of the steps above are actually grouped together into enclosing targets and others represent half a dozen sub-targets. More accurately, the flow of the build is as follows:
However, this expanded list is still not the end of the story because it ignores targets supplied specifically to be overridden to extend or modify the default behaviour of the build. Most of the targets in the second list have an empty BeforeX and empty AfterX target that exist purely to enable extra targets to be inserted into the above sequence.
Comparing this expanded list with my theoretical list of build steps earlier, further highlights the most signifcant difference between that and the Team Build target framework; the lack of iteration through a subset of steps for each component. Logical layers or components are generally organized as Visual Studio solutions. Therefore, the TeamBuild target in the expanded target list above looks like the equivalent of my component-build step. Unlike in my theoretical list of steps, the TeamBuild target does not execute repeatedly for each layer or component. Instead targets within the TeamBuild target do so; in other words the scope of the iteration is very different. The clean targets are executed for every component or layer. Then the source code for all the components is retrieved and labeled (get and label), and then each component or layer is compiled in turn. Tests are only run after all the components have been compiled. Instead of building and testing component by component, Team Build applies each major step (clean, compile, test, etc) in the build in turn to all of the components.
This system-wide approach is less intuitive than the component by component approach. It means that:
The solution is simple, run the build process multiple times for each component using the same build number and label. Unfortunately, this is not as easy as it seems. Firstly, one of those intialize targets, interacts with a datbase of builds within TFS. This does not like different builds to have the same build number. Secondly, there is no easy way to chain Build Definitions together to force one to run after the other. It is defiantely possible to do so, but for now I have found it too difficult to bend the framework to fit my preferred component by component approach. This is something I would normally revisit in detail at some point in the future but given the significant changes in build technology for Visual Studio 2010, the investment in doing so is probably no longer worthwhile.
I do not want to suggest that the Team Build framework is completely inflexible. The Team Build targets above are only a starting point. A large number of additional targets in the file exist only to act as extension points. They are intended to be overriden to perform specific behaviour not covered 'out of the box'. These targets with suggested reasons for overriding include:
To override any of these, you create a target with the same name either inside the TFSBuild.project file after the import of the Team.Foundation.Build.targets file, or in a separate targets file that the TFSBuild.project file imports after the Team.Foundation.Build.targets file.
As you might expect several teams have identified common extensions that they need, and many of these have made their way into an open-source-style extensions package consisting of additional tasks and targets that can be incorporated into the build. This includes support for working with assembly files, BizTalk, SQL Server, de-tokenizing configuration files, etc. On my project we already use the de-tokenizing, assemblty and code-quality support from this package.
It looks like there is sufficient room to override the build wherever you need to. On my project we have, so far, overidden the following:
We have, however, also had to override the CoreTestConfiguration targets described in the Test Target section. This highlights another potential problem with the framework; there is no way to insert behaviour between the processsing of items when a collection is passed as a parameter to a task.
Another potential problem is the omission of override targets between some of the main steps. The before and after targets are part of compound targets. If there is something that needs to happen between finishing compiling and running tests, it has to be placed in either the afterCompile or beforeTest target. Neither of these may actually be truly suitable places for that function because switching off a compound target such as test (using the property $(RunTests) for example), switches off the related before and after targets too.
Fortunately, there is a way to insert a new target between two existing targets. the dependency list for each target is specified in a suitably named property. Overriding that property, and inserting the name of an additional target into the list works very nicely. In fact, it works so nicely, that it really makes the provision of the before and after targets redundant. They could be removed from the targets file simplifying it considerably. To do add an 'after' target simply override the relevent property defining the list of dependencies for the core target and add the name of the additional after target.
The result is that my project's TFSBuild.project files import three other target files, the standard Team.Foundation.Build.targets file, the targets file from the extensions package, and a file that contains organization-specific targets and tasks we want available to all the TFSBuild.project files in our organization.
One of our original requirements for the build required developers to be able to run the compile and test steps of the build on their workstations. Team Build provides for this situation. Two targets, DesktopBuild and DesktopRebuild provide this. Throughout the Team.Foundation.Build.target file, the targets and proeprties are guarded by conditions testing the value of the IsDesktopBuild property. Setting this property to true and executing the TFSBuild.proj file with either of these as the starting target, runs the relevent subset of targets.
Next, read Microsoft Team Build Details