Repeatable builds are central to starting on the path to a successful continuous integration strategy. What I mean by repeatable builds is that a developer should be able to get the source code from a repository and in a single step run a set of complex tasks, tasks such as code quality checking, compiling the source code, running tests, determining code test coverage, labelling versions or deploying finished software. That single step process must produce the same result for every developer, on every machine so that developers can concentrate on writing code and know confidently that the supporting processes are in place and providing the desired feedback in a centrally managed manner.

There are a number of build automation platforms, NAnt, MSBuild and Rake to name a few of the popular systems in the .NET space. There is no right or wrong answer in choosing a build automation system, although I find MSBuild to be the one I use the most, primarily do the fact that many companies are not comfortable with non-Microsoft products. It also counts in MSBuild’s favour that any machine that has a full .NET framework installed automatically has MSBuild installed. Personally I think Rake, which is ruby based, is a fantastic product that provides a very clean way to configure your builds in a programmatic manner, however I will use MSBuild for all the examples in this post.

Conventions

The first step to ensure that code can be built on any machine is to make sure that all source code dependencies are part of the source code repository so that there are no prerequisites for a build to succeed on a particular machine. I follow a set of conventions for every project that makes this as easy as possible and minimises the time to setup a new project.

On the left above is the root of a new project containing a folder for source code (called src) and a folder for all external dependencies (called tools). The build script is in the root folder and is named the same as the solution file in the src directory. A reports directory is created during the build process where all tool output is collected possibly to be consumed by a CI tool. The tools directory contains the binary dependencies the source code requires and external tools the build process depends on, in the past I manually downloaded these tools and placed them in this directory however with the advent of package managers such as NuGet and OpenWrap I let these tools manage as many of these as possible.

While a build process can be used to perform many different operations, there are a small number of operations that cover what most projects commonly require. A single MSBuild file can contain configurations for many of these operations and when we run the build file we are able to choose one of these operations to perform, they are termed targets by MSBuild. A target may depend on other targets for example a target responsible for running tests will be dependent on the target responsible for compiling the source code as the test runner needs a compiled assembly to test. The generic targets I begin each project with an be seen below and consist of:

For tests I follow a convention that has each test assembly named such that it ends with “.Tests”, the same goes for each context specification assembly which ends in “.Specs”. By following these conventions and using no hard coded values in the MSBuild configuration I can reuse the same build file in a new project immediately by just creating the directory structure, adding the source code to the appropriate directories and naming the build file the same as the source code solution file. As the entire build is self contained we can integrate the results into the CI server of our choice and not have to rely on a particular CI servers features. The build can be executed by a developer or a CI server by running msbuild someproject.msbuild from the command line or for example msbuild someproject.msbuild /t:Tests to run just the Tests target.

MSBuild configuration file details

I’ll give a brief breakdown of what each section of my standard MSBuild build file does. There are plenty of comments in the file so it should be most self explanatory.

MSBuild configuration global properties section –

In this section I define paths, file names, executable tool locations, tool options and version numbers that are used throughout the configuration. All paths are defined relative to the MSBuild configuration file’s location and names are based on the MSBuild file’s name.

Imported Tasks Section –

The targets in MSBuild consist of a set of MSBuild Tasks, many tasks are built-in but third party sets of tasks exist such as the MSBuildExtensionPack and MSBuildCommunityTasks. Below are some examples of adding third party tasks to the configuration file, I include these tasks in the tools directory:

The clean target firstly removes the reporting directory from a previous run if it exists using a Condition construct before recreating an empty reports directory. The MSBuild task is used to perform operations on a Visual Studio solution file, here we tell MSBuild to execute the built in “Clean” Target on the solution. This is the same as if you clicked the Build –> Clean Solution menu in VIsual Studio.

The label target is mostly used by a CI server to version a particular build, this excerpt is from a CruisCeontrol.Net CI Server setup and uses the global variable “CCNetLabel” to set the revision number of the assembly and assemblyfile details. I use the MSBuildExtensionPack task to accomplish this. The CreateItem task is used to get a list of AssemblyInfo.cs files from the solution that exclude the Tests and Specs assemblies if they conform to the conventions of having names ending with .Tests or .Specs.

The Code Quality task uses the StyleCop task to scan the solution code file for stylistic compliance. An interesting parameter to take note of is “TreatErrorsAsWarings”, if you set this to false your build will fail on stylistic errors, this is desirable on new projects but may be difficult on existing projects until the code base is cleaned up with a tool such as Resharper or CodeMaid.

The Coverage task is perhaps the most complex task,mainly due to the fact that I am using an open source coverage framework called opencover that is relatively young and does not yet have a dedicated MSBuild task. If you are willing to pay for a license NCover has better MSBuild support. Points to take note of is the use of &quote; to escape quotes for command line arguments to the Exec MSBuild task and the use of the ReportGenerator tool after execution to transform the output from opencover.

The specifications target uses the MSBuild Exec task to execute the Machine.Specifications console runner to run the specifications on assemblies that match our convention i.e. name ending in .Specs.

The Build Target:

<!-- The default build task that pulls the other tasks together, usually executed by a developer -->
<Target Name="Build" DependsOnTargets="Clean;CodeQuality;Compile;Test;Specs">
</Target>

The build target is the default target (specified in the root Projects node at the top of the file). This target just lists the targets it depends on and these are executed in order. This target would be used by a developer to execute the build on a local machine, it does not label the project assemblies.

The CI Target:

<!-- The CI build task that pulls the other tasks together and includes assembly labelling that pulls in the CI servers build number -->
<Target Name="CI" DependsOnTargets="Clean;CodeQuality;Label;Compile;Tests;Specs">
</Target>

The CI target is the same as the default build task just with the addition of labelling the project assemblies with the next build number. This task would be specified on the command line by a CI server.

The CICoverage Target:

<!-- The CI build task that pulls the other tasks together, tests are run including codecoverage and includes assembly labelling that pulls in the CI servers build number -->
<Target Name="CICoverage" DependsOnTargets="Clean;CodeQuality;Label;Compile;CodeCoverage;Specs">
</Target>

The CICoverage target is the same as the CI target with addition of code coverage information.

These simple set of targets when used with project conventions create the ability to produce valuable repeatable builds with minimal effort, it is literally a case of create the directory structure, add the source code, name the build file to match the source code solution and you are ready to build. There is obviously a LOT more one can do and I hope to explore some complex real world problems in future posts.

One Comment

Excellent post. Thanks for sharing.Even though it’s a bit old, this post still rocks.

For anyone having trouble with the opencover and Nunit, saying DirectoryNotFoundException if you use a CreateItem tag to assemble all the assemblies. The problem is with Nunit console runner not being able to deal with a list of assemblies separated by semicolon. Which the default output from the CreateItem.

This can be easily fixed by adding a separator symbol: So that the whole