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