CI Server

Redefine Your Build Process with Continuous Integration

Jay Flowers

This article discusses:

  • A basic CI server
  • Launching builds and tests
  • Source control and reports
  • Setting up CI Factory
This article uses the following technologies:

Code download available at:ContinuousIntegration2008_03.exe(168 KB)


Lowering the Cost of Adoption
Getting Started
Build and Test
Source Control
Logging and Reports
Triggering a Build
Running the CI Server
Customizing CI Factory
The Rest of the Work

When using a Continuous Integration (CI) system, team members integrate application components early and often, up to several integrations a day per developer. Lest that idea set your teeth on edge, note that each integration is immediately checked and verified by an automated build to find any errors instantly. While the concept of CI might take some getting used to, it can be a valuable system.

A basic principle of a CI system is that the word build needs to be redefined. Formerly, you might have thought of a build as simply a compilation. When it comes to CI, you must understand that the definition of build now includes the all-important steps of verification and testing. CI depends on the presence of tests. Without tests, all you have is continuous compilation.

Since developers are the main users of a CI system, they will determine whether the system is a success or a failure. Developers really only want two things: a build that takes less than five minutes and an easy way to find the cause of a build failure. Success will also depend on your team's build etiquette. I recommend these simple guidelines:

  • Never leave the building if you have broken the build.
  • Don't submit to a broken build.
  • Be nice. When you see that the build is broken, look at the build report. If you know how to fix the issue, contact the developer that broke the build and offer to help.

Lowering the Cost of Adoption

My recent experience suggests that a growing number of development shops are adopting CI as a best practice, and many more would get on the wagon if the cost of adoption was lower. It can take a veteran programmer a couple of days to get a basic CI server up and running; a neophyte might take up to a week.

The frustrating thing is that 80 percent of the work performed preparing a CI server and 80 percent of the work done by the server are the same—clean up after the previous build, get the latest source code, increment versions, compile, run tests, execute static analysis, and create installers. Very little, if anything, varies between CI server instances. This commonality is the key for lowering the cost of adopting continuous integration.

In this article I will explore the work required to set up and use a CI server and various solutions that help get the work done. I will first create a CI server manually using CruiseControl.Net, MSBuild 2.0, MbUnit, and WatiN. Following that, I will show you how to use an open source project called CI Factory to do the same thing with far less effort.

The sample application I will use is a simple guestbook from the open source project NunitAsp. I will compile the application, create an Microsoft Installer (MSI) setup app, install it, and test it with WatiN. The finished guestbook application allows you to save your name and a comment, as shown in Figure 1.

Figure 1 Finished Sample App Saves Names and Comments

Figure 1** Finished Sample App Saves Names and Comments **(Click the image for a larger view)

As you might have guessed, the app is storing the name and comment in the session state. The WatiN test for this is simple, as you can see in Figure 2.

Figure 2 Testing the Sample App

[Test] public void Register() { Ie = new IE("about:blank"); Ie.GoTo("http://localhost/GuestBook/GuestBook.aspx"); Ie.TextField(Find.ByName("name")).TypeText("Jay"); Ie.TextField(Find.ByName("comments")).TypeText("Hello"); Ie.Button(Find.ByName("save")).Click(); Assert.AreEqual("Jay", Ie.TableCell(Find.By("innerText", "Jay")) .Text, @"innerText does not match"); Assert.AreEqual("Hello", Ie.TableCell(Find.By("innerText", "Hello")).Text, @"innerText does not match"); }

WatiN is a Microsoft® .NET testing framework built on Internet Explorer®. It will open a new instance of Internet Explorer and drive the test. The test will enter the name Jay and the comment Hello, click the save button, and validate that the guests table contains the expected content.

The guestbook application does not come with an installer. I created an installer by adding a Visual Studio® Web Setup Project to the solution. I then added the primary output of the guestbook project to the bin folder and the files Global.asax, GuestBook.aspx, and web.config to the Web Application Folder. With that I have an installer!

This leaves a solution containing three projects: the production application guestbook, the test project GuestBook.Test, and the Deployment Project GuestBookSetup. If only we had a CI system to build it ...

