Tuesday, March 25, 2008

TeamBuild 2008 - Passing properties to Solutions

Our build process requires that we label our builds according to the version that we are building, i.e. Assemblies will be stamped with 1.0.2.1 so the build label should be BuildDefinition_1.0.2.1-BuildCount. I won't detail the method to create the custom build number as that has already been done very nicely by Martin Woodward - you can see that here.

What hasn't been explained, for which I spent a few hours (okay days) trying to figure out was how to pass that version to our solutions.

Here's our process:
I created a custom build task that stored the version by build definition and incremented that version on each successful build (the script passes the build definition to the task, the task sets the output properties, if the last build was not successful, the version would not have been incremented instead the BuildCount would be incremented showing how many times that particular version was attempted). I then passed the output from the task to the SolutionToBuild list. The initial try left me with my default of 0.0.0.0 (discussion on whether that was right to default it to that can be held at another time). I thought my default property was overriding my output from the task. That was not the case. I finally found that the property was not being passed appropriately to the Item list.

I finally broke down and consulted the expert (at least the one I knew, Buck Hodges). Buck was kind enough to forward me on to Aaron Halberg who gave this reply:

"This is the last bug I fixed for SP1 – passing dynamically generated properties through to solutions/projects. Solving it is rather complicated, unfortunately.

I don’t think that his current workaround will do the trick, for example – he’s expecting the Version property to still be set when CoreCompileSolution is executed, and it won’t be, since he modified it in the global context and it wasn’t passed through to the recursive calls that result in CoreCompileSolution being executed.

He could override the CallCompile target to include the fix I added for SP1:

<Target Name="CallCompile"
DependsOnTargets="$(CoreCompileDependsOn)">

<
PropertyGroup>
<
OutDir Condition="'%(ConfigurationToBuild.PlatformToBuild)' !=
'Any CPU'
">
$(BinariesRoot)\%(ConfigurationToBuild.PlatformToBuild)\%(ConfigurationToBuild.FlavorToBuild)

</OutDir>

<OutDir Condition=" '%(ConfigurationToBuild.PlatformToBuild)' == 'Any CPU' ">$(BinariesRoot)\%(ConfigurationToBuild.FlavorToBuild)\

</OutDir>
</PropertyGroup>


<
MSBuild Projects="$(MSBuildProjectFile)"
Properties
="BuildAgentName=$(BuildAgentName); BuildAgentUri=$(BuildAgentUri); BuildDefinitionName=$(BuildDefinitionName); BuildDefinitionUri=$(BuildDefinitionUri); [... Removed for space ] TestResultsRoot=$(TestResultsRoot); $(CustomPropertiesForBuild)"
Targets="CoreCompile">
<output TaskParameter="TargetOutputs" ItemName="CompilationOutputs" />
<msbuild>

<OnError ExecuteTargets="SetBuildBreakProperties;OnBuildBreak;" />

</Target>

Then, after setting the version property in his BuildNumberOverrideTarget, he would do:

Version=$(Version);$(CustomPropertiesForBuild)

The only alternatives to that option that I can think of involve somehow storing the version property so that it can be set again in the appropriate context. For example, he could use the WriteLinesToFile task to write it out to a text file in the BuildNumberOverrideTarget and then do something like:<output taskparameter="Lines" itemname="VersionFileLines">

%(SolutionToBuild.Properties);Version=%(VersionFileLines.Identity)

…or:<output taskparameter="Lines" itemname="VersionFileLines">

Version=$(Version);$(CustomPropertiesForBuild)

The former adds the version property to the SolutionToBuild Properties metadata for each solution. The latter adds it to the CustomPropertiesForBuild property."


I followed the first option but both work well. Choose the one that best fits your style. I haven't seen this information on Aaron's or Buck's blog so I posted it here. If I see it in the near future I will post a link that direction.

Update: Here's Aaron's post: http://blogs.msdn.com/aaronhallberg/archive/2008/05/12/orcas-sp1-tfs-build-changes-part-2.aspx

1 comment:

Ryan said...

I've had to wrestle with the same problem a bit myself and came up with a different solution, though this only works when using TFSBuild and wont work in normal desktop MSBuild's.

My solution was simple, I used the setbuildproperties task when I generate the buildnumber to set BuildNumber or something of that nature, and then everywhere I need to pull it in and dont' know if it will be available in the context (which it often isn't) I use getbuildproperties to pull it back out of the server's session.

If you don't want to use buildnumber, I believe there may be a customproperties or something, either way I think the build properties space accessed by these tasks is a good place for those dynamic variables which are hard to push through the many MSBuild instances seen in one build, just unfortunate that MSBuild doesn't have this independent of TFSBuild.