Aug 11, 2010

Automating your builds and deployments with MSBuild

I think I’ve posted before about build automation but I didn’t really stress how important this is for a dev team. This is probably the one thing that will support your team day in and day out. It will save you lots of tedious, manual labor and a lot of frustration when you integrate with other systems. Once you get your builds going on a build server like TeamCity visibility into issues increases dramatically. You can also expect issues to be resolved dramatically faster than before. One thing to remember is that this new increased visibility is not to give you ammo against you team mates, but to enable you to help them. The bottom line is that automating your build and deployments is well worth the time you’ll spend to set it up.

This will probably turn into a little series, but I’ll show how to set up a basic build script. I’ll be using MSBuild but the same concepts apply if you happen to be using nant. I used to prefer nant because I find it easier to work with. MSBuild has a somewhat cumbersome, awkward syntax but it seems easier to adopt in Microsoft environments.

I’ll assume a simple solution that consists of a web project, some library projects and at least one test project. The solution is self contained – in other words all the tools and libraries we need are in the solution folder. All we need to do is check out and off we go. This enables us to build without even opening studio. Our goal is to clean the solution, build it and run the unit tests. As you will see this is not very difficult to accomplish.

The basic building block for a MSBuild script is a target. A target is a group of tasks that will be executed sequentially in the order they are defined in your build script. A very basic build script can look like the one below:

<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="Build">
        <MSBuild Projects="solution.sln" />
    </Target>
</Project>

This a simple project that consists of one target – Build – which also happens to be the default target for the project. We save this as solution.build for example. Using the .build extension actually gives you intellisense when working with your build file in VS 2010. You can setup a batch file that will call MSBuild and execute the script we just created with the following line:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\msbuild.exe solution.build %*

This will execute the default target in your build file – Build. The ‘%*’ means that any parameters that you pass to this batch file will be appended to the command line used to invoke MSBuild.

One thing that we would like to do is ensure a clean build. We need to clean the output folders of all assemblies that might be stale before we build our solution. We can easily do this by setting up a new target called Clean. In here we’ll define a list of files we’d like to delete using and ItemGroup. Once defined, the item group can be passed to the Delete task to clean our output folders. Your new build script will look similar to this:

<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="Build" DependsOnTargets="Clean">
        <MSBuild Projects="solution.sln" />
    </Target>

    <Target Name="Clean">
        <ItemGroup>
            <BuildFiles Include="$(MSBuildProjectDirectory)\**\obj\**\*.*" />
            <BuildFiles Include="$(MSBuildProjectDirectory)\**\bin\**\*.*" />
        </ItemGroup>
        <Delete Files="@(BuildFiles)" />
    </Target>
</Project>

Here we’ve introduced a new target and leveraged a couple of important MSBuild features. We stated that the Build target depends on the Clean target. MSBuild will now ensure that the Clean target runs before the Build target. Inside the Clean target we used ‘$(MSBuildProjectDirectory)’ – this means we would like to use the value of the property ‘MSBuildProjectDirectory’. In this case it is a build in property but we’ll see shortly how you can define your own properties. We are now at a point where our script can perform a clean build of the solution. Time for a check in! In fact you can now start to leverage this build script on a build server like TeamCity where by defining some artifacts you can have your build output packaged and ready for deployment.

Finally we would like to control the active configuration for our project. We don’t necessarily want to package and ship debug assemblies! We can accomplish this easily by defining a property named Configuration in our build script. This will most often take one of two values - [Debug|Release]. We’ll pass the value of this property to our MSBuild tasks in our Build target. The build script will now look like this:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <Configuration Condition="$(Configuration) == ''">Release</Configuration>
    </PropertyGroup>

    <Target Name="Build" DependsOnTargets="Clean">
        <MSBuild Projects="$(ProjectName).sln" Properties="Configuration=$(Configuration)"/>
    </Target>

    <Target Name="Clean">
        <ItemGroup>
            <BuildFiles Include="$(MSBuildProjectDirectory)\**\obj\**\*.*" />
            <BuildFiles Include="$(MSBuildProjectDirectory)\**\bin\**\*.*" />
        </ItemGroup>
        <Delete Files="@(BuildFiles)" />
    </Target>
</Project>

We can now change the active configuration for our build simply by passing a command line parameter to our batch file.

build.bat /p:Configuration=Release

We now have a very basic build script that we can start using in a build server or from the command line. This pretty much concludes the first part of what is to become a series.