Getting Started

From a developer's perspective, I find it helpful to think of CI as task-based development. After I complete a task, I submit to the build. Let's go a little deeper here and talk about task size. Tasks are things that you can complete in a day or less. Tasks are things that, when complete, leave the system in working order. In addition, tasks are generally smaller than requirements.

There are many tasks to be performed when setting up a CI server. Let's start with the physical server that will host the CI server. First, the development environment needs to be installed on the CI server. This can include external dependencies needed for integration testing like Microsoft SQL Server® as well as controls or other runtime dependencies needed for compilation. The remainder of the development environment will contain the development directory tree. Tree Surgeon is a free tool for creating a development tree ( The tree should have clear locations for build scripts, production source code, test source code, and third-party dependencies. With the tree defined, I can begin a build script (see Figure 3).

Figure 3 CI Build Script

<PropertyGroup> <ProjectName>GuestBook</ProjectName> <RootDirectory>c:\Projects</RootDirectory> <ProjectRootDirectory>$(RootDirectory)\$(ProjectName) </ProjectRootDirectory> <ProjectCodeLineDirectory>$(ProjectRootDirectory)\Trunk </ProjectCodeLineDirectory> <BuildDirectory>$(ProjectCodeLineDirectory)\Build</BuildDirectory> <SourceDirectory>$(ProjectCodeLineDirectory)\Source</SourceDirectory> <ThirdPartyDirectory>$(ProjectCodeLineDirectory)\Third Party </ThirdPartyDirectory> <UnitTestDirectory>$(SourceDirectory)\Unit Test</UnitTestDirectory> <InstallDirectory>$(SourceDirectory)\Install</InstallDirectory> </PropertyGroup>

Without getting radical, you should attempt to have no external dependencies. This increases the simplicity of your development environment. If you have no external dependencies, all that a new developer need do is copy the dev tree from the source on your server. That is the ultimate in simplicity. However, most projects include some external dependencies, so all of your external dependencies should be codified in an installation script. Check to see whether the dependency is present, and if not, install it. The script could be executing an install from a shared drive on the network or even downloading the installer from the Web.

Configuration of external dependencies should be codified in your script as well (databases, IIS, registry entries, environment variables, and so on). The key is to spend the few minutes it takes to codify your development environment. Doing this will make setting up a new environment simple when you need to set up additional CI servers.

Build and Test

At this point I've laid the groundwork for a basic build script to manage external dependencies, compile, and execute unit tests, like the following:

<Target Name="Build"> <CallTarget Targets="SetupVirtualDirectory"/> <CallTarget Targets="Compile"/> <CallTarget Targets="RunTests"/> </Target>

The first target, SetupVirtualDirectory (see Figure 4), will map the Web Application Project directory in IIS as a virtual directory. This is the only external dependency for this project. Also, SetupVirtualDirectory allows a developer to avoid the time-consuming step of deploying the Web application. Later, when I am executing this script on the CI server, I will actually perform a full install.

Figure 4 Targeting SetupVirtualDirectory

<Target Name="SetupVirtualDirectory"> <Exec WorkingDirectory="$(SystemRoot)\System32\inetsrv\APPCMD.EXE" Command='Add APP /"Default Web Site" /path:/GuestBook/physicalPath:$(SourceDirectory) \Production\GuestBook' /> <Exec WorkingDirectory="$(SystemRoot)\System32\inetsrv\APPCMD.EXE" Command='Set APP "Default Web Site/GuestBook" /virtualDirectoryDefaults.physicalPath:$(SourceDirectory) \Production\GuestBook' /> </Target>

The compile target is simple as well. It scans the Source directory for any C# projects and compiles them:

<ItemGroup> <Projects Include="$(SourceDirectory)\**\*.csproj" /> </ItemGroup> <Target Name="Compile"> <MSBuild Projects="@(Projects)" Targets="Build" StopOnFirstFailure="false" ContinueOnError="false"> </MSBuild> </Target>

There are two C# projects—the app project and the test project:

