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.


Template expansion with nAnt

If you have a nAnt driven build system, or are planning on putting one together, template expansion can be a very useful tool. It helps you account for variability in the build environment, be it on developer machines or the build server. I’ve posted a couple of links before for nAnt, (see JP’s excellent tutorial series) but this one keeps coming up so I figured I’d dedicate a post to it.

You can simply define properties in your build file and .config templates (or other templates) with tokens to be replaced with property values. When you copy the template file over, the tokens can be replaced by their respective property values, using the filterchain feature.

Here’s a very simple file that doesn’t do much besides copy a .config file over to the deploy folder and expand the tokens in it; right below it there is a section of the web.config.template we will use;

   1: <?xml version="1.0"?>
   2: <project name="eploringormpatterns">
   3:   <property name="connection.string" value="Data Source=ARES;Initial Catalog=ormpatterns;Integrated Security=True" />
   4:  
   5:   <target name="expand.template" default="true">
   6:     <copy file="src\web\web.config.template" tofile="deploy\web\web.config">
   7:       <filterchain>
   8:         <replacetokens>
   9:           <token key="connection.string" value="${connection.string}" />
  10:         </replacetokens>
  11:       </filterchain>
  12:     </copy>
  13:   </target>
  14: </project>

   1: <?xml version="1.0"?>
   2: <configuration>
   3:   <connectionStrings>
   4:     <add name="ormpatterns" connectionString="@connection.string@"
   5:         providerName="System.Data.SqlClient" />
   6:   </connectionStrings>
   7: </configuration>

When you run this build file the web.config.template file will be copied over to “deploy\web\web.config” and the @connection.string@ token will be replaced with the value in the property with the same name.

It would be nice if we didn’t need to touch the .build file every time we needed to customize a property. Well, there’s a way to do that. We can extract the properties that vary in a separate file, say local.settings.xml and include that in the build file. That way we only need to change a tiny file to make the build run in our environment. We end up with something like the file below:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <properties>
   3:   <property name="connection.string" value="Data Source=ARES;Initial Catalog=ormpatterns;Integrated Security=True" />  
   4: </properties>

and in the build file we include the local.settings.xml file like so:

   1: <include buildfile="local.settings.xml" />

Now we have one simple file to edit in order to account for changes in the environment and we never have to touch the build file for small changes.

On a final note, when setting up your nAnt build system, try to avoid folders with spaces in the name. This will save you much pain and suffering,


Search

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010