Archive

Archive for the ‘Inner Workings’ Category

Dynamic Parameters and Configuration: Inner Workings

15 December, 2009 1 comment

What’s So Hard About Them?

It appears that one of the more popular improvements for the 1.5 release is dynamic parameters (and their associated elements dynamic values). A dynamic parameter allows an administrator to define a project definition with some “placeholders”. When the project runs, it populates these “placeholders” from either user-specified values or some defaults.

Now, the concept is very, very simple – just replace some item with a value. Unfortunately because of the way configuration has been implemented in CruiseControl.NET, this is not that simple a task :-( So let’s take a look at how configuration works in CruiseControl.NET and how it has been “hacked” to allow for dynamic parameters.

From Text to Data

At the moment, all the configuration for CruiseControl.NET is loaded from a XML file (basically a plain text file). This gets converted from XML (which is not very useful to the system) to POCOs (Plain Old Class Objects). A developer new to CruiseControl.NET would think that this involves a bit of magic (I certainly did!)

The way this conversion works is via a third-part library called NetReflector. This uses attributes on the classes to convert the XML into POCOs. For example, here is a piece of the executable task:

[ReflectorType("exec")]
public class ExecutableTask
    : BaseExecutableTask
{
    [ReflectorProperty("executable", Required = true)]
    public string Executable = string.Empty; 

    [ReflectorProperty("baseDirectory", Required = false)]
    public string ConfiguredBaseDirectory = string.Empty; 

    [ReflectorProperty("buildArgs", Required = false)]
    public string BuildArgs = string.Empty; 

    [ReflectorArray("environment", Required = false)]
    public EnvironmentVariable[] EnvironmentVariables = new EnvironmentVariable[0];

    [ReflectorProperty("buildTimeoutSeconds", Required = false)]
    public int BuildTimeoutSeconds = DEFAULT_BUILD_TIMEOUT;

    // Other code omitted
}

The ReflectorType attribute at the start of the class defines <exec> as an item that can appear in the configuration. The ReflectorProperty attributes then define the allowed values on <exec>. So far, so good.

The first three properties defined are all strings – so these get converted into strings within the POCO. The fourth property is an array of EnvironmentVariables – so what happens here? This is where NetReflector does its stuff and automatically generates POCOs from the XML configuration. The same happens with the last property, which is an int, NetReflectors handles the conversion automatically.

This process is all done at start-up. CruiseControl.NET starts up, loads the configuration, runs it through NetReflector and the resulting POCOs are then fixed until the configuration is reload. In theory the values from the configuration are supposed to be read-only after the configuration has been loaded, but it is possible that the classes can change their configuration values :-(

What’s The Big Deal?

At this point, you may be wondering what the big deal is?

When people want dynamic parameters, they also want a nice easy way to use them. For example, if the project had parameters for baseDirectory and time-out, people want to use the following type of configuration:

<nant>
  <baseDirectory>$[baseDirectory]</baseDirectory>
  <buildTimeoutSeconds>$[timeout]</buildTimeoutSeconds>
</nant>

Now baseDirectory is no problem, because the underlying data type is a string. But how do we handle timeout? The underlying data type is an int – so NetReflector tries to convert this and fails with an exception! Nasty :-(

And unfortunately, due to the way NetReflector works, it is not an easy change to handle this.

Evolution of Dynamic Values

When I first started on this feature I added dynamicValues as another configuration element. So the above example would be written as:

<nant>
  <baseDirectory>C:\Somewhere</baseDirectory>
  <buildTimeoutSeconds>600</buildTimeoutSeconds>
  <dynamicValues>
    <directValue>
      <parameter>baseDirectory</parameter>
      <property>baseDirectory</property>
      <default>C:\Somewhere</default>
    </directValue>
    <directValue>
      <parameter>timeout</parameter>
      <property>buildTimeoutSeconds</property>
      <default>600</default>
    </directValue>
  </dynamicValues>
</nant>

Now this worked (more on this below), but as you can see the parameters now take up more space then the actual task definition! When the feature was first released this is the first thing they complained about.

So, I went back to the drawing board.

Now the application liked the dynamicValues element because it allowed it to change the settings when the project was run. Even though the underlying value is changed, because the dynamicValues element is static, the application can reset them because it always knows what to set. People didn’t like this because it was extra work, plus it was harder to figure out exactly what needed to be where (plus if you misspelt the property name, things didn’t work!)

In contrast people liked the short-hand version – it was similar to other programs (e.g. NAnt, MSBuild, etc.), it reduced the possibility of errors and most importantly – it was less typing. The application hated it because of NetReflector (see above) and even for string values it caused problems (the value was being overwritten.)

Thus we had two solutions – one that people liked but was technically impossible and the other that worked but was disliked. So the next step in the evolution of this feature was to add a converter from one to the other.

Configuration Pre-processing

CruiseControl.NET already has a pre-processor, but after looking at it, I went with a different approach (it was too complex to try and add it to the existing processor). The new pre-processor is specific to dynamic values and it involved hacking NetReflector a little. The change was very simple – NetReflector will check to see if a class has a pre-processor method on it, if it does it passes the XML for that class into that method to be converted.

The CruiseControl.NET side involved converting the shorthand parameters into the full XML definition. So the user would enter the first (short) version, the pre-processer would convert it and the application would see the second (long, ugly) version. This provides a win-win situation for both the application and the users :-)

Now, I could go into the technical details on how it does this, but if you really want to see how, take a look at the code. All I’ll say for now is it scans all the elements and attributes for any short-hand parameters and then generates a matching parameter for it in the configuration. It is a little nasty in the conversion step, because it needs to handle sub-properties and arrays, but it appears to be working.

And Finally, How are They Used?

The pre-processing is all to set up the dynamicValues element. This then gets converted into POCOs so the CruiseControl.NET can use them. The actual usage is at run-time. When a project runs a task (or a labeller or source control block) that has parameters it will pass the parameters into that task (or other item). In order for this to work, the item needs to implement IParamatisedItem, which is the interface that says “hey, I can use parameters”.

To help with using parameters there is a static utility class called DynamicValueUtility. This uses reflection to find the property and set its value. The tricky part was matching the configuration property name to the actual property name (since one is in the code and the other is set via an attribute), but it is all working, including handling arrays. The actual classes then call this utility class to perform the actual work.

To make things even simpler, there are a few base classes (TaskBase, LabellerBase and SourceControlBase) that implement this functionality by default. All a class needs to do is derive from one of these classes and it automatically get dynamic parameters functionality.

That’s All Folks

This has been a quick look at dynamic parameters and how they came to pass in CruiseControl.NET. Hopefully in a future version we’ll make them work even better, but considering the current limitations, this provides a working implementation :-)

Debugging WebDashboard

One of the current issues with debugging WebDashboard is a developer needs to attach to the IIS process under which it is running, which assumes the developer has the right version of Visual Studio. Yuck!

Visual Studio 2005 and later comes with its own web server, which can be used for debugging. This is as easy as setting the web project as the start-up project and pressing F5 (or using the appropriate menu command). Everything else is handled automatically for the developer.

Unfortunately this doesn’t work for WebDashboard – instead an error pops up saying a DLL cannot be started directly. The reason for this history – CruiseControl.NET was originally a .NET 1.0 project and it has slowly been migrated over time.

So, why can’t we use the built-in web server? Because the project file has not been configured as a “web application” (does anyone remember those annoying web site projects?), so Visual Studio doesn’t know how to use the web server.

The good news, is this is very easy to change! While I was away I had a situation where I wanted to debug the dashboard, but couldn’t (I was having issues with IIS 5.0 on my laptop). So I poked around in the project configuration a bit and found this line:

<ProjectType>Local</ProjectType>

Looking at .NET 2.0 web projects (or later) they have a different line:

<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

Things have changed a bit between 1.1 and 2.0 – and obviously the upgrade wizard couldn’t handle it.

So, replacing the first line with the second is all that is needed to upgrade to a proper web application, including being able to use F5 debugging.

Nice and simple, I really should have looked into this earlier!!

Inner Workings: The Anatomy of the Dashboard

10 June, 2009 1 comment

An Introduction

On the mailing list there was a question today about the parts of the dashboard and how they match up with the configuration. So I thought it’s time for me to write down what I know about the parts of the dashboard and how they work.

First, there are four basic levels to the dashboard. These levels are:

  • Farm – the areas of the dashboard that do not relate to any build server
  • Server – the overall summary of a build server (e.g. an instance of the CC.NET service or console)
  • Project – a project on a build server
  • Build – an instance of an integration for a project on a build server

Each level has its own set of plug-ins, as would be expected since they show very different information. To show these levels, here is a screen shot of each.

Note: These are running on my development instance of CC.NET, hence they don’t have a proper version number or very many plug-ins installed.

Farm Level View

Dashboard-Farm

Server Level View

Dashboard-Server

Project Level View

Dashboard-Project

Build Level View

Dashboard-Build

It is important to know about these levels, because putting a plug-in in the wrong location will put items in an unexpected place!

The Parts of a Page

Every page has four basic parts – and a plug-in only has control over one part. These four parts are:

Dashboard-Parts

The header and the footer are both generated by the main page template. These contain common items to every page, such as the name and version of CC.NET, common links, breadcrumbs and when the date/time the page was rendered. These do change slightly depending on the level, but this is outside the control of a plug-in.

The side bar contains a set of links. Again, these vary slightly depending on the level, but they are controlled by the system again. However, these links are defined in the configuration – each plug-in exposes one or more links to add to the side bar. More on this in a little while.

Finally, there is the content area. This is entirely up to the plug-in to populate. The system completely ignores the content area and assumes the plug-in will generate meaningful content. This content is accessed by clicking on one of the links in the side bar.

Overlying these parts onto a farm level view looks like the following:

Dashboard-Parts2

Where the Config Comes In

Hopefully by now, you will have some ideas of where things fit. But let’s make things perfectly clear and delve into how the configuration relates to the pages.

First, here is the configuration I used to generate the above screen shots:

  1: <dashboard>
  2:   <remoteServices>
  3:     <servers>
  4:       <server name="local" url="tcp://localhost:21234/CruiseManager.rem" allowForceBuild="true" allowStartStopBuild="true" backwardsCompatible="false" />
  5:     </servers>
  6:   </remoteServices>
  7:   <plugins>
  8:     <farmPlugins>
  9:       <farmReportFarmPlugin />
 10:       <cctrayDownloadPlugin />
 11:       <administrationPlugin password="********" />
 12:     </farmPlugins>
 13:     <serverPlugins>
 14:       <serverReportServerPlugin />
 15:     </serverPlugins>
 16:     <projectPlugins>
 17:       <projectReportProjectPlugin />
 18:       <viewProjectStatusPlugin />
 19:       <latestBuildReportProjectPlugin />
 20:       <viewAllBuildsProjectPlugin />
 21:     </projectPlugins>
 22:     <buildPlugins>
 23:       <buildReportBuildPlugin>
 24:         <xslFileNames>
 25:           <xslFile>xsl\header.xsl</xslFile>
 26:           <xslFile>xsl\modifications.xsl</xslFile>
 27:           <xslFile>xsl\NCoverSummary.xsl</xslFile>
 28:         </xslFileNames>
 29:       </buildReportBuildPlugin>
 30:       <buildLogBuildPlugin />
 31:       <xslReportBuildPlugin description="NCover Report" actionName="NCoverBuildReport" xslFileName="xsl\NCover.xsl"></xslReportBuildPlugin>
 32:     </buildPlugins>
 33:     <securityPlugins>
 34:       <simpleSecurity />
 35:     </securityPlugins>
 36:   </plugins>
 37: </dashboard>

Like I said, it is very simple. The part that we are interested in the the <plugins> section. This defines all the plug-ins that can be seen. For the moment, I’m going to ignore the <securityPlugins> as this is specific to 1.5.0 and I’ve already covered it in a previous post.

There are four sections of plug-ins: <farmPlugins>, <serverPlugins>, <projectPlugins> and <buildPlugins>. These map to each level in the dashboard. Within each section there are one or more plug-ins. These plug-ins define the links that appear in the side bar. For example, in the <farmPlugins> section:

  • <farmReportFarmPlugin> maps to the “Farm Report” link
  • <cctrayDownloadPlugin> maps to the “Download CCTray” link
  • <administrationPlugin> maps to the “Administer Dashboard” link

Most plug-ins have these link titles as hard-coded values within them – so they can’t be changed. Clicking on a link will pass control to the plug-in, which generates the content to be displayed (this is actually a simplification, but it will do for this post.)

Normally, there is only one instance of a plug-in per section – having multiple plug-ins generates some unexpected results, so it is recommended against. However, some rules are made to be broken.

Breaking the Rules – <buildPlugins>

The above details cover most plug-ins in the dashboard. However, the one area where the rules get broken is in the <buildPlugins> section. This section defines two special plug-ins - <buildReportBuildPlugin> and <xslReportBuildPlugin> (1.5.0 will be adding a third – <htmlReportPlugin>).

First, the <xslReportBuildPlugin> element. This breaks the rules by allowing multiple instances. It does this because of two properties: description and actionName. The description is the text of the link to appear in the side bar, while the actionName is the command name to be passed to the server. The actionName MUST be unique, otherwise the poor dashboard will get confused!

This plug-in takes in a XSL-T template and transforms the build log for a project into an HTML report (which is the third parameter in the element). This means we don’t need to develop lots and lots of plug-ins (e.g. one for each report), instead we can just write a style sheet and get it to transform the results.

The second rule breaker is <buildReportBuildPlugin>. This is a required plug-in and there can only be one instance of it. The reason it is different is it has an <xslFileNames> section in it. This section is similar to the xslFileName attribute in the <xslReportBuildPlugin>, but it has one major difference. The <xslReportBuildPlugin> generates a link in the side bar, the <xslFileName> element doesn’t. Instead, it’s transform gets merged into one big page – the “Build Report”.

The following picture shows how these relate:

Dashboard-BuildParts

Clicking on an <xslReportBuildPlugin> will generate a completely different content area, one that is not affected by <buildReportBuildPlugin> at all.

Summary

There are four general levels – farm, server, project and build. Each has its own config section and allows a different set of plug-ins.

Within each page, there are four areas – header, footer, sidebar and content. The plug-in generates the content area and defines links to go into the sidebar – everything else is handled by the system.

For the build level, there can be multiple <xslReportBuildPlugin> – each defines a link in the side bar, with custom content. The <xslReportBuildPlugin> section defines the items to appear within the “Build Report” – these do not appear as links within the side.

Hopefully this provides a better understanding of the parts of the dashboard.

Inner Workings: The Project Scheduler

20 February, 2009 3 comments

The Heart of the Matter

The core of CruiseControl.Net is the project scheduler. This is the piece of code that is responsible for scheduling builds – without which CruiseControl.Net just wouldn’t work.

Before I delve into the actual workings of the scheduler, let’s quickly review how builds can be scheduled.

First and foremost, in order to schedule a project build a trigger is required. There are a number of different types of trigger – from interval triggers to scheduled triggers to projects that monitor other locations or projects. Additionally triggers can be combined or filtered. But one thing all filters have in common is they tell the scheduler a build needs to be performed.

The second part of the scheduler is the queues. Early versions of CruiseControl.Net had each project running in its own little world – they didn’t affect other projects. As the number of projects increased, this lead to increased contention on the build servers and a lack of resources. To counter this queues were added. A queue is a group of projects, of which only one can have a running build at any point in time. Generally they work on a first-in, first-out basis, but it is possible to set queue priorities.

With this background, let’s delve into how project scheduling actually works

Managers, Integrators and Queues

The main class that handles everything in CruiseControl.Net is CruiseServer. This is responsible for starting everything and handling all user interactions. But, it doesn’t actually handle the scheduling of builds. This is handled by a number of other classes.

Looking at CruiseServer, there is a IntegrationQueueManager class. This encapsulates all the actual projects and their integrators. Now an integrator implements IProjectIntegrator and is responsible for the actual triggering of a build. In a moment I’ll return to how it does this, but first, how is an integrator started.

When IntegrationQueueManager is instantiated it iterates through all the projects and ensures that there is a queue for each project. If there is no queue, then it creates a new queue with the same name as the project. These queues are all added to an IntegrationQueueSet.

Once the queues have been initialised, the IntegrationQueueManager then calls ProjectIntegratorListFactory to generate all the project integrators. As well as containing the project configuration, the integrator also contains a reference to the associated queue. At the moment there is only one IProjectIntegratorProjectIntegrator.

This completes the initial setup of the queues and integrators, the next step is to start the projects integrating. This is done by calling the StartAllProjects() method (by CruiseServer), or by calling Start() for a specific project (StopAllProjects() and Stop() do the opposite).

When StartAllProjects() is called, it iterates through all the project integrators and checks to see whether the project can start. This involves checking the configuration and then the state persistence (both new in 1.4.3). If both these checks start, then the integrator is started. The actual starting of the integrator is done by calling the Start() method on the integrator.

Nice and simple, but here’s a diagram to illustrate this process:

Build Startup

Triggering Builds

The above initialisation got to the point of calling Start() on ProjectIntegrator. This method starts a new thread that contains a polling loop. This loop checks to see if there is an integration every 100ms. If there is an integration it then calls the Integrate() method on Project which performs the actual integration (e.g. pre-build, source control, tasks and publishers).

This check consists of two parts. First it checks the queue to see if there is a pending integration request. If there is a request, it locks any queues that need to be locked, starts a new request and calls the Integrate() method. After this it cleans up and exits the check logic.

The second part of the check, which is only performed if there is no pending request, is to check all the triggers. Each trigger has a Fire() method, which performs the actual check. The output of the fire method is an integration request or null – if the output is not null then it gets added to the queue.

The root level trigger is a combination trigger, which merely iterates through each child trigger and calls its Fire() method – it is up to each child trigger whether it returns a request or not.

Once the trigger checking has finished, it checks the queue to see if the request is next. If not, it enters a loop until the request is ready. Then when the request is ready, the check logic finishes – it doesn’t actually call Integrate() after the trigger checking. What happens is the polling loop goes through another cycle and then the integration request gets returned from the first part of the check.

The following diagram shows this:

Integration Polling

Queues

The final piece of the puzzle is the integration queues. I’ve already mentioned them a couple of times, but let’s pull them apart and see how they actually work.

First of all, queues do not use the built-in queue classes – they use a List<> instance instead. The reason for this very simple – they do more than just adding and removing items – they also allow re-ordering (based on priorities). Plus items on the queue are not removed until completed – which would cause issues with de-queuing.

In turns of how they work. When an item comes in, the queue checks to see if it already exists. If the item exists it applies any re-ordering rules (ignore, re-add or replace existing), otherwise it just adds it to the queue. It will look at any other items in the queue and then add it after the last item with the same priority, or before the next highest priority.

When the integrator checks for a request, it will always return the item in position 0 (the start of the list). This item remains there until the integrator performs its clean-up.

Finally, there are a few call-back methods that are used to synchronise the state between the queue and the integrator.

And that’s all there really is to queues – very, very simple. The following diagram shows how queues relate to the integration polling cycle.

Integration Polling Queues

Summary

This post has covered how project integrations work and how builds are scheduled.

The main driver for the process is IntegrationQueueManager, which responsible for initialising everything and then starting the actual integration cycle. The actual integration cycles are handled by a polling thread within each integrator.

The actual process for starting a build is controlled by both the integrator and the queue. The integrator checks the triggers to see if a build should be scheduled, and when one is found it adds it to its associated queue. Then, in the next polling cycle it retrieves the request off the queue and actually performs the integration.

The queues act to limit the number of project builds at any time, and do this by storing requests. The integrator will only perform a request when it is the first item in the queue.

Final Note

This post is about how builds are scheduled – I haven’t covered how an actual build is performed. But that is an entirely different topic, so I will leave that for another time :-)