C:\Projects\GuestBook\Trunk\Source\ Unit Test\GuestBook.Test\GuestBook.Test.csproj C:\Projects\GuestBook\Trunk\Source\Production\ GuestBook\GuestBook.csproj

Note that while this approach allows for new projects to be added without requiring a change to the build script, you do lose the ability to control the order in which your projects will be built. This creates a significant problem if you have dependencies between projects. In the case where you need to control the build order, explicitly list your projects in the ItemGroup.

To run the unit tests, I will use MbUnit via its associated MSBuild task (see Figure 5). It takes a list of assemblies as an argument and executes all the tests in each. The script is set to scan the Unit Test directory for each and every file in a bin directory matching the format *.Test.dll. As long as developers follow this convention, the build scripts don't need to change for additional test projects.

Figure 5 Running Unit Tests with MbUnit and MSBuild

<UsingTask TaskName="MbUnit.MSBuild.Tasks.MbUnit" AssemblyFile="$(ThirdPartyDirectory)\MbUnit\MbUnit.MSBuild.Tasks.dll" /> <ItemGroup> <TestAssemblies Include="$(UnitTestDirectory)\**\bin\**\*.Test.dll" /> </ItemGroup> <Target Name="RunTests"> <MbUnit Assemblies="@(TestAssemblies)" ReportTypes="Xml" ReportFileNameFormat="unittest" ReportOutputDirectory="$(BuildDirectory)\UnitTestReport" /> </Target>

This script, named GuestBook.msbuild, is located in the Build directory, and I placed a batch file, Build.bat, in the Source directory. Developers will be living out of the Source directory, so having a batch file there will make it easy for them to execute the build script:

call "%ProgramFiles%\Microsoft Visual Studio 8\VC\vcvarsall.bat" msbuild ..\Build\GuestBook.msbuild /t:Build SET /P variable="Hit Enter to exit."

When running the build script on the CI server, I may want to test in a more true-to-life environment. This is easily accomplished by executing an installer. The guestbook solution includes a Web Deployment Project: GuestBookSetup.vdproj. To build this project I need to call devenv.exe instead of MSBuild, since deployment projects are not currently supported by MSBuild:

<Target Name="CreateInstaller"> <Exec WorkingDirectory="$(SourceDirectory)" Command='"C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe" $(ProjectName).sln /Build Debug /project GuestBookSetup' /> </Target>

I will need to execute the install silently, as this will be a fully automated build. This is something that is best known up front when writing an installer. It can be troublesome to try and add this capability after the installer has been written. In the MSBuild script I need a target to install and uninstall, like so:

<Target Name="InstallApplication"> <Exec WorkingDirectory="$(InstallDirectory)\GuestBookSetup\Debug" Command='msiexec /i GuestBookSetup.msi /q' /> </Target> <Target Name="UninstallApplication"> <Exec WorkingDirectory="$(InstallDirectory)\GuestBookSetup\Debug" Command='msiexec /x GuestBookSetup.msi /q' /> </Target>

These targets use the Exec task to execute the MSI file with the application msiexec.exe, found in PATH. The switch /i is for install, /x is for uninstall, and /q is for quiet. This installer package requires no user input; the default settings are acceptable for testing. However, it is possible to pass parameters to an installer package with msiexec.

The entry target for the CI server looks like this:

<Target Name="Triggered"> <CallTarget Targets="UninstallApplication" ContinueOnError="true"/> <CallTarget Targets="Compile"/> <CallTarget Targets="CreateInstaller"/> <CallTarget Targets="InstallApplication"/> <CallTarget Targets="RunTests"/> </Target>

I now have a build script that can perform a personal build on a developer's machine as well as a public build on a CI server. I use CruiseControl.Net, better known as CCNet, to kick off this new build script. CCNet installs to the Program Files directory and can be shared by many different projects. There are two files that are used to configure CCNet: the server's ccnet.config and the dashboard's dashboard.config. The dashboard configuration is simply for display purposes and does not play a part in creation of the product, in this case guestbook. As such, its .config file will not be kept in source control.

The server .config file is a different story and is critical to the build process. In order to manage the .config for guestbook in source control, I will create a new XML file and define the configuration using a Document Type Definition (DTD) referenced from the CCNet server's .config file:

