Automated Coder

Exploring the Code of CruiseControl.Net

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>

5 Responses to “Dynamic Build Parameters – Some Examples”

  1. 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

  2. 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

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>