Inner Workings: Configuration in the Dashboard

18 February, 2009 Leave a comment

Introducing Configuration

One of the nice things about the dashboard is the ability to customise the plug-ins that it uses. These plug-ins are configured in the dashboard.config file, which sits in the root folder of the web site.

While it is easy to configure plug-ins, the question is how can we modify how the code reads the configuration to include new configuration items?

First off, configuration in the dashboard uses NetReflector. This takes care of the actual serialisation and deserialisation of the configuration. In order to add a new property it is merely a matter of adding a ReflectorPropertyAttribute to the property that needs to be serialised/deserialised. The harder questions is where should this attribute be added?

Configuration Interfaces

A quick look into the configuration folder will show the following interfaces:

IW-Configuration Interfaces

These three interfaces define the information that can be retrieved from the configuration. The top interface – IDashboardConfiguration – defines the entry level configuration. This interface maps to the <dashboard> element in the configuration file.

The other two classes map to the next level in the configuration – IPluginConfiguration maps to plug ins, while IRemoteServicesConfiguration maps to <remoteServices>. Within each of these interfaces are the actual configuration properties, most of which map to other interfaces or classes.

The main point to know from here, is the dashboard stores these interfaces and uses them, instead of the actual implementations of the interfaces. The first place to add new configuration information is these interfaces.