C:\Program Files\CruiseControl.NET\server\ccnet.config <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE cruisecontrol [ <!ENTITY GuestBook SYSTEM "C:\Projects\GuestBook\Trunk\Build\CCNetProject.xml"> ]> <cruisecontrol> &GuestBook; </cruisecontrol>

Source Control

Let's add a new dimension and introduce source control. A CI server should be able to perform a clean build, deleting not only the compiled artifacts, but also the source files and test execution environment. It is a bad practice to perform dirty builds. It is even worse to perform a "sourdough" build. This is where you require a previous build be present in order to successfully build.

Notice in the source control block that a fresh copy of the program source is pulled for every build. Cleancopy is set to true, like so:

<sourcecontrol type="vss" autoGetSource="true" applyLabel="true"> <project>$/GuestBook/Trunk</project> <username>Build</username> <password>password</password> <workingDirectory>c:\projects\GuestBook\Trunk</workingDirectory> <executable>C:\Program Files\Microsoft Visual Studio\VSS\win32\SS.EXE</executable> <ssdir>c:\Source Safe Databases\GuestBook</ssdir> <cleanCopy>true</cleanCopy> </sourcecontrol>

Most source control systems require the same type of configuration information: location of the repository, credentials, path to the root folder of the project, and the working folder on disk. And while the sample for this article uses Visual SourceSafe® (VSS), all the source control blocks in CCNet look similar for different source control providers (Visual Studio Team Foundation Server, Subversion, Perforce, Vault, and so on).

Besides performing a clean get at the beginning of the build, it will also create a label at the end of the build, provided that the build is successful. The label is applied to the project node—Trunk in this case. Remember that Trunk contains everything needed to build this version of the product. So if you needed to come back to this version six months from now, pulling from this label will provide everything you need to successfully build the application. This is something that I have seen many teams neglect.

Many teams fail to see the CI system as part of their product. It may not be something that you deliver to the customer, yet it still plays a critical role in your ability to deliver. Treat it with the respect it deserves and include it in source control, labeling it with your product's source.

Another benefit of this setup is that it lessens the need for anyone to make changes from the build server. Changes to the MSBuild script are easily made from your workstation, checked into source control, and then pulled to the build server by CCNet. This allows you to avoid the troublesome practice of making changes on the build server and then forgetting to check those changes in. The CCNet MSBuild task compiles the solution like so:

<msbuild> <executable> C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe </executable> <workingDirectory> C:\Projects\GuestBook\Trunk\Source </workingDirectory> <projectFile> C:\Projects\GuestBook\Trunk\Build\GuestBook.msbuild </projectFile> <buildArgs> /noconsolelogger /p:Configuration=Debug /v:diag </buildArgs> <logger> C:\...\ThoughtWorks.CruiseControl.MSBuild.dll </logger> </msbuild>

Logging and Reports

MSBuild does not ship with an XML logger, nor does CCNet. You can download one from the CCNet wiki. It is referenced here from the Build directory. XML logs are important to CCNet, as the dashboard UI applies XSLT against the XML output from the build to produce HTML. The MSBuild task will collect the XML and include it in the CCNet XML log file for each build. To see a nicely formatted MSBuild report on the dashboard, you will need to add a configuration entry to the dashboard.config file, located at C:\Program Files\CruiseControl.NET\webdashboard\dashboard.config. Add this node at the location /dashboard/plugins/buildPlugins, as in the code here:

<xslReportBuildPlugin description="MSBuild Output" actionName="MSBuildOutputBuildPlugin" xslFileName="xsl\msbuild.xsl" />

Look back at the build script target RunTests and notice that the unit test execution emits an XML report to the directory \Trunk\Build\UnitTestReport. This XML needs to be included so that unit test reports can be generated. CCNet can then merge this into the final build log for reporting on the Web dashboard by adding a file merge publisher block into the ccnet.config file:

<merge> <files> <file>c:\projects\GuestBook\Trunk\Build\ UnitTestReport\unittest.xml</file> </files> </merge>

