zaterdag 7 juni 2008

Setting Up a Continuous Integration process using CruiseControl.NET and MSBuild.
Part I: creating the MSBuild build script

Intro

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. :)

Requirements

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:


  • Make sure that the latest buildscript will be used

  • Clean the source directory

  • Get the latest version of the codebase out of Visual SourceSafe

  • Build the entire codebase

  • Execute the unit tests that I have using NUnit

  • Perform a statical code analysis using FxCop


The MSBuild build-script

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.

Skeleton of the buildscript

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'.

Clean Target

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:

Getlatest Target

In order to get the latest version of the source out of SourceSafe, I’ve created the following step:



Here, I just make use of the VssGet Task that is part of the MSBuild Community Tasks project.
Also, notice that this Target depends on the createdirs Target; this means that, when you execute the getlatest Target, the createdirs Target will be executed before the getlatest Target is executed.

The createdirs tasks looks like this:

BuildAll Target

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 $(outputdir).

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 $(outputdir) variable.

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:

Offcourse, you can put multiple solution files in the Projects attribute of the MSBuild Task.
You’ll have to separate the sln files with a semicolon.

NUnit Target

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 .Tests.dll).

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).

FxCop Target

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.

Therefore, I eventually opted to put the path where FxCop is installed in my %PATH% environment variable, and decided to use the Exec Task so that I could call the fxcopcmd tool:

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 FxCopCmd.exe.

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 ...


Executing Targets via MSBuild

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.

For instance:

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.

1 opmerking:

Anoniem zei
Deze reactie is verwijderd door een blogbeheerder.