Team Build IsA MSBuild?

I had one of those Aha! moments this weekend. You know, the ones that Oprah is always talking about (not that I watch, it just happens to be on sometimes when I’m in the room…) It turns that somehow in my dealings with Team Foundation Build (i.e. “Team Build”) I had always maintained this subconscious assumption that Team Build was really just MSBuild++, or rather, a proper superset of the functionality that MSBuild provides. I probably wouldn’t even have balked had someone shown me a UML diagram like this:

image

The assertion there being that Team Build is just a realization of MSBuild, extended to work in a broader, team environment. A follow-on assertion (that certainly I made) is that the team who created Team Build started with MSBuild as a basis and worked out. My “Aha! Moment” was realizing that in fact it is not an IsA relationship at all, but rather a pure HasA relationship:

image

Internally, Team Build uses MSBuild, and as such, to perform many of its common tasks it depends on MSBuild, but Team Build is most certainly not MSBuild.

I’m sure after reading this most people are probably thinking two things:

  1. Duh!
  2. So what?

As for your first thought; hey, cut me some slack. I never claimed to be the sharpest tool in the shed. That said, odds are I’m not the first person to incorrectly assume a more intimate relationship between Team Build and MSBuild than what actually exists.

To address your second thought; let’s look at this from the other perspective. What assumptions might you hold about the capabilities of MSBuild that may actually be capabilities inherent to Team Build which have no corollary in the context of MSBuild. Specifically for me this showed up as support for building (and controlling the build process of) “solutions”.

If you’ve done any work customizing a MSBuild project’s build process, the ability to override build properties and targets has probably been very useful. If not, then let me edify you now:

You have the ability to override build properties and targets in your MSBuild projects (which includes C# (.csproj), VB.NET (.vbproj), ASP.NET Web Deployment Projects (.wdproj) , et al.) In fact, if you crack open the .csproj file for any of your VS2008 C# projects, you’ll see the following markup at the bottom:

<!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->

If your Team Build experience is at all like mine then you’re more than a little familiar with both the Target element and the nomenclature of BeforeTarget/AfterTarget. The idea there is pretty straightforward, rather than using the clunky old “PostBuild Steps” from the days of yore (which you can still do, but why?) they are explicitly inviting you to interact directly with MSBuild in altering your build steps. This has been written about quite a bit and can be very handy for simplifying the lives of current and future developers on a project. One thing we like to do on my current project is to ensure that any actions normally taken by our installer are automatically applied after each build on a development machine. For instance:

<Target Name="AfterBuild" Condition="$(DevEnvDir) != '*Undefined*'">
  <Message Text="Copying My targets file" />
  <Copy SourceFiles="$(ProjectDir)My.targets" DestinationFolder="$(MSBuildExtensionsPath)\My\" ContinueOnError="false" SkipUnchangedFiles="false" />
</Target>

That target will copy the “My.targets” file from the project’s source directory to the system configured path for MSBuild extensions after each successful build. That may not be very useful in your situation, but for our project it is a major developer productivity boon.

Let’s say, however, that you wanted some particular bit of functionality to execute after each successful Solution build. How would you go about it? Well, I can tell you one thing that will not work – adding solution-related targets to your .csproj file. One reason may seem obvious, each project is build individually by MSBuild. Therefore, when a particular project is building it really has no information about the overall status of the outer build. The more important reason, however, is that generally speaking, your solutions are not built by MSBuild.

Now if you really want to you can call msbuild.exe from the command-line to build your solution. And if your solution consists only of projects whose type is one of the few project types directly supported by MSBuild, then MSBuild can build your solution. However, the vast majority of Visual Studio solutions will never be built by MSBuild. True story.

So what does build them? More often than not, the build is managed by Visual Studio itself. Other tools that you may run into which can directly build solutions are NAnt and of course Team Build, which I’ll get to shortly.

If you look in your solution file (.sln) you’ll see the first few lines looks like this:

Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008

Notice that A) that is obviously not XML syntax and B) it’s pretty specific about which application this file’s format belongs to. In other words, this is not an MSBuild file, this is a Visual Studio file. When you execute the image command, Visual Studio (not MSBuild) runs the show. It decides which projects should build, when they should build and which build targets to execute, and it decides how and where to log any output and where to drop the output files. It also, for example, knows that Visual C++ projects should build using VCBuild whereas C# projects should build using MSBuild.

Alright, back to Team Build. How does Team Build know what to do with a .sln file that is specific to Visual Studio? The team behind Team Build coded support for that (external) file format into their system and decided what to do with them. Generally speaking, they don’t do much with them besides parsing out the configured projects and their supported build platform and configuration pairs. After that, Team Build handles solution building just like it does project building – it has a set of predefined solution targets that execute in a predefined order (all of which is overridable) that ultimately calls a task they wrote named MSBuild and passes in the solution file’s path along with the configuration and platform to build as well as a bunch of additional properties and metadata. What’s important to note here is that Team Build’s MSBuild Task does execute MSBuild, it just does so in an over-controlling-boss fashion. One of the side-effects, however, is that Team Build does not support building solutions which contain non-MSBuild project types. Rather than failing when it encounters those projects, MSBuild instead will skip them and build the project types it does support (I’m still in the process of validating that statement.)

MSDN has a walkthrough on configuring Team Build to build a Visual Studio Setup Project (one of the project types supported by Visual Studio but not supported by MSBuild.) The meat of the walkthrough is really near the end:

<Target Name="AfterCompile">
    <Exec Command="&quot;$(ProgramFiles)\Microsoft Visual Studio 9.0\Common7\IDE\devenv&quot; &quot;$(SolutionRoot)\HelloWorldTest\HelloWorldTestInstaller\HelloWorldTestInstaller.vdproj&quot; /Build &quot;Debug|Any CPU&quot;"/>
    <Copy SourceFiles="$(SolutionRoot)\HelloWorldTest\HelloWorldTestInstaller\Debug\HelloWorldTestInstaller.msi; SolutionRoot)\HelloWorldTest\HelloWorldTestInstaller\Debug\setup.exe" DestinationFolder="$(OutDir)" />
</Target>

This target will be called once the compilation chain is complete (all solution and project configurations have been compiled.) What does it do? Because Visual Studio knows how to build Setup projects and MSBuild does not, it actually calls out to devenv.exe (the Visual Studio executable) passing it “/build ‘Debug|Any CPU’” and the path to the project file to build.

So where does all this knowledge of Visual Studio, MSBuild, and Team Build leave us? Well for me it has created a bit of a quandary and at this point I’m still not sure the best way to resolve it. I need to find a way to interact with the solution build sequence of Visual Studio (and probably Team Build at some point) such that I can execute some custom validation on a fully built solution, rather than on individual projects. The only thing I can be 100% certain of is that MSBuild will have little or no direct involvement.

For now that’s where I leave this post. I have a feeling my follow-up will involve Visual Studio eXtensibility hooks convoluted enough to make grown men weep. But until I know that for sure, I’ll just keep smiling while I think about kittens and bunnies.

Leave a Reply