The unit test reports for MbUnit can be shown in two places: the summary build report and a detailed report. The detailed report metadata should be added to the dashboard .config file at the location /dashboard/plugins/buildPlugins, like this:

<xslReportBuildPlugin description="Unit Details" actionName="UnitDetailsBuildReport" xslFileName="xsl\MBUnitDetails.xsl" />

Likewise, the summary report should be added at the location /dashboard/plugins/buildReportBuildPlugin/xslFileNames:


Triggering a Build

While all the major parts of the CCNet configuration are complete, we still need to add a trigger to cause the build to occur:

<triggers> <intervalTrigger seconds="60" buildCondition="IfModificationExists" /> </triggers>

This trigger block will potentially cause a build to occur every 60 seconds. CCNet will ask the source control repository if there have been modifications since the last build took place; if the answer is yes, it will trigger a build.

There are many more configuration elements that can be added to the CCNet .config file. It can send e-mail to notify you of the result of a build. You can control the label or version number for each build. There are many interesting things you can do.

Running the CI Server

Now that I have finished configuring CCNet, there are just a few items left before the CI server is fully operational: I need to add everything to source control and start the CCNet server.

Depending on how you installed CCNet, you turn on the server either by starting the Windows service or by executing the ccnet.exe console application. If you have chosen the Windows service, you need to make sure the desktop has access to the service because WatiN requires it.

To view the dashboard, open http://localhost/ccnet in a browser. The main dashboard project grid shows a little information on the progress of a build. There is a download link for the CCTray tool on the left. To use CCTray on your desktop, configure it after installation so that it knows about the build server and which projects you want to subscribe to. This tool then resides in the system tray and alerts you to changes in the status and activity of the build server. It even tries to predict when the build will be complete. Figure 6 shows a successful build report for the guestbook project.

Figure 6 CCNet Report for a Successful Build

Figure 6** CCNet Report for a Successful Build **(Click the image for a larger view)

The build report includes a summary page and several detailed reports. Notice the unit test summary report and links to detail reports on the left. The MSBuild Output detail report is available on the left, too. The View Build Log link displays the raw XML log file for this build instance.

Using CI Factory

Now I will demonstrate how to accomplish the same thing and more with CI Factory, but with much less effort. After downloading and installing CI Factory, open the solution file CI Factory.sln. Open the Arguments.xml file from the Solution Explorer to configure a new project.

First I set the name of the Project to GuestBook:

<property name="ProjectName" value="GuestBook" />

Next I set the names of the folders for the dev tree. Rather than use the default values, I changed these to reflect my development tree, as shown in Figure 7.

Figure 7 Setting the Folder Names for the Dev Tree

<property name="ProjectsDirectory" value="c:\Projects"/> <property name="ProjectCodeLineName" value="Trunk"/> <property name="ProductDirectoryName" value="Source"/> <property name="ThirdPartyDirectoryName" value="Third Party"/> <property name="ProductionDirectoryName" value="Production"/> <property name="UnitTestDirectoryName" value="Unit Test"/> <property name="InstallDirectoryName" value="Install"/>

Now set the build master and developer e-mail addresses. CI Factory will configure CCNet to e-mail a build report to developers when a build to which they have contributed modifications completes. It will additionally e-mail the build owner when a build fails, as you can see from this code:

<property name="BuildMaster.Email" value=""/> <largeproperty name="Developer.List"> <value expand="true" xml="true"> <user name="MyBrotherDarell" group="developer" address=""/> <user name="MyOtherBrotherDarell" group="developer" address=""/> </value> </largeproperty>

There are other options that can be set in this file, though most of the defaults are fine for my purposes here. For example, you can collect coverage statistics on the WatiN tests by setting the following property to true:

<property name="NCover.IIS" value="test" />

Finally, I select the tools stack for the build as shown in Figure 8.

Figure 8 Configuring Build Tools

<strings id="Packages.InstallList"> <string value="VisualSourceSafe"/> <string value="CSDiff" /> <string value="SourceModificationReport"/> <string value="Versioning"/> <string value="MSBuild" /> <string value="VS.NETDeploy" /> <string value="NCover" /> <string value="DotNetUnitTest"/> <string value="nDepend"/> <string value="FxCop"/> <string value="Simian" /> <string value="Analytics" /> <string value="Alerts" /> <string value="Deployment"/> <string value="Workspace"/> </strings>

