Using MSBuild to write a build tool

While re-writing the team's build tool I have come across some strengths and weaknesses of using msbuild.  Our sample solution to build contains basically one project of every type that you can create in Visual Studio 2005.  There are many goals on writing this build tool, but here are the main goals for the code base to be built:

  1. Have versioning for the assemblies.
  2. Archive a version of the code.
  3. Only deploy proper build files for the release build as well as copy over web project files.
  4. Make the build xml as generic as possible so it can easily migrate to TFS.

1.  In order to solve the versioning problem I used the Microsoft.VersionNumber.Targets.  With this you can specify the major, minor, revision..etc versions and it will recursively go through the project directories and change the assemblyinfo file.  One catch, typically you would sync with source control before the build and most source controls that I know get the latest with the read only attribute.  So, I had to write a custom task to change that attribute setting, then it all worked well.  There are a few catches with using the Microsoft.VersionNumber.Targets, one that was giving me problems was specifying a solution to build outside of the directory that the build project xml is sitting in - which you basically can not do.

2.  To archive the code base, the best way to do it is to write a custom task.  We're generating the build number on the fly, so in order to copy the code base to the new build folder, and set the build folder solution to be the solution to build its best to write a custom task.  I started to do the copying and zipping in the main msbuild project xml and it just started getting really messy.  Its best to keep it clean, use a custom task, and make it so you have some flexibility in the long run.

3.  This one is a tricky one also.  Building a web project is a no brainer - but deploying the web files (aspx, ascx, etc) is the tricky part.  I looked at two alternatives to the website project: building a web application project (wap) and building a web deployment project (wdp).  I started with the web deployment project.  Now, the web deployment project is great if you want to build only your website and deploy it to a particular environment with a merged assembly.  But, the issue here is incorporating the wdp with your msbuild project.  If you right click on the wdp and look at the project file, guess what...its a msbuild file!  So, you can do stuff in that file to copy, delete, etc.  But, I had a lot of problems trying to hook that wdp build file into properties in your main build project.  So basically you're left with two options - try to write something to change the wdp project xml to fit in with your main msbuild proj (to include properties, etc) or don't use wdp.  I originally tried to work with the wdp file, and I'm sure that there is something that you can do to incorporate the two build projects (your build proj and the wdp.proj) but I was unsuccessful with that approach.  So, I used the web application project and then I wrote my own task that would be the "AfterBuild" task and copy over the files.

4.  By having class libraries with my custom tasks I can easily move this to TFS.  Also, the code executing the build file serializes the xml and sets the properties to their approriate values.

Here's the "final" version (I removed some specific information)

<?xml version="1.0"?>

<Project xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema"

DefaultTargets="UpdateAssemblyInfoFiles;BuildAll;CopyOutputFiles;DeleteFiles" xmlns="https://schemas.microsoft.com/developer/msbuild/2003">

<Import Project="$(MSBuildExtensionsPath)\Microsoft\AssemblyInfoTask\Microsoft.VersionNumber.Targets" />

<UsingTask TaskName="BuildArchiveDirectory"

AssemblyFile="D:\MyLocation\BuildArchive.dll"/>

<PropertyGroup>

<AssemblyTitle>Brett</AssemblyTitle>

<AssemblyBuildNumberType>NoIncrement</AssemblyBuildNumberType>

<AssemblyRevisionType>NoIncrement</AssemblyRevisionType>

<AssemblyMajorVersion>6</AssemblyMajorVersion>

<AssemblyMinorVersion>3</AssemblyMinorVersion>

<AssemblyBuildNumber>0001</AssemblyBuildNumber>

<AssemblyRevision>0</AssemblyRevision>

<AssemblyFileBuildNumberType>NoIncrement</AssemblyFileBuildNumberType>

<AssemblyFileRevisionType>NoIncrement</AssemblyFileRevisionType>

<AssemblyFileMajorVersion>6</AssemblyFileMajorVersion>

<AssemblyFileMinorVersion>3</AssemblyFileMinorVersion>

<AssemblyFileBuildNumber>0001</AssemblyFileBuildNumber>

<AssemblyFileRevision>0</AssemblyFileRevision>

<BuildOutputDirectory>d:\temp\</BuildOutputDirectory>

<SourceArchiveDirectory>d:\buildArchive\</SourceArchiveDirectory>

<BuildFiles>**\bin\**\*.*</BuildFiles>

<DeletePDB>**\bin\Release\*.pdb</DeletePDB>

</PropertyGroup>

<ItemGroup>

<SolutionFile Include="" />

</ItemGroup>

<ItemGroup>

<DeletePDB Include="**\bin\Release\*.pdb" />

</ItemGroup>

<Target Name="BuildAll">

<MSBuild Projects="@(SolutionFile)" Properties="Configuration=Debug;Platform=Any CPU;RunCodeAnalysis=true" />

<MSBuild Projects="@(SolutionFile)" Properties="Configuration=Release;Platform=Any CPU;DebugSymbols=false" />

<CreateItem Include="$(BuildFiles)">

<Output TaskParameter="Include" ItemName="OutputFiles" />

</CreateItem>

<CreateItem Include="$(DeletePDB)">

<Output TaskParameter="Include" ItemName="FilesToDelete" />

</CreateItem>

</Target>

<Target Name="CopyBuildFiles">

<!--build version has to be in this format: 1.0\0000-->

<BuildArchiveDirectory BuildVersion="1.0\0000" ArchiveDirectory="$(SourceArchiveDirectory)" SolutionFile=""/>

</Target>

<Target Name="CopyOutputFiles" DependsOnTargets="BuildAll">

<Copy SourceFiles="@(OutputFiles)" SkipUnchangedFiles="false" DestinationFiles="@(OutputFiles->'$(BuildOutputDirectory)%(RecursiveDir)%(FileName)%(Extension)')" />

</Target>

<Target Name="DeleteFiles" DependsOnTargets="CopyOutputFiles">

<Delete Files="@(FilesToDelete-&gt;'$(BuildOutputDirectory)%(RecursiveDir)%(FileName)%(Extension)')" />

</Target>

</Project>