From NetReflector to XAML
A Quick Intro
I always seem to have a big backlog of things to look into – ideas to play around with, concepts to try, etc. – and lately I haven’t had much time to investigate. However, with my pending change to a new job I sometime have small periods with nothing to do (or when I don’t want to start a larger task.) So it is an ideal time to “play” around with some ideas.
Warning: This is just an idea at this point in time (i.e. a proof of concept). It may make it into a future version of CruiseControl.NET. But then again, it might not!
One of the ideas I have been wanting to investigate for a while is using XAML for the configuration. Currently CruiseControl.NET uses a custom serialisation called NetReflector. As far as I am aware, we are the only people using this library, so it doesn’t get a lot of maintenance. Now it works reasonably well (especially considering it was first developed for .NET 1.0!), but there are some limitations with it. I have added some small tweaks to it over time, but it would be nice to move to something more mainstream.
XAML – A New Serialisation?
Anyone who has been working with WPF or Silverlight (or even WF) will be familiar with XAML. XAML is an extension to XML that provides a consistent format for deserialisable definitions. The .NET framework has included classes for reading and writing XAML since .NET 3.0. Unfortunately, there were two (three with Silverlight) versions of these classes with .NET 3.0 and 3.5 – one for WPF and another for WWF (later WF). The good news with .NET 4.0 is there will be a new namespace just for XAML – System.Xaml which will provide a single entry point for working with XAML (although from what I understand WPF will still have its own implementation.)
XamlReader (XamlServices in .NET 4.0) provides methods for loading a XAML definition and converting it into POCO instances. These instances can be any class – they don’t have to be WPF or WF. This means a developer can write their classes and then use XamlReader to load them, without any extra work on their part! Of course, there are attributes that can extend the functionality, but they are optional.
So, in theory it would be nice and easy to change to use XamlReader (or XamlServices when we move to .NET 4.0) and get rid of NetReflector
The Plan
To test this concept, I decided to build a quick configuration reader and some configuration classes (in the future I would like to change how CruiseControl.NET handles its configuration, but that is a post for another time). I’m not going to modify the existing classes, but instead build a separate configuration section. If this is used in future we will probably do it in a different way, but this is just to test the concept.
Current Configuration
To test the configuration I’m going to use a modified version of the configuration for CruiseControl.NET on ccnetlive (modified because its only a proof of concept, I don’t want to have to generate the entire configuration model!)
Here is the configuration I converted:
<?xml version="1.0" encoding="utf-8" ?>
<cruisecontrol>
<project name="CCNet" queue="CCNet">
<webURL>http://ccnetlive.thoughtworks.com/ccnet/server/local/project/CCNet/ViewLatestBuildReport.aspx</webURL>
<modificationDelaySeconds>30</modificationDelaySeconds>
<workingDirectory>e:\sourcecontrols\sourceforge\ccnet</workingDirectory>
<artifactDirectory>e:\download-area\CCNet-Builds\1.5.0</artifactDirectory>
<maxSourceControlRetries>3</maxSourceControlRetries>
<triggers>
<intervalTrigger seconds="300"/>
</triggers>
<sourcecontrol type="svn">
<executable>E:\tools\svn.bat</executable>
<revert>true</revert>
<cleanUp>true</cleanUp>
<trunkUrl>https://ccnet.svn.sourceforge.net/svnroot/ccnet/trunk</trunkUrl>
<timeout>1800000</timeout>
</sourcecontrol>
<tasks>
<nant>
<executable>Tools\NAnt\NAnt.exe</executable>
<baseDirectory>e:\sourcecontrols\sourceforge\ccnet</baseDirectory>
<buildFile>ccnet.build</buildFile>
<buildArgs>-listener:CCNetListener,CCNetListener</buildArgs>
<targetList>
<target>all</target>
</targetList>
<buildTimeoutSeconds>1000</buildTimeoutSeconds>
</nant>
<buildpublisher>
<sourceDir>e:\sourcecontrols\sourceforge\ccnet\Publish</sourceDir>
<publishDir>e:\download-area\CCNet-Builds\1.5.0</publishDir>
<useLabelSubDirectory>true</useLabelSubDirectory>
</buildpublisher>
</tasks>
<publishers>
<merge>
<files>
<file>BuildMetrics\*-result.xml</file>
<file>BuildMetrics\NDependOut\*.xml</file>
</files>
</merge>
<xmllogger />
<artifactcleanup cleanUpMethod="KeepLastXBuilds"
cleanUpValue="75" />
<artifactcleanup cleanUpMethod="KeepLastXSubDirs"
cleanUpValue="75" />
<rss />
</publishers>
<externalLinks>
<externalLink name="CruiseControl.NET Home Page"
url="http://ccnet.thoughtworks.com/" />
</externalLinks>
</project>
</cruisecontrol>
New Configuration
After some playing around (and a bit of research) I came up with the following configuration:
<?xml version="1.0" encoding="utf-8" ?>
<CruiseControl xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://www.thoughtworks.com/ccnet/2/0"
xmlns:tasks="http://www.thoughtworks.com/ccnet/2/0/tasks"
xmlns:scm="http://www.thoughtworks.com/ccnet/2/0/sourcecontrol">
<Project Name="CCNet" Queue="CCNet"
ArtifactDirectory="e:\download-area\CCNet-Builds\1.5.0"
WorkingDirectory="e:\sourcecontrols\sourceforge\ccnet"
WebURL="http://ccnetlive.thoughtworks.com/ccnet/server/local/project/CCNet/ViewLatestBuildReport.aspx"
ModificationDelay="{Time 5, Unit=Minutes}">
<Project.Triggers>
<Interval Period="{Time 5}"/>
</Project.Triggers>
<Project.SourceControl>
<scm:Subversion Executable="E:\tools\svn.bat"
Timeout="{Time 30, Unit=Minutes}"
TrunkUrl="https://ccnet.svn.sourceforge.net/svnroot/ccnet/trunk"
Project.MaxRetries="3"
Revert="True"
CleanUp="True">
</scm:Subversion>
</Project.SourceControl>
<Project.Tasks>
<tasks:Nant BuildFile="ccnet.build"
Timeout="1000"
Executable="Tools\NAnt\NAnt.exe"
BuildArgs="-listener:CCNetListener,CCNetListener"
BaseDirectory="e:\sourcecontrols\sourceforge\ccnet">
<tasks:Nant.Targets>
<tasks:Target>{Parameter buildType}</tasks:Target>
</tasks:Nant.Targets>
</tasks:Nant>
<tasks:BuildPublisher SourceDir="e:\sourcecontrols\sourceforge\ccnet\Publish"
PublishDir="e:\download-area\CCNet-Builds\1.5.0"
UseLabelSubDirectory="True" />
</Project.Tasks>
<Project.Publishers>
<tasks:Merge>
<tasks:MergeFile>BuildMetrics\*-result.xml</tasks:MergeFile>
<tasks:MergeFile>BuildMetrics\NDependOut\*.xml</tasks:MergeFile>
</tasks:Merge>
<tasks:XmlLogger />
<tasks:ArtifactCleanup CleanUpMethod="KeepLastXBuilds"
Value="75" />
<tasks:ArtifactCleanup CleanUpMethod="KeepLastXSubDirs"
Value="75" />
</Project.Publishers>
<Project.ExternalLinks>
<ExternalLink Name="CruiseControl.NET Home Page"
Url="http://ccnet.thoughtworks.com/" />
</Project.ExternalLinks>
</Project>
</CruiseControl>
This is a fully working example – I have put together a loader that will handle it (actually, I’m just calling XamlReader, it does all the work for me!)
Namespaces
First thing to note, there are multiple namespaces. I have added namespace mappings so they give nice URL namespaces, instead of the CLR reference assemblies. There are also multiple namespaces that are needed – the default namespace (http://schemas.microsoft.com/winfx/2006/xaml), plus a namespace for tasks (http://www.thoughtworks.com/ccnet/2/0/tasks) and source control (http://www.thoughtworks.com/ccnet/2/0/sourcecontrol). This is because certain items in the old way needed a meaningless suffix just to identify it from another item with a similar name. The prime example is null items – there is a nullTask, a nullSourceControl, etc. Now they are tasks:Null and scm:Null – a more consistent naming style.
The namespaces also means people can add their own tasks with pre-existing names. Don’t like the way tasks:Null works? You can add your own version – your:Null. It has the same basic name, so people know it does the similar functionality, but since it has its own namespace it will go to your version.
No More Types
The type attribute has been removed from SourceControl (and all other similar examples). Instead the item is defined by adding it as the content, e.g. <scm:Subversion>.
In the old configuration, the type attribute is a nuisance. It is handled in a different way from all the other configuration settings (from an internal code view). This change moves it more in line with the rest of the configuration (plus it is needed for XAML!)
Times and Parameters
Times were another item that are not handled well in the existing configuration. In some places they are an integer value (seconds), other places they are a custom time (that allows specifying the unit).
The XAML version uses a much simpler approach – I have added a mark-up extension to handle the conversion. For example, on the Subversion block the timeout block has “{Time 30, Unit=Minutes}”. This will set the timeout to 30 minutes, or 500 seconds.
This will happily work anywhere there is a time period. Under the hood the property is an integer (will probably need to change to a double). I added a new MarkupExtension called Time that converts from the above syntax to a number of seconds.
Parameters will also work in the same way, but there is one issue I haven’t resolved (yet). Parameters are dynamic values – they change per build. XAML loads a static instance, and it doesn’t change unless re-loaded. This means the {Parameter} setting will be loaded when the configuration is loaded – but we need it to change for every build. Gives me something to look into
In Conclusion
This will require a significant change to the configuration files, but it will simplify the internal workings of configuration a lot! But it offers some nice changes to the syntax, which will simplify things in the long run.
At this point the main stopping point is the requirement for .NET 3.0 or later – CruiseControl.NET still targets .NET 2.0. Perhaps when we do a .NET 4.0 version of CruiseControl.NET we can look at making these changes.
Let me know what you think…
Wow, that is awsome! i love xaml, so this would be a very welcome change
looks good craig
Great to see this exploration of XAML for Config! Made a few comments about it on my XAMLified blog
Thanks, Rob
Rob Relyea
Microsoft, XAML Team