CI Factory is organized with the concept of packages as a means for extension. The VisualSourceSafe package will provide integration with source control. CSDiff will provide diff reports on the dashboard. Versioning will maintain version information for both the assemblies and files. MSBuild will manage the compilation and reporting. NCover will ... well, you get the idea.

At this point, I am finished configuring the CI Factory installer, so I execute the run batch file C:\Tools\CI Factory\run.bat to create the guestbook project. The script will install required software that is missing and prompt for acceptance to licenses.

Next, the batch file starts the CCNet server as a console application and opens a Visual Studio solution of all the build files. I have found it convenient to have a project dedicated to managing the build scripts. First, only the files that I need to edit are included in the project. It also saves time to have the assistance of the Visual Studio editor when writing XML. It is so much more difficult to create malformed XML in Visual Studio; the editor does a great job of bringing errors to your attention. You can also take advantage of what an XSD (XML Schema Definition) enables the editor to do: provide IntelliSense®! In contrast, Notepad has never brought an error to my attention.

At this point I have a plain installation of CI Factory. The tree it created is very similar to what I created by hand earlier. You may notice the server and dashboard directories in the Build directory. These contain a copy of CCNet dedicated to serving the guestbook project. They are internal dependencies here as opposed to the external and quite possibly shared dependencies in the previous example. The CCNet project configuration file is in the same location as in the previous example: C:\Projects\GuestBook\Trunk\Build\ccnetproject.xml.

The main difference I would draw your attention to is that there are two projects—one to build the guestbook product and one to manage the build scripts. The build scripts project monitors the build directory in source control, simply updating the files on disk from the repository when triggered. The project that builds the product ignores changes made to the build directory.

In my previous example, I had to write an MSBuild script, but here most of that has been taken care of during installation and is maintained in large part by the Main.Build.xml script. Like the MSBuild script I wrote, Main.Build.xml contains a target named triggered, but it does not contain a target named Build. The Build target is in a script named Personal.Build.xml and is located in the Source directory.

There are more batch files than Build.bat. For example, Test.bat will execute just the test portion of the build script. But Build.bat includes more functionality, such as updating a developer workspace from source control:

..\Build\nant\bin\nant.exe -buildfile:Personal.Build.xml UpdateSource IF NOT %ERRORLEVEL%==0 exit /B %ERRORLEVEL% ..\Build\nant\bin\nant.exe -buildfile:Personal.Build.xml %1 %2 %3 %4 %5 %6 %7 %8 SET /P variable="Hit Enter to continue."

Customizing CI Factory

This default install of CI Factory meets a significant portion of my needs, but you can do a lot to personalize the environment through postinstall configuration and other customizations. One thing to point out when customizing CI Factory is that it is configured using NAnt, so even though we use MSBuild to control our build through CCNet, we must use NAnt's XML syntax to customize CI Factory.

The CCNet dashboard is already configured, so there is no need to add configuration information for using different XSLT files. The default install even configured IIS, creating several virtual directories. I altered the dashboard.config slightly, as shown in Figure 9, simply changing the order of the sections shown in the main build report. I prefer to have the deployment files near the top of the report, as that is the section that lists all the hyper links to download files like MSI files, executables, and ZIP files created during the build.

Figure 9 Customizing the CCNet Build Report

<buildReportBuildPlugin imageFileName="images/BuildReport.gif"> <xslFileNames> <xslFile>xsl\header.xsl</xslFile> <xslFile>xsl\modifications.xsl</xslFile> <xslFile>Packages\Deployment\deployment.xsl</xslFile> <xslFile>xsl\compile.xsl</xslFile> <xslFile>Packages\VS.NETCompile\Compile.xsl</xslFile> <xslFile>Packages\MSBuild\compile-msbuild.xsl</xslFile> <xslFile>Packages\DotNetUnitTest\MbUnitDisplaySummary.xsl</xslFile> <xslFile>Packages\NCover\NCoverDisplaySummary.xsl</xslFile> <xslFile>Packages\Simian\SimianDisplaySummary.xsl</xslFile> <xslFile>Packages\nDepend\nDependSummaryDisplay.xsl</xslFile> <xslFile>Packages\FxCop\FxCopSummaryDisplay.xsl</xslFile> </xslFileNames> </buildReportBuildPlugin>