Next, since these are interfaces, they don’t contain the NetReflector attributes – these attributes are defined on the actual implementations.

Configuration Implementations

There are four classes that implement the configuration interfaces:

IW-Configuration Classes

NetReflectorPluginConfiguration and NetReflecotRemoteServicesConfiguration are both very straight-forward. They implement the associated interfaces and contain the NetReflector attributes. There is a simple one-to-one mapping between the properties in the interfaces and the classes, and also to the configuration elements.

DashboardConfigurationLoader is the class responsible for loading the configuration settings. Internally it uses NetReflector to do the actual serialisation and deserialisation. It also implements IDashboardConfiguration and is responsible for exposing the second-level configuration, which is where things start to get a little messy.

DashboardConfigurationLoader uses lazy-loading for retrieving the configuration. When the instance is instantiated it calls GetTypeTable() to initialise NetReflector and then stops. Later, when the application wants to retrieve either the plug ins or remote services configuration it calls the relevant method (LoadPluginsConfiguration() or LoadRemoveServicesConfiguration()) to load the configuration and then stores it internally. These methods both rely on the Load() method to find the relevant XML node in the configuration and then use NetReflector to load it.

As such DashboardConfigurationLoader does not have any NetReflector attributes in it – instead it needs to be coded to handle new configuration items. As such it is easier to add new configuration items to either <plugins> or <remoteServices> than it is to add it to <dashboard>.

