Sep 26

In Part 1 I talked, quite generally, about what automated builds and continuous integration servers are. In this part I’ll walk you through setting up a simple automated build script for your company’s extension library.

Setting Up Your Project

Project ExplorerFor a while now I’ve been creating an Internal folder under the main project, which has folders for the Tools (NAnt, MbUnit, etc), documentation (if needed), libraries, etc. This has been working out well, but I’m probably going to switch the method used by many open source projects, where the top level directory has a src (for your actual source code), lib (for reference assemblies), and bin (for tools) folders. See the image at the right for my current layout.

Notice that those tools, NAnt/MbUnit/NCover/etc, are actually checked into the project. They’re not sitting in my Program Files directory or on some network share. Each project has a copy of all the tools it needs (and everything those tools need to run), which enables not only the build server to pull down everything it needs from source control, but new developers as well. One checkout command and they’re good to build and run the project. This is definitely a time saver, and, if nothing else, I highly recommend implementing this practice or one similar.

For reference, I’ll be using NAnt 0.85 (available here), MbUnit 2.4.197 (available here), NCover 1.5.8 (one of the last free versions available before they became a commercial product – and while this version doesn’t support some of the newer stuff in C# 3 as their commercial version does, it’ll work for our purposes – available here), and NCoverExplorer (which is now also commercial and integrated into NCover, but I have the latest freely copy available here).

The Build Script

Create a file in the root of your project to hold the actual NAnt configuration, which will build your project and run its unit tests. I usually name the file

A couple of quick pointers for working with NAnt:

  1. The word artifacts, as in most build systems, refers to anything produced by the build system itself, such as reports, executables, installation files, documentation, etc.
  2. Variable declaration & use:
    <property name="build.dir" value="bin" />
    <delete dir="${build.dir}" />
  3. Method declaration (normally called targets):
    <target name="compile" description="Compiles the project using MSBuild."></target>
  4. Outputting text to the screen:
    <echo message="Outputting this message to the screen." />

You start a NAnt build script with

tags, which specifies the project name and the default target (method) to run when one isn’t specified by the calling application:

<project name="MyExtensions" default="build-server"

Now for the meat of the build script. Let’s start off with a few basic parameter declarations:

<property name="build.project" value="MyExtensions" />
<property name="build.dir" value="${build.project}\bin" />
<property name="build.config" value="Release" />
<property name="build.fullPath" value="${build.dir}\${build.config}" />
<property name="build.toolPath" value="C:\WINDOWS\Microsoft.NET\Framework\v3.5\msbuild.exe" />
<property name="tools.dir" value="${build.project}\Internal\Tools" />
<property name="build.testBuildDir" value="${build.project}.UnitTests\bin" />
<property name="reports.dir" value="${build.dir}\Reports" />
<property name="reports.ncover" value="${reports.dir}\NCover-Report.xml" />
<property name="build.outputPath" value="\\fileServer\Assemblies\MyExtensions" />

These specify the path to the build directory, where the MsBuild executable is on the machine (which we’ll use in a later section), where the tools are located, and where to output various artifacts. All of these paths are relative to where your build script is located, so if you placed it in your root folder with your Visual Studio solution file, these paths should work out.

Next we’ll specify a four targets (methods); two to act as convenience targets that call out to other targets, one that cleans the current build artifacts, and the last one that compiles the project by using the Visual Studio solution file:

<target name="build-server" depends="clean, compile, unitTests, ncoverexplorer-report publishOutput"
	description="In addition to the normal full build, it copies the solution output to a specified network share." />
<target name="full-build" depends="clean, compile, unitTests, ncoverexplorer-report"
		description="Does a full build of the project and runs unit tests." />
<target name="clean" description="Destroys the directory where all assemblies/reports are generated.">
		failonerror="false" />
		failonerror="false" />
<target name="compile" description="Compiles the project using the MSBuild executable.">
	<echo message="Using MSBuild to build configuration: ${build.config}" />
	<exec program="${build.toolPath}"
		commandline="${build.project}.sln /p:Configuration=${build.config} /nologo /noconsolelogger /noautoresponse" />

Notice that the build-server and full-build targets use the depends attribute, which will call out to each of the specified targets, in order. The build-server and full-build targets are identical except for the last target call, publishOutput. Discussed below, this target will copy the library’s build outputs to our file share for everyone to access. Since we only want to do this on the build server, and not when ran locally, we’ll name the target differently.

The clean target just deletes the bin directory if it exists, and the compile target will call out to the actual MsBuild.exe to compile the solution.

There are built-in NAnt tasks (or available in the NAnt Contrib project) that will compile solutions and do a few of the other tasks that I’m doing by hand, such as running NUnit and building installers. I prefer this method, though, for more control over what’s getting called and less breakage when upgrading various tools.

OK, so I go and say that, and now show you the unitTests target, which uses a custom NCover task. I made an exception for this step, since NCover normally requires a special COM object to be registered before it’s ran, which I had no interest of doing through a script. The custom task takes care of all that:

<target name="unitTests" description="Runs all needed unit tests with MbUnit and checks coverage with NCover.">
		unless="${directory::exists(property::get-value('reports.dir'))}" />
	<!-- Call NCover, which will call MbUnit to run the tests.
		While MbUnit runs, NCover does its work.
			- To add additional unit test libraries, add the full path to the unit test DLL
			   at the end of the commandLineArgs attribute, separating it with a space
			   and being mindful of the ${build.config} variable.
			   Do NOT wrap this line, as NCover will fail.
			- To add a new assembly you want to check coverage on, add the assembly
			   name at the end of the assemblyList attribute, separating them with a
			   semi-colon.  -->
	<ncover program="${tools.dir}\NCover\NCover.Console.exe"
			commandLineArgs="/report-folder:${reports.dir} /report-name-format:MbUnit-Report /report-type:Xml ${build.testBuildDir}\${build.config}\${build.project}.UnitTests.dll" />

As the comment says, the NCover task will set itself up as needed, then call MbUnit to run through the unit tests, while it basically keeps an eye on what parts of your code are getting called. NCover then produces a report listing each function point (usually equal to a line in your code) that was hit while the unit tests ran. More function points being called == higher code coverage percentage.

This next target will call out to NCoverExplorer, which simply takes in the NCover report made in the previous target and generates a report of its own for use in its GUI app, along with a nice little HTML report for display in CruiseControl.NET’s interface later on:

<target name="ncoverexplorer-report" description="Produces a condensed report in XML format from NCover.">
	<exec program="NCoverExplorer.Console.exe" basedir="${tools.dir}\NCoverExplorer">
		<arg value="/xml:${reports.dir}\NCoverExplorer-Report.xml" />
		<arg value="/html:${reports.dir}\NCoverExplorer-Report.html" />
		<arg value="/project:&quot;${build.project}&quot;" />
		<!-- Minimum coverage for a "passed" test in %. -->
		<arg value="/minCoverage:95" />
		<!-- Show the highest level of detail in the report. -->
		<arg value="/report:5" />
		<arg value="${reports.ncover}" />

Now we simply copy the project’s output (or in our case, the .dll from the extension library) to an output directory. I usually have it copy it to a commonly accessible file share for easier access:

<target name="publishOutput" description="Publishes the solution's output by copying it to a specified directory.">
	<copy todir="${build.outputPath}" overwrite="true">
		<fileset basedir="${build.fullPath}">
			<include name="${build.project}.dll" />
			<include name="${build.project}.pdb" />

An optional last step is to create a batch file in the root of the project which simply calls out to the NAnt executable, passing in your new build file as a parameter. This batch file can then be ran to kick off the full build script by calling build.bat full-build:

@MyExtensions\Internal\Tools\NAnt\NAnt.exe %*

Which runs through and results in a nice little “BUILD SUCCEEDED” message:

Build Script

Gives me the warm and fuzzies every time.

Well, that’s pretty much it. A very basic build script, but it gets the job done. I’d recommend poking around the build scripts of some of the more popular open source projects to get better idea of what these scripts are really capable of automating for you. Take a look at Ninject’s for building a public framework that targets different platforms, or Subtext’s for building a website solution.

A skeleton project setup with this build script, complete with the needed tools and everything, can be downloaded here.

In Part 3 I’ll go over setting up a basic build server using Cruise Control.NET. The build server basically just calls out to this build script, so, thankfully, the bulk of the work is already done.