Friday, 27 March 2009

XmlMultiMassUpdate - Extending MSBuild Community Task's XmlMassUpdate

The MSBuild Community Tasks library provides lots of additional and very useful functionality to MSBuild through new MSBuild tasks. One task that we are using on our build server to merge, at build time, certain configuration settings based on the environment we are building for is the XmlMassUpdate task.

XmlMassUpdate takes a source XML document and updates it from another XML document from a particular document root. Our merging routine operates as described in Doran Yaacoby's excellent blog post.

The Problem


The issue we were having is that XmlMassUpdate can operate only on a single XML file. For our build process, we wanted to merge the contents of all substitution.config files into all appSettings.config in the entire solution tree. This is because we use have multiple executing assemblies (ie. web applications, class libraries for unit tests, console applications, etc.) that are being built in a single solution. Also, because we use a templated CruiseControl.NET configuration (and the maintenance overhead), we could not manually hard-code individual XmlMassUpdate tasks in each solution to update the config files. The ability to operate only on a single file at a time was causing problems.

The Solution


To overcome this we created our own XmlMultiMassUpdate task which is based on the XmlMassUpdate task. The task now can take an item group with multiple files and will update all the files in the set. For example:


<ItemGroup>
<ContentFiles Include="C:\example\**\config\appSettings.config" />
<Substitution Files Include="C:\example\**\config\substitutions.config" />
</ItemGroup>

<XmlMultiMassUpdate
ContentFile="@(ContentFiles)"
SubsitutionsFile="@(SubstitutionFiles)"
ContentRoot="/"
SubstitutionsRoot="/" />



Constraints


There are a some constraints when using a file set (rather than an individual file) in the XmlMultiMassUpdate plugin:

  1. The number of files in the content item group and the substitutions item group must be the same.

  2. The original XmlMassUpdate allows the specification of a 'MergeFile' where the output will be written. When using multiple files a merge file cannot be specified. Instead the substitutions file is merged into the content file and the content file gets overwritten with new content.



The Full Source


I have not made the full source code for the XmlMultiMassUpdate available in this post. Mainly because it has been done as part of this project at the NHS Information Centre and all such things should follow the appropriate channels. If you really think it would be useful to you though, please email me at talk@peppermint-it.com and I'll see what I can do.

Wednesday, 18 March 2009

Redefining Configuration Variables: StackOverflowException

The Problem


Today I made some changes to our CruiseControl.NET solution in order to get a project working that is slightly different from our general project structure. After making the changes I restarted the service but it wouldn't start. Looking in the Event Log I got a rather generic "There has been an error in the .NET 2.0 Framework" kind of exception which wasn't very helpful. What was going on?

Some Background


To understand the reason for the service termination, a decent understanding of CCNET's cb:define and cb:scope configuration declarations is needed. For those of you who may not be familiar with these declarations, I'll give you a brief run-down:

cb:define
This declaration allows you to define a variable in the CCNET configuration that can be used later. For example:

<cb:define workingDir="C:\example"/>


will create a variable called workingDir which can be used throughout the project like so:

<workingDir>$(workingDir)</workingDir>


cb:scope
The scope declaration allows you to override a previous definition for a certain scope. The following example shows this:

<cb:define workingDir="C:\example"/>
<!-- workingDir will equal C:\example -->
<workingDir>$(workingDir)</workingDir>
<cb:scope>
<cb:define workingDir="C:\different"/>
<!-- workingDir will now equal C:\different -->
<workingDir>$(workingDir)</workingDir>
</cb:scope>
<!-- workingDir will equal C:\example again -->
<workingDir>$(workingDir)</workingDir>


The Reason


Eventually I discovered why the service wasn't starting. I had kind of assumed that the 'define' and 'scope' declarations were similar to those in a programming language such as C#. In C# the following would work:

string workingDir = "C:\example";
Console.WriteLine(workingDir); // Writes 'C:\example'
workingDir = "C:\different";
Console.WriteLine(workingDir); // Now writes 'C:\different'


This, however, is not the case. My configuration, in an attempt to achieve "C:\example\anotherLevel" as a workingDir within the scope was:


<cb:define workingDir="C:\example"/>
<cb:scope>
<!-- Attempting to get C:\example\anotherLevel -->
<cb:define workingDir="$(workingDir)\anotherLevel"/>
</cb:scope>


This was resulting in a StackOverflowException which was killing the service when the configuration file was read and parsed.

The Solution


In the end I had to effectively redefine the complete path within the scope:

<cb:define workingDir="C:\example"/>
<cb:scope>
<!-- Redefine with full path -->
<cb:define workingDir="C:\example\anotherLevel"/>
</cb:scope>


While this results in a bit of duplication in the configuration file, I think that a working service is more important!

Thursday, 12 March 2009

CCNET & Subversion: Invalid SSL Certificate

As we are developing our automated deployment pipeline and are refactoring our build process anyway, we have decided that now is the time to make the long awaited switch from Visual Source Safe to Subversion for our source code control. With a SVN server already installed and a repository ready and waiting we set to work changing our ccnet.config to use Subversion source code control blocks. It was then that this issue raised its head.

The Problem


Our SVN server was configured to use SSL for a little bit of extra security and best practice. The thing is that the SSL certificate was a self certified one (rather than an official one from a recognised body) which means the svn.exe console application used by Cruise Control.NET was prompting as to whether the certificate should be accepted (due to its invalidity). The trouble is, CCNET had no idea what to do and just errored then and there.

The Solution


To overcome this issue we had to log in and manually run the SVN console application against the repository. When prompted we said to accept the certificate permanently. This had to be done with the same user under which the CCNET service runs.

Once this was done CCNET could access Subversion successfully and we were on our way to a successful switch.