The final class in configuration is CachingDashboardConfigurationLoader. This class has a internal reference to a DashboardConfigurationLoader which it uses to do the actual work. The main work this class does is storing the DashboardConfigurationLoader instance in the HttpContext cache and retrieving it from there on subsequent use.

This means the first time the dashboard starts it will load the configuration from the file – every other time it retrieves the cached configuration. This is vitally important because it means the dashboard will not pick up any changes to the configuration! If the configuration is changed then the dashboard must be restarted!

Tying It Together – the Objection Store

While this explains how the configuration works, it doesn’t explain how to get an instance of configuration to where it is needed. The answer to this question is very easy – just add a IDashboardConfiguration, IPluginConfiguration or IRemoteServicesConfiguration argument to the constructor of the class that needs it. This argument will then be populated by Objection.

Objection is an IoC library that does all the bindings between the different components. It is smart enough to auto-detect which classes implement which interfaces, and it also has the ability to be configured. CruiseObjectSourceInitializer is the class that handles this configuration and it is used for linking the configuration interfaces to the actual implementations.

For configuration, all three interfaces are loaded into the store. CachingDashboardConfigurationLoader is loaded for IDashboardConfiguration and the other interfaces are loaded from the properties on CachingDashboardConfigurationLoader.

Finally, the good news is CruiseObjectSourceInitializer is called automatically by the system prior to any other processing. This means it is then available to every other class.

