I've changed the layout of my weblog, I hope you like it.
If you have any remarks regarding the layout, if you don't find it readable, if you miss something, please let me know.
The tasks that CruiseControl.NET should execute for your project, are configured in the ccnet.config file.
The ccnet.config file can contain multiple project configuration blocks. However, I like to have each project-configuration in it's own, separate file. In my opinion, this is much more manageable.
In order to put each project-configuration in its own XML file and import it in the ccnet.config file, you can make use of DTD entities to substitute constants with the contents of other XML files.
This is how I've done it:
<!DOCTYPE cruisecontrol [
<!ENTITY project1 SYSTEM "file:D:\folder\project1_ccnet.xml.config">
<!ENTITY project2 SYSTEM "file:D:\folder\project2_ccnet.xml.config">
The above piece of code makes sure that the &project1 and &project2 'placeholders' are replaced with the content of the project1_ccnet.xml.config and project2_ccnet.xml.config files.
I just saw that CruiseControl.NET 1.4 has a new approach to accomplish this, however, I haven't tried it yet.
The CC.NET config file is in fact very simple. You just have to put the Tasks that you've defined in your MSBuild file in the CC.NET config file.
Your CC.NET config file could look like this:
The above configuration file is by all means not complete; I've kept it simple, and left out some tasks. However, you should have an idea :)
The reason why I specify which executable must be used by msbuild, is very simple:
My project is written in VS.NET 2008, but targets the .NET 2.0 framework. So, by default, CC.NET will use the MSBuild program that has been delivered with the .NET 2.0 framework.
This results in an error: MSBuild doesn't recognize the VS.NET 2008 solution file format, and will stop with this error:
Solution file error MSB5014: File format version is not recognized. MSBuild can only read solution files between versions 7.0 and 9.0, inclusive.
This is offcourse due to the fact that the MSBuild that is used by VS.NET 2005 doesn't know anything about the solution file format that is used by VS.NET 2008.
You can solve this issue by specifying that CC.NET should use the MSBuild executable that can be found in the directory of the .NET 3.5 framework.
It is possible that CruiseControl.NET will not be able to execute your project, because CC.NET can't find an appropriate XmlLogger.
In this case, you'll find the following error in the CC.NET logfile:
Cannot create an instance of the logger. Could not load file or assembly 'ThoughtWorks.CruiseControl.MsBuild.dll' or one of its dependencies. The system cannot find the file specified.
You can solve this problem by placing the XmlLogger for MSBuild (you can find the dll here in your project working directory.
I’ve been struggling lately to get a new project that I’ve started at work, under Continuous Integration.
Although I’ve used CruiseControl.NET & NAnt in my previous project for CI purposes, things didn’t go so smooth now ...
In my current project, I’m using Visual Studio.NET 2008 and targetting .NET 2.0.
Now, I wanted to use MSBuild for the build process and that’s where it all started.
I had to spent some time searching on the Net in order to get everything working like I wanted. It seems that there’s no single source of documentation for MSBuild & CC.NET which addresses all the problems that I’ve encountered.
So, the intention of this article, is to help other people setting up a CC.NET environment with MSBuild, and it will also serve as a reference for me, so that I can grab back to it when needed. :)
What I wanted to achieve, is very simple:
I have a build machine where CruiseControl.Net is installed. This machine is already used for another project of mine for which I’m using NAnt for the build process.
The new project that I’ve started, is being developped in VS.NET 2008, targets the .NET 2.0 framework and is under SourceControl via Visual SourceSafe.
I wanted to have a CI process that regularly looks in VSS and, when something has changed, performs the following tasks:
In order to automate all the steps above, I needed to have a build-script first which I can execute using MSBuild.
Such a script is also handy when the application you’re building consists of numerous VS.NET solutions; instead of opening each solution separately in Visual Studio, compiling it, opening the next solution ..., you can build the entire codebase using a single command line.
This is quite handy and productive, I can tell you :)
For every ‘task’ (clean source directory, get latest, build codebase, etc… ) that I want to execute, I’ve created a Target in the build script.
A first little problem I encountered was that MSBuild doesn’t contain any tasks that would allow you to get a latest version out of VSS, run NUnit unit-tests or perform a code analysis with FxCop out of the box.
Fortunately, there exists an open source project called the 'MSBuild Community Tasks Project' which contains additional tasks that can be executed by MSBuild. This means that you don’t need to write your own MSBuild Tasks.
Before creating the Targets, I’ve defined a few properties which I will use in all the tasks:
I define the working directory (where my source can be found) as the builddir, and a directory where the assemblies that have been build should be placed (outputdir).
Next to that, I also have an artifactsdirectory where the results of the unittests and code analysis will be put.
The last line in the above code is necessary so that we can use the additional MSBuild Tasks that can be found in the MSBuild Community Tasks Project.
Now, we can start creating our 'Targets'.
I want to have the possibility to start from a 'clean sheet', so I really need a Target which justs deletes everything that can be found in my builddir and outputdir.
This Target is very simple; you just have to make use of the Delete Task:
The createdirs tasks looks like this:
This is the first target where I’ve had some issues, although it’s task is very trivial:
Compile and build everything that can be found in the
$(builddir), and make sure that the assemblies that have been built are placed in the
It seemed very easy to do, since I found out that MSBuild.exe (which is the program I use to compile the code) had a property
OutputDir. So, it would be fairly easy to set this property to the
Alas, to no avail. My assemblies were never copied to the output-directory. Eventually, I discovered that there also exists an
OutputPath property, so I tried it. This seemed to work.
So, the buildall Target looks like this:
This Target is quite simple:
With this Target, I run the NUnit tests that have been written in my test assembly. (I tend to name all my Test-assemblies
The results of the unit-tests procedure are placed in the artifacts directory as an XML file. In this way, I can easily incorporate the test-results in my CC.NET report (more on this later).
This one was a bit cumbersome.
I started out writing this Target with the FxCop task that can be found in the MSBuild Community Project; it looked like this:
The reason why I do not apply the output XSL stylesheet, is very simple: I want CC.NET to display the results on the Dashboard, so CC.NET should read the XML file, and apply the XSL stylesheet.
Now, this Target just worked fine on my development box. However, on my ‘build server’ *ahum* (my previous dev Workstation), I couldn’t get it working.
On the build machine, I constantly kept getting errors.
Apparently, msbuild was trying to locate FxCop in
C:\Program Files\Microsoft FxCop 1.32, but I don’t have this old version of FxCop installed.
I’m using FxCop 1.36 beta instead.
In order to keep my build script a bit readable, I’ve created an
ItemGroup in where I define all the command-line arguments that I want to pass to
By default, the items that are defined in an
ItemGroup will be concatenated with a semicolon. This is something I do not wanted offcourse, since command line arguments should be separated by a space.
It is easy to define that the items should be separated by a space:
@(Args, ' ')
There’s a litle sublety with the Exec command however: it doesn’t work well when you have a commandline argument that is a path which contains a space. You should escape such paths with quotes, but I haven’t succeeded in getting it to work with MSBuild yet ...
Now that we’ve defined all the Targets, we need to see if they work offcourse.
Executing a Target is fairly easy:
Just open up a VS.NET command prompt (or open a regular command prompt and make sure that the path to the MSBuild.exe utility is in your path environment variable), and navigate to the location where your msbuild build-script is located.
Then, you just execute MSBuild, make sure that your build script is used, and tell MSBuild which target he should execute. You can also override the default values of the parameters (like $(outputdir) ) that we’ve defined in our script.
msbuild myproject.msbuild /t:buildall /p:outputdir=r:\myproject\release /p:buildmode=release
I think that this is enough text for today. I will soon post a subsequent article in where I’ll explain how to use this script in CruiseControl.NET.