I’ve had the fortune of being involved in some pretty complex and elegant Team Build configurations. I’ve started to blog about individual points several times, but ended up figuring that I should instead write a series of posts about common goals and general Team Build approaches. I can’t take credit for all this content. Some I’ve developed over time, some I’ve learned from others on the web, and yet other stuff I’ve learned from co-workers. That said, here we go with Part 1.
Every person has their personal preference, and this is typically taken to the nth degree with programmers. What follows are my personal preferences. Yours may vary.
First, I like to separate any custom team build stuff from the default TFSBuild.proj file. I’ll update properties already existing in that file, or available via the Microsoft.TeamFoundation.Build.targets file, directly in TFSBuild.proj. But all my custom stuff exists in a separate file, or files. For example, I’ll aggregate all my custom tasks into a CustomTasks.targets file, and import that at the bottom of TFSBuild.proj:
2: <!-- ADDITIONAL REFERENCE PATH
3: The list of additional reference paths to use while resolving references. For example:
5: <AdditionalReferencePath Include="C:\MyFolder\" />
6: <AdditionalReferencePath Include="C:\MyFolder2\" />
10: <!-- Add our custom target overrides -->
11: <Import Project="Environment.proj"/>
12: <Import Project="CustomTasks.targets" />
Here, we’ve imported the CustomTasks.targets file that contains all of our customizations, and we’ve also imported an Environment.proj file that contains metadata about the environment to which we typically deploy this build configuration. In this way, we can create additional build for additional environments, and only need to change the Environment.proj file.
Within the CustomTasks.targets file, I declare any custom MSBuild tasks I need, as well as define my overrides and custom targets. (See http://msdn.microsoft.com/en-us/library/aa337604.aspx for a list of overridable team build targets). An example skeleton is as follows:
1: <?xml version="1.0" encoding="utf-8"?>
2: <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
4: <UsingTask TaskName="XmlMassUpdate" AssemblyFile="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.dll" />
5: <UsingTask TaskName="Attrib" AssemblyFile="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.dll" />
6: <UsingTask TaskName="XmlFile.GetValue" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft.Sdc\Microsoft.Sdc.Tasks.dll" />
7: <UsingTask TaskName="XmlFile.SetValue" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft.Sdc\Microsoft.Sdc.Tasks.dll" />
9: <Target Name="BuildNumberOverrideTarget">
10: <CallTarget Targets="CalculateCustomBuildNumber" />
13: <Target Name="GenerateDocumentation">
14: <CallTarget Targets="CreateCustomDocumentation" />
17: <!-- Custom targets removed for brevity. -->
For custom build tasks, I typically rely heavily on the Sdc and MSBuild Community tasks (http://www.codeplex.com/sdctasks and http://msbuildtasks.tigris.org/). There’s not alot they can’t do, but if you find something, you can always develop a custom task yourself.
Adjusting Application Settings During a Deploy
One of the custom tasks I rely *heavily* on is the XmlMassUpdate task. There are many scenarios where you want to tweak configuration settings when you deploy to an environment, and this task is perfect for it. In a nutshell, you provide a set of configuration data (in this case, Xml), and the task dynamically updates/removes/adds the config content to match your desired settings. I typically embed the configuration directly in the custom build file, but you could also declares this externally. MSBuild supports including “random” Xml in the build file via the <ProjectExtensions/> node (anything within this content is ignored by MSBuild). So, using this and the XmlMassUpdate tasks, we can do the following (example only):
1: <ProjectExtensions xmlns:xmu="urn:msbuildcommunitytasks-xmlmassupdate">
2: <MyAppConfig xmlns="">
5: <add xmu:key="key" key="MyDeployedValue" value="One" />
6: <add xmu:key="key" key="MySecondDeployedValue" value="Two" />
13: <Target Name="ConfigOverride">
14: <Attrib Files="$(SolutionRoot)\MyApp.config" ReadOnly="false" />
1: <XmlMassUpdate ContentFile="$(SolutionRoot)\MyApp.config"
5: NamespaceDefinitions="msb=http://schemas.microsoft.com/developer/msbuild/2003" />
The attrib task is there because, by default, files grabbed from source control to be used during a build are read-only. You’ll want to check the docs on how to use XmlMassUpdate, but the short of this says (assume you call the ConfigOverride target somewhere else in your build process):
On the file MyApp.config in my solution path, replace/modify content starting at the /configuration node, using the content included in the CustomTasks.targets file (this same file) starting with the node located at Project/ProjectExtensions/MyAppConfig/configuration. In this case, it will update the appSettings/add node with an “key” attribute of ‘MyDeployedValue’ to have a “value” attribute of ‘One’, and similarly for the second entry.
I use this for all manner of things, from updating configs, to updating project files (they’re just Xml after all), to updating Sandcastle help file docs, to adjusting ClickOnce settings on a deploy. We’ll see more about these in the next several posts.