Tuesday, 30 September 2008

Getting Started With MSBuild

I tried using MSBuild for the first time this week, and took a couple of wrong turnings along the way. Here's a brief summary of how I got started and how to perform a few basic tasks...

What is it?

MSBuild is the build system used by Visual Studio to compile your .NET projects. Check out this nice introduction to MSBuild on CodeProject. The .csproj files you may be familiar with from Visual Studio are in fact nothing more than MSBuild project files.

Why use it?

There are a few reasons why you might want to use MSBuild directly in preference to simply compiling your solution in Visual Studio:

  • You can use it on a dedicated build machine without the need to install Visual Studio.
  • You can use it to batch build several Visual Studio solutions.
  • You can use it to perform additional tasks such as creating installers, archiving, retrieving from source control etc.

It is the third reason that appeals to me, as I wanted to automate the creation of zip files for source code and runtime binaries, as well as run some unit tests.

Creating a project file

MSBuild project files are simply XML files. I created master.proj and put it in the root of my source code folder, alongside my .sln file.

Build Target

The first code I put in to my project file was an example I found on the web that performed a Clean followed by a Build. Here is a slightly modified version of the project file:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build"  
    xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <DeploymentProject>MyApplication</DeploymentProject>
    <OutputDirectory>$(DeploymentProject)\bin\$(Configuration)</OutputDirectory>
  </PropertyGroup>

  <Target Name="Clean">    
    <RemoveDir Directories="$(OutputDirectory)" 
            Condition="Exists($(OutputDirectory))"></RemoveDir>
  </Target>
  <Target Name="Build">
    <MSBuild 
      Projects="$(DeploymentProject)\MyApplication.csproj"
      Properties="Configuration=$(Configuration)" >      
    </MSBuild>
  </Target>
</Project>

I ran the build script using the following command line: MSBuild master.proj

It deleted all my source code! Aarrghh!! Fortunately I had a recent backup available.

The reason was that I had not put a default value for Configuration in the PropertyGroup, and my OutputDirectory property was missing the bin from its folder path. Note to self: always backup before running any script that can delete files for the first time.

Zip Target

After my unfortunate experience with clean, I was more determined than ever to create a target that would perform a source-code backup for me! To use the Zip task in MSBuild you first need to download and install MSBuild Community Tasks.

Then you create an ItemGroup defining which files you want to be included (and excluded) before using the Zip task to actually create the archive. Here's the two targets I created - one to make a release package, and one to make a source code archive:

<Target Name="Package" DependsOnTargets="Build">
    <ItemGroup>
      <!-- All files from build -->
      <ZipFiles Include="$(DeploymentProject)\bin\$(Configuration)\**\*.*"
         Exclude="**\*.zip;**\*.pdb;**\*.vshost.*" />
    </ItemGroup>
    <Zip Files="@(ZipFiles)"
         WorkingDirectory="$(DeploymentProject)\bin\$(Configuration)\"
         ZipFileName="$(ApplicationName)-$(Configuration)-$(Version).zip"
         Flatten="True" />
    </Target>

    <Target Name="Backup">
    <ItemGroup>
      <!-- All source code -->
      <SourceFiles Include="**\*.*" 
        Exclude="**\bin\**\*.*;**\obj\**\*.*;*.zip" />
    </ItemGroup>
    <Zip Files="@(SourceFiles)"
         WorkingDirectory=""
         ZipFileName="$(ApplicationName)-SourceCode-$(Version).zip" />
</Target>

See Ben Hall's blog for another example.

Test Task

My final task was to get some NUnit tests running. Again the MSBuild Community Tasks are required for this. It took me a while to get this working correctly, particularly because it seemed to have trouble detecting where my copy of NUnit was installed. Here's the XML:

<Target Name="Test" DependsOnTargets="Build">
    <NUnit Assemblies="@(TestAssembly)"
           WorkingDirectory="MyApplication.UnitTests\bin\$(Configuration)"
           ToolPath="C:\\Program Files\\NUnit 2.4.7\\bin"
           />
</Target>

Conclusion

It took me longer than I wanted to work out how to do these basic tasks with MSBuild, but even so, I am sure the time will be very quickly recouped as I will be able to reuse most of these tasks on future projects. The jury is still out on whether it is preferable to use MSBuild to NAnt though.

Post a Comment