Dynamic Build Parameters – Some Examples
Posted by Craig Sutherland on 12 May, 2009
Some New Exciting Functionality
In the 1.5.0 release we are adding the ability to define dynamic build parameters. A dynamic build parameter is a value that can change for different builds – and it can be set by the user when they perform a force build.
This addresses a simple issue in the current CruiseControl.NET – if even one parameter in a project needs to differ between builds, multiple projects are needed! Now, an administrator can define just one project and allow multiple different variations on it.
In this post I’ll go through some of the possibilities for this feature. I won’t cover everything, and the feature itself is still in its infancy, but hopefully this will provide a start for people to trial it.
A Very Basic Configuration
To keep things simple, I’m only going to work with a very simple configuration (ccnet.config). This is the base config:
<cruisecontrol><project name="Test Project"><description>A demonstration project to show the features of the dynamic build parameters.</description><sourcecontrol type="svn"><trunkUrl>svn://svn.mycompany.com/firstproject/trunk</trunkUrl><workingDirectory>C:\SourceControl\FirstProject\</workingDirectory></sourcecontrol><triggers><intervalTrigger /></triggers><tasks><nant><buildFile>App.build</buildFile><targetList><target>Dev</target></targetList><buildArgs>-D:reason=testing</buildArgs></nant><gendarme><assemblies><assemblyMatch expr="*.dll"/></assemblies><limit>100</limit></gendarme></tasks><publishers><rss /><xmllogger /></publishers></project></cruisecontrol>
This project uses Subversion as the source code repository, using a repository called firstproject. It has the default interval trigger (checks for modifications every 60 seconds). When a build is triggered it will run an NAnt build script called App.Build and then perform a Gendarme analysis. Finally it will generate an RSS feed and log the XML results (so the dashboard can use it.)
Basically I’m going to add three dynamic parameters:
- Define the target to build
- Change the reason for the build (a buildArg for the NAnt script)
- And vary the limit on the Gendarme analysis
And to finish things off, I’ll show how the same project can be used to generate different builds via a schedule.
So, let’s get the show on the road.
Defining a Target
The first parameter I want to vary is the build type. This is a target in the NAnt script, and I’m going to allow three different targets: Dev, Test and Prod (this could also be done as a buildArg, but I want to leave that for later!)
The first thing to do is to add the parameter to the project. Parameters are defined at the project level for two reasons:
- It saves duplicates if the parameter needs to be used in multiple places
- It makes it simpler for the server to work out which parameters need to be sent to the client (thus it is faster)
Since the build target can only be one of three values, I’m going to define a range parameter:
<parameters><rangeParameter name="Type"><description>The type of build to perform.</description><allowedValues><value>Dev</value><value>Test</value><value>Prod</value></allowedValues><default>Dev</default></rangeParameter></parameters>
All parameters must be defined within a parameters block within the project. As a minimum definition, each parameter must have a name – which is what is sent to the client.
This definition defines a parameter called “Type”, it is a range parameter with three possible values: “Dev”, “Test” and “Prod”. Finally, the default value is “Dev”. When a force build is triggered, the user will be displayed a combo box (or a drop down list depending on the client) with these three values in it, and “Dev” will be the initial selection.
The next step is to use the parameter in a task (currently they are limited to tasks and publishers, this may change if there is enough support for it.) The following shows how I would modify the <nant> task to use the parameter:
<nant><!-- Omitted to save space --><dynamicValues><directValue parameter="Type" property="targetList.target[0]"><default>Dev</default></directValue></dynamicValues></nant>
This simply says replace the first target in the targetList with the parameter value. This is a direct replacement – the entire value will be replaced with whatever value was selected for Type.
Note that there is also a default value here. This is used for when a value has not been selected (i.e. when the interval trigger detects changes).
This is a very simple example of a pre-defined value that entirely replaces the existing value. Now, let’s see some of the other parameter types.
A Numeric Parameter – Gendarme Limit
The next thing I’ll show is how to use a numeric parameter value. Again, this needs to be defined at the project level in the parameters block:
<parameters><!-- Omitted to save space --><numericParameter name="GendarmeLimit"><description>The limit of Gendarme errors.</description><minimum>50</minimum><maximum>500</maximum><default>100</default></numericParameter></parameters>
This says we want to also have a GendarmeLimit parameter, this is a numeric value and must be between 50 and 500. The default value will be 100.
To use this, it is the same as the target type, just add a direct replacement dynamic value:
<gendarme><!-- Omitted to save space --><dynamicValues><directValue parameter="GendarmeLimit" property="limit"><default>100</default></directValue></dynamicValues></gendarme>
Note, the value will automatically be converted into the correct data type for use in the task. If there is a data type mismatch (e.g. a test value is put into a numeric property), then the build will fail with an error.
Now, let’s add one more parameter – one that doesn’t use direct replacement.
A Build Reason
This is a bit of a contrived example, but here we are going to pass a reason for the build into the NAnt task.
First, let’s define the parameter:
<parameters><!-- Omitted to save space --><textParameter name="Reason"><description>Reason for the build being forced.</description><minimum>10</minimum><maximum>255</maximum><required>true</required></textParameter></parameters>
This is a text parameter with a minimum length of 10 and a maximum length of 255.
This time, to use the parameter we are going to use a replacementValue:
<nant><!-- Omitted to save space --><dynamicValues><!-- Omitted to save space --><replacementValue property="buildArgs"><format>-D:"{0}"</format><parameters><namedValue name="Reason" value="Triggered" /></parameters></replacementValue></dynamicValues></nant>
This has the same property attribute, but otherwise is very different. A standard .NET format string is required (internally this is literally passed onto string.Format()!) And there is also a list of parameters to use. As you might have guessed, this dynamic value can use multiple parameters. If a property needs to use more than one parameter, then this is the syntax that is required.
Note for the parameters a namedValue is used. The name is the name of the parameter, while the value is the default value. This is needed because there can be multiple parameters, each with its own default value!
Scheduling A Prod Build
The final item I’ll cover is how to use parameters together with triggers. In this case, I’m going to schedule a build every night that uses the Prod target.
This is fairly easy to do, all we need to do is add a new parameterTrigger that contains the schedule trigger. The following shows how to do this:
<triggers><intervalTrigger /><parameterTrigger><trigger type="scheduleTrigger"><time>1:00</time><buildCondition>ForceBuild</buildCondition></trigger><parameters><namedValue name="Type" value="Prod"/><namedValue name="Reason" value="Scheduled"/><namedValue name="GendarmeLimit" value="500"/></parameters></parameterTrigger></triggers>
This will schedule a build every night at 1am and pass in “Prod” for the Type, “Scheduled” for the Reason and 500 for the GendarmeLimit.
The Final Configuration
Hopefully this has provided a quick introduction to how parameters and dynamic values can be used. If you can think of how this can be improved, let me know and I’ll see what I can do.
And to finish up, here is the complete configuration:
<cruisecontrol><project name="Test Project"><description>A demonstration project to show the features of the dynamic build parameters.</description><sourcecontrol type="svn"><trunkUrl>svn://svn.mycompany.com/firstproject/trunk</trunkUrl><workingDirectory>C:\SourceControl\FirstProject\</workingDirectory></sourcecontrol><triggers><intervalTrigger /><parameterTrigger><trigger type="scheduleTrigger"><time>1:00</time><buildCondition>ForceBuild</buildCondition></trigger><parameters><namedValue name="Type" value="Prod"/><namedValue name="Reason" value="Scheduled"/><namedValue name="GendarmeLimit" value="500"/></parameters></parameterTrigger></triggers><tasks><nant><buildFile>App.build</buildFile><targetList><target>Dev</target></targetList><buildArgs>-D:reason=testing</buildArgs><dynamicValues><directValue parameter="Type" property="targetList.target[0]"><default>Dev</default></directValue><replacementValue property="buildArgs"><format>-D:"{0}"</format><parameters><namedValue name="Reason" value="Triggered" /></parameters></replacementValue></dynamicValues></nant><gendarme><assemblies><assemblyMatch expr="*.dll"/></assemblies><limit>100</limit><dynamicValues><directValue parameter="GendarmeLimit" property="limit"><default>100</default></directValue></dynamicValues></gendarme></tasks><publishers><rss /><xmllogger /></publishers><parameters><rangeParameter name="Type"><description>The type of build to perform.</description><allowedValues><value>Dev</value><value>Test</value><value>Prod</value></allowedValues><default>Dev</default></rangeParameter><numericParameter name="GendarmeLimit"><description>The limit of Gendarme errors.</description><minimum>50</minimum><maximum>500</maximum><default>100</default></numericParameter><textParameter name="Reason"><description>Reason for the build being forced.</description><minimum>10</minimum><maximum>255</maximum><required>true</required></textParameter></parameters></project></cruisecontrol>
RSS - Posts
Wim Hollebrandse said
May have spoken a bit too soon. Came across the following article:
So that presumably means you can use dynamic parameters without having to define a list in your appropriate build section of your project definition.
So I could simply use the builArgs element for the msbuild task as such:
/p:Configuration=$[BuildMode|Debug] /p:Environment=[Environment|Staging]
I hope I understood that correctly.
Also, is there a way that these dynamic params can be specified when forcing a build from the web app or the CCtray? Or is it only available through the client API at the moment?
Apologies for screaming too soon!
Thanks,
Wim
Wim Hollebrandse said
Forgot article link:
http://confluence.public.thoughtworks.org/display/CCNET/Dynamic+Parameters
Wim Hollebrandse said
Never mind, missed out the parameter section, the popup now appears when forcing a build.
Great stuff!
Craig Sutherland said
Hi Wim,
Glad I’ve been able to help
To answer your original question – yes it is a convoluted way of doing things. Unfortunately it was the only way to do it using CruiseControl.NET – because under the hood we load the definitions into some POCO instances using NetReflector. Later on I modified NetReflector to allow the substitution method, but under the hood it just converts the XML into the “convoluted” definition.
One day we will have to look into this a bit better, but for the moment it works
Craig
Wim Hollebrandse said
Hi,
I’m toying around with the CTP version (1.5).
Isn’t this and overly convoluted way of doing this? We’re using MSBuild and have defined our own properties like ‘Environment’. Based on that, the appropriate MSBuild tasks will deal with this accordingly and generate output for the specific environments.
Ultimately, all I want to be able to do is pass an additional bunch of build args (/p:Environment:Staging and so on..) to a Build Project definition.
It looks like the current way of doing it only lets you substitute the XML element’s inner Text. And it seems a violation of DRY too, as you’d need to define the list of values in the ccnet.config file, but clearly we’ve already got it defined it our MSBuild files. Why the need for a list of defined values?
Am I missing a simple trick to be able to pass custom (but dynamic!)properties to the buildArgs of a defined project?
Thanks,
Wim