A continuous integration server is an essential tool in the box of any team – even a one man team. I’ve used a number of different servers, but in the end I’ve always come back to CruiseControl.Net. It’s a solid, no-nonsense server which has great community support. Since it supports MSBuild scripting, it lets you use almost any tool on your controlled builds; MbUnit, NUnit, NCover, and FxCop are the few I use most often, and they’re a tiny subset of all the coding goodness that can be played with.
Continuous Integration in a Very Small Nutshell
In case you’re not familiar with continuous integration (henceforth CI), the story is this: you set up a continuous integration server which will listen to your source control repository and notice when someone adds or changes something in the repository. The server will the proceed to check the changes out, and try to build. Should the build succeed, it will try to execute any other tasks you may have set it, such as running code metrics, unit tests, and so forth. If any of the tasks fails, it will raise a general hue and cry, stamp its little feet, and generally make sure everyone knows that the build is broken as soon as possible. This helps locate issues caused by the changes from two people (or one careless person) within a few minutes of check in at worst, making the task of fixing them so much easier.
You can read a more detailed discussion of CI by the Great Lord Martin Fowler.
Note: If you’re not using a source control system, such as Subversion (SVN) or CVS,I strongly suggest you stop reading now, as this article won’t help you much. What you should do instead is, download Subversion from its website, install it, and learn to love it. It will save your backside one day, trust me. The same goes for unit testing – a CI process without tests can be pretty redundant, so you’d better Google it up a bit.
Ok, CI is Useful… But Why Have Multiple Instances?
Assuming you’ve decided that CI is worth looking into after all, you may still wonder why the hell you would want to have the same project built on multiple machines. In my case, it came about due to a requirement where my team had to ensure that a product works well both on Windows XP and Vista. Many projects ago, way back in my Java days, we’d done the same with a project so we could run unit and integration tests on one machine, and performance tests on another. Since performance tests can be taxing on a machine (especially if you use a crappy one to ensure minimum hardware requirements) you don’t really want to run them on the same machine as your regular build, as they’d slow down your entire process. One of the reasons to have CI is to have fast notification when things go pear-shaped, so slowing it down by a few magnitudes is a really bad idea.
How Do You Go About It?
The following list describes the steps I follow when I’m preparing a project for Cruise Control. I usually externalize the configuration files and properties even if I have no intention of using multiple machines – they’re a lot easier to manage that way, and it allows me to configure tools and paths per server rather than per project.
Note: This is not a beginner’s guide to Cruise Control Configuration. These steps assume that you have a working project configuration to begin with, or can write one up yourself. If you need assistance, you can find a short guide to cruise control .net configuration. A web search will also return plenty of links.
- Externalize all paths in your build script to a properties file.
- Externalize your cruise control project configuration to its own file.
- Add server variables to your main cruise control configuration file.
Externalize all paths in your build script to a properties file
Any build script you use will invariably have a number of external dependencies, such as the executables for additional tools that contribute to the build, run unit tests, and so on. The location of these dependencies might vary from machine to machine, so what you can do is, instead of having the paths directly into your build file, write them out in a properties file instead. Both MSBuild and NAnt support this, and it’s really easy. A properties file is just an xml file with property names and values, like this:
<Project xmlns=”http://schemas.microsoft.com/developer/msbuild/2003″>
<!– Tools –>
<PropertyGroup>
<!– Root path to the sources for any project. –>
<SourcePath>c:\projects\source</SourcePath>
<!– The root folder for the build tools. –>
<ToolPath>c:\build_tools</ToolPath>
…
…
</PropertyGroup>
</Project>
And so on. You can place this file in a location which you are sure will be present in all servers (I like to create a folder called build_tools in c:, and keep this sort of thing there). To reference the properties file in MsBuild, you can use the following syntax:
<Import Project=”c:\build_tools\common.properties”/>
You can then reference properties from the file in your MSBuild script as usual. NAnt uses similar syntax; since I haven’t used it for many moons though, I’ll refer you to the NAnt user manual rather than giving you a specific example.
Externalize your Cruise Control Configuration Into its Own File
If I had to mention one thing that annoys me about cruise control, it has to be the fact that all projects have to be configured in the same file. That bugs me. Luckily, there are ways to work around this problem using simple XML techniques.
First, copy the project element for your project to a separate configuration file. We’ll modify this to make it server-agnostic in a little while, but for now, just save it somewhere safe.
Next, add an entity definition to your main ccnet.config file, like so:
1: <!DOCTYPE cruisecontrol[
2: <!ENTITY myproject SYSTEM "file:c:\projects\configs\myproject\myproject.config">
3: ]>
4: <cruisecontrol>
5: &myproject;
6: </cruisecontrol>
Where myproject.config is the name of the configuration file you just extracted. The path should point to location of the file on the server running the configuration. The entity definition will cause the XML handler to substitute the &myproject; item with the contents of the file – think of it like you’re declaring a constant.
Warning: When you modify the main ccnet.config file, the ccnet service will notice and load the changes. This does not happen when you change configuration files that are linked in using this technique, so if you change the external configuration, your changes will not be picked up until you restart the service or touch the configuration file again.
Add server variables to your main cruise control configuration file.
So far we have a configuration file that we can easily drop onto separate servers. If you’ve externalized all dependencies and placed the appropriate properties file on all your build servers, you should be good to go – except for one minor detail. The project element in your configuration file will also specify the name of the project as displayed by the web dashboard and the cctray utility, as well as the URL that cctray will point you to when you click the “Display Web Site” item. Ideally, you want to have cctray display some indication of what server each build is on, so you can easily tell which is which in case of problems. You can easily solve this as follows: in the main ccnet.config file, add the following entities:
1: <!DOCTYPE cruisecontrol[
2: ...
3: ...
4: <!ENTITY serverDescription "WinXP">
5: <!ENTITY serverAddress "xpmachine.mybuildservers.com">
6: ]>
This will make the &serverDescription; and &serverAddress; entities available to the ccnet.config file and any files it loads in. This means we can go to our project configuration file and change the project name and web URL:
1: <project name="myproject-&serverDescription;">
2: <workingDirectory>C:\Projects\Source\myproject</workingDirectory>
3: <category>CSharpProjects</category>
4:
5: <sourcecontrol type="svn">
6: ...
7: ...
8: </sourcecontrol>
9:
10: <webURL>http://&serverAddress;/ccnet/server/local/project/myproject-&serverDescription;/ViewLatestBuildReport.aspx</webURL>
11:
12: <triggers>
13: <intervalTrigger name="continuous" seconds="300" buildCondition="IfModificationExists"/>
14: </triggers>
15:
16: <tasks>
17: <msbuild>
18: <executable>C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe</executable>
19: <projectFile>C:\Projects\source\myproject\Build.msbuild</projectFile>
20: <buildArgs>/p:Configuration=Debug</buildArgs>
21: <targets>Build</targets>
22: <logger>ThoughtWorks.CruiseControl.MsBuild.XmlLogger,C:\Program Files\CruiseControl.NET\server\ThoughtWorks.CruiseControl.MsBuild.dll</logger>
23: </msbuild>
24: </tasks>
25:
26: <publishers>
27: ...
28: ...
29: </publishers>
30: </project>
Cruise Control will now report the project name and URL according to the values of the serverDescription and serverAddress entities – in this case, we’ll get “myproject-WinXP” for project name, and “http://xpmachine.mybuildservers.com/ccnet/server/local/project/myproject-WinXP/ViewLatestBuildReport.aspx” as a web URL.
Warning: Avoid using spaces in the project name or any part of the URL – cctray encodes spaces into + signs which seem to confuse the web dashboard.
Note: When you change the name of an existing project, the build counter will be reset. This could cause problems if you use automatic tagging of builds.
Where To Go From Here
At this point, you should be able to set up the same project on multiple instances of Cruise Control fairly easily. Also, if you’re using the same build machine for a number of projects, you may find that you can quite easily re-use the same property files between them, and so reduce some duplication in the configurations – it will be worth it if you ever need to change folder structure, for example.
You can also look at MbUnit or NAnt’s conditional tasks, or at MbUnit’s test categories. This would allow you to specify in the properties what sort of tests you want to run on a given build server, so you could run only performance tests, or only integration tests.
Also remember that you could conceivably externalize more configuration blocks from the project configuration file – either to reuse them or to allow the definition of more finely grained, server specific blocks. Be careful with this though, as the chain of files could quickly get out of hand.
RE: Externalize all paths in your build script to a properties file
not sure how you did this, a properties file w/ key and value cannot be imported in MSbuild using the method you displayed. User gets error “The imported project file could not be loaded. Data at the root
level is invalid. Line 1, position 1.”
Ouch! You’re right :$ Mixed up the nant and msbuild properties file. The MsBuild properties file is actually an xml file – corrected the post above.
Many thanks for pointing this out 🙂
Thanks, this article was useful indeed. How about the following:
Suppose I want to re-use the same project twice in the same CCNet.config file but with different ‘parameters’. For example, I want to run the same build script – once on a development branch and then on a release branch. I’d like to use a single CC server for both projects and of course, I don’t want to duplicate the project item contents. Is this doable?
Hi Elad, thanks for your comment.
I see how that would be useful, but I’m not quite sure it’s supported. I’ll look into it though 🙂
Nice article, thanks for sharing!
As for Elads comment (I know it’s old 😉 ), I’m using scoping and reuse blocks in my ccnet.config – it can be really neat. Checkout http://confluence.public.thoughtworks.org/display/CCNET/Configuration+Preprocessor . Write to me at andersjuulsfirma at gmail dot com for a copy of my configuration.
Thanks, Anders 🙂