Summary

Configuration in the dashboard is exposed via three interfaces: IDashboardConfiguration, IPluginConfiguration or IRemoteServicesConfiguration. IPluginConfiguration and IRemoteServicesConfiguration use simple NetReflector-based implementations for exposing configuration, while IDashboardConfiguration is implemented by a loader class.

There are two configuration loader classes – one that does the actual load and a second that caches the results. This caching loader means any configuration changes will not be detected until the dashboard is restarted.

Finally configuration is made available by requesting it in the constructor of a class. Objection is responsible for adding the configuration implementation and can do so because it has been loaded in CruiseObjectSourceInitializer before any other processing is done.

Inner Workings Posts

18 February, 2009 1 comment

Since I’ve been involved in coding for CruiseControl.Net I’ve spent a lot of time just trying to figure out how it works. Some things are straight-forward and can be changed with a minimum of fuss. Other things seem to obfuscated and are almost impossible to change.

However, given enough time – both to read the code and debug it – it’s possible to understand what is happening. And rather than forget all these things I’ve spent the time learning, I thought I’d write them down.

These posts will be technical and aim to cover different hard to understand parts of CruiseControl.Net. I’m going to try and cover all the components of CruiseControl.Net – server, CCTray and dashboard – with a focus on some of the more challenging areas.

As a background I’m going to assume people know C# and have a basic understanding of the different parts of CruiseControl.Net (Remote, Core, WebDashboard, CCTrayLib, etc.) plus have looked around the code some.

Mainly I’m going to focus on how these issues affect development. They will be built around breaking down a “problem” area and seeing how to work with the code. As such I’m more interested in the code rather than the functionality, although the two are related. So what I cover will have relevant to how CruiseControl.Net works, but the focus won’t be on it.

So stay tuned and feel free to give me any feedback – I promise I’ll listen :-)

Follow

Get every new post delivered to your Inbox.