I also made a configuration change to the MSBuild package. By default, it will create a ZIP file of the primary output of all projects in the production tree. In this example, there is only one such project, guestbook, so I don't need a ZIP file; an MSI will be fine. Creation of the ZIP file is disabled by editing these two properties in the package, setting them to false:

<property name="Compile.ToCopyToBin" value="false" overwrite="false"/> <property name="Compile.ToDeployZip" value="false" overwrite="false"/>

Just like in the MSBuild script that I wrote earlier, I need to install and uninstall the MSI file. So I edit the Main.Build.xml file in the Build directory, adding exec tasks before and after the call to run the unit tests:

<exec program="msiexec" commandline="/i ${Install.MsiSourceDir}\${Install.MsiSourceFile} /q" verbose="true"/> <call target="UnitTest.RunTests" /> <exec program="msiexec" commandline="/x ${Install.MsiSourceDir}\${Install.MsiSourceFile} /q" verbose="true"/>

I also want to maintain the good developer experience from the previous example and ensure that developers have the guestbook Web Application Project directory mapped in IIS as a virtual directory. I do this by adding a configuration script to the Workspace package. I create a new directory named IIS in the workspace's Configuration directory and add a new file, IIS.Script.xml. The script is simple, containing only one target to create the virtual directory:

<target name="Workspace.Configuration.IIS"> <mkiisdir dirpath="${ProductionDirectory}\GuestBook" vdirname="GuestBook" defaultdoc="GuestBook.aspx" enabledefaultdoc="true" /> </target>

To have this target called, I need to register it with the Workspace package. This is done by adding an include task as well as adding IIS to the list of configuration scripts:

<strings id="Workspace.Configuration"> <string value="MbUnit"/> <string value="IIS"/> </strings> <include buildfile="Configuration\IIS\IIS.Script.xml"/>

Now that I have a working build server, I'll add the guestbook product to source control and see the build triggered.

CI Factory's main dashboard project grid (see Figure 10) shows lots of information about current build status and activity, such as the build time elapsed and the changes that have been detected. The download for CCTray is on the left, same as before, though this installer will configure CCTray for you.

Figure 10 Build Status Reported in the Dashboard

Figure 10** Build Status Reported in the Dashboard **(Click the image for a larger view)

When the build is complete I can view the report for version, as shown in Figure 11. You will notice the link to the MSI file is included in the build report.

Figure 11 Build Report

Figure 11** Build Report **(Click the image for a larger view)

Scrolling down you can see summary reports from some of the other tools that I chose to include in the Package list during the install: NCover, Simian, and FxCop. You can also see links to their detail reports, including NDepend. These packages require no extra configuration on my part.

The Rest of the Work

What I demonstrated in this article is 80 percent of the work that is common to all CI systems. You might be wondering, what is in the other 20 percent? Managing the database for an application is a common example. I use a source control repository to help keep my team in sync. The CI system can play the leading role in synchronizing the team's databases both for personal use and public build server testing.

Another common example is system deployment, not just to a development or test environment but to a customer environment as well. Some of the most advanced and successful CI systems are handling deployment directly into customer environments. This is today's ultimate goal—CI all the way out to the user desktop.

Some interim goals to help give you headroom are things like automating the creation of your release notes, extending the CI system to the test team's desktops (similar to how the build.bat extends into developers' desktops), and the creation of a virtual machine or even a set of virtual machines in a release build in addition to installers. The common theme here is automating what can be automated and running that automation often.

Jay Flowers is the creator of the CI Factory open source project. He contributed the CI Factory section to the book Windows Developer Power Tools. Jay has also been involved in the MbUnit project and other smaller open source projects. His enthusiasm for software development is evident in the articles and blog posts on