Ok, so you used the New Team Build Type wizard to create your Team Build, but now you find that your code isn’t building because some of the paths are wrong. This is probably due to having hard-coded paths in your project for things like Strong-name keys or, in my case, CodeSmith property sets that have explicit paths to their associated CodeSmith templates.
Need: I need to have the build dump the source files to a specific location on the Team build Server other than the one used by default to accommodate hard-coded paths. This need will exist until a strategy can be defined to move to relative or UNC paths for templates and strong-name keys.
Problem: The New Team Build Wizard doesn’t give you an input element to define the SolutionRoot property in the build.
The SolutionRoot is the folder where the Team Build will store all of the source code files for compilation. This is the property that needs to be overridden in our case.
Issue: By default, the Microsoft.TeamFoundation.Build.targets files defines the SolutionRoot property as:
SolutionRoot = $(BuildDirectoryPath)\$(TeamProject)\$(TeamBuild)\BuildType\..\Sources
So if we have creating a new Build Type under a team project named Argos and setting the New Build Type Wizard’s values to the following:
Team Build Name = NightlyBuild BuildPath = C:\BuildTemp
our resulting final SolutionRoot property would be C:\BuildTemp\Argos\NightlyBuild\Sources. This is not exactly where I would have expected it to go, and not at all where I need it to go.
Solution: To set a new starting path for the Get from source control, you need to override the default SolutionRoot property that team Foundation gives you. This can be done by adding the following snippet to your TFSBuild.proj file for your Team Build.
<SolutionRoot>[Wherever you need source control to start dropping files on Get]</SolutionRoot>
Pretty simple, huh… To make sure that you are overriding the standard behavior, you need to be sure that the SolutionRoot entry that you add comes after the following Import statement:
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v8.0\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
Because MSBuild works on the premise of “Last one defined wins”, we need to define our property after theirs to make ours stick.
VERY IMPORTANT: The path supplied in the SolutionRoot property is passed to the Clean target. This target is responsible for “cleaning”, (i.e. deleting) the folders that will be used for the build as well as all subfolders. If you set the SolutionRoot to a folder that has critical files below it (like C:\ or C:\Windows), MSBuild WILL TRY TO DELETE IT! I learned this the hard way as I was figuring out all of the above. I hosed a machine so badly that it had to be reimaged. For more info on how TFS Team Build handled this, see my previous post on TFS Team Build Can Run Forever as well as Mike Ruminer’s MSBuild clean and c:\
Conclusion: There are times that you need to force Team Build to utilize a consistent, well know folder structure to make a build work correctly with regard to referenced dependencies. The out-of-the-box behavior is to create a source code structure under the BuildPath. If you need to force it to utilize your standard structure, you can override the SolutionRoot property. The standard behavior of the Team Build is to Clean the SolutionRoot folder before performing a source control Get, so don’t put critical folders in the SolutionRoot property. Make sure that you place your SolutionRoot override after the Import statement to make sure that it “sticks”.