Orcas SP1 TFS Build Changes, Part 2

As promised, here are some more details on other SP1 changes for TFS Build. 

3. Detect test results.

In Whidbey, a failed test would result in a failed build - builds were either Succeeded or Failed, so there wasn't really much middle ground.  In Orcas we added two new build status properties, CompilationStatus and TestStatus; and a new value for the overall build status - PartiallySucceeded.  The overall status of the build would be Succeeded if an only if there were no errors of any kind during the build; PartiallySucceeded if no errors occurred before or during compilation; and Failed otherwise.  If a test failed, CompilationStatus would be succeeded, TestStatus would be failed, and the overall Status would be partially succeeded.

Many users wanted a bit more control, and in particular wanted a failed test to result in a Failed build.  I posted a workaround that allowed this here, but it was rather hacky - the idea was to set CompilationStatus to failed when a test failed and then let the default logic kick in and fail the build.  In SP1 we introduced a new property - TreatTestFailureAsBuildFailure - that will cause a test failure to fail the build without having to modify CompilationStatus.  Just set this property to true and the new default logic will be to mark the overall status of the build as PartiallySucceeded if no errors occur before or during testing.  That is, if a test fails, CompilationStatus will be succeeded, TestStatus will be failed, and the overall Status will be failed.  Note that partially succeeded builds are still possible when errors occur after successfully running unit tests (e.g. when copying binaries to the drop location).

4. Dynamically created properties.

This one is pretty confusing, and requires an understanding of how the MSBuild task works, an understanding of the Team Build targets file, etc.  I'll describe the problem first, then the solution, and then for anybody who feels like sticking around just a bit about why this problem exists.

The Problem

The first time I heard about this issue, a user was dynamically generating a build number in the BuildNumberOverrideTarget in the standard fashion illustrated here, along with a corresponding version number with which to stamp assemblies.  He was then, however, attempting to actually use that dynamically generated version number during the compilation of his individual solutions.  That is, he had something like:

     <SolutionToBuild Include="SomeSolution.sln">
      <Properties>VersionNumber=$(VersionNumber)</Properties>
    </SolutionToBuild>

...where $(VersionNumber) was getting generated dynamically in BuildNumberOverrideTarget.  Unfortunately, the value getting passed into the individual solutions was the original value of the property - namely, the empty string.  (Note that this same issue would apply if he had attempted to access the VersionNumber property in one of the Before/AfterCompileConfiguration or Before/AfterCompileSolution targets.)

The Solution

The fix is to put all dynamically generated properties into the CustomPropertiesForBuild property (or the CustomPropertiesForClean property if you need to access the property in Before/AfterCleanConfiguration and/or Before/AfterCleanSolution).  For example, the user above could do something like:

   <Target Name="BuildNumberOverrideTarget">
    <BuildNumberGenerator>
      <Output TaskParameter="BuildNumber" PropertyName="BuildNumber" />
      <Output TaskParameter="VersionNumber" PropetyName="VersionNumber" />
    </BuildNumberGenerator>
    <PropertyGroup>
      <CustomPropertiesForBuild>$(CustomPropertiesForBuild);VersionNumber=$(VersionNumber)</CustomPropertiesForBuild>
    </PropertyGroup>
  </Target>

The Description

So what the heck is going on here anyways!?  Well, the issue revolves around the mechanism used by Team Build to support multiproc MSBuild.  In particular, the unit of parallelism in MSBuild is the project, meaning that to build configurations and solutions in parallel Team Build has to use the MSBuild task to call back into TfsBuild.proj for each configuration/solution combination - hence the Compile/CompileConfiguration/CompileSolution targets. 

Furthermore, when a project is invoked with the MSBuild task, all of the environment from the caller (properties, item groups, etc.) is lost - only those properties passed in via the Properties property of the MSBuild task (along with any global properties - see my blog post here for details) are available in the new context.  Since Team Build cannot know the full list of dynamically generated properties, none are passed into these recursive calls to TfsBuild.proj.  Or at least, that's how it used to work...  In SP1, we go ahead and pass the CustomPropertiesForBuild property into the first of these recursive calls, after which any dynamically generated properties (placed into this container) become global and are available for the rest of the chain.