Configuration Pre-processor

The CCNet configuration preprocessor acts on the ccnet.config file. Preprocessor directives are specified in the XML namespace "urn:ccnet.config.builder" to distinguish them from ordinary configuration markup. Any markup not in the preprocessor's namespace are passed through unchanged.

Preparing to Use the Preprocessor

The configuration preprocessor is invoked by declaring an XML Namespace of "urn:ccnet.config.builder" on the root configuration element ("<cruisecontrol>"). The namespace abbreviation you choose must be specified on any preprocessor directives you use. For the rest of this explanation, we will specify it as "cb", a mnemonic for "configuration builder".

1<cruisecontrol xmlns:cb="urn:ccnet.config.builder">

Preprocessor Elements

The configuration preprocessor has several elements that control its processing of your configuration.
  • The <define> element is used to define constants to be expanded later.
  • The <include> element is used to include the contents of another file.
  • The <scope> element is used to encapsulate sections that change the value of an existing constant.

Defining Preprocessor Constants

The <define> element is used to define a preprocessor constant. It can be used in several ways:

Text constants (values are strings):

Define a constant named "foo" with a value of "bar":

1<cb:define foo="bar"/>

You can define more than one constant in the same define element:
1<cb:define a="1" b="2" c="3"/>

Nodeset constants (values are XML fragments):

Define a constant named "baz" with a value of an xml fragment:

1<cb:define name="baz">
2  <some_element>
3    <some_inner_element/>
4  </some_element>
5</cb:define>

Any valid XML inside the define element is considered to be the constant's value. This includes elements, attributes, text nodes, and comments.

Expanding Preprocessor Constant Values

Once defined, preprocessor constants can be expanded in two ways: as text references or as XML references.

Text References

References of the form "$(const_name)" which are found in attribute values or text nodes will be expanded such that the text value replaces the reference. If no constant exists with the given name and there exists a windows environment variable with the same name, that environment variable's value will be used.

Examples

Use in an attribute:

1<cb:define foo="bar"/>
2<somexml attr1="$(foo)"/>

expands to:
1<somexml attr1="bar"/>

Use as text of an element:
1<cb:define foo="bar"/>
2<somexml>$(foo)</somexml>

expands to:
1<somexml>bar</somexml>

Use of Windows environment variables:
1<env dir="$(PATH)"/>

expands to:
1<env dir="... your PATH environment value ..."/>

XML References

When the preprocessor encounters an element in the preprocessor namespace, and the element name is not one of the predefined keywords (define,scope,config-template, etc), the element will be replaced by the constant value associated with the element name.

Examples

Use as text of an element:

1<cb:define foo="bar"/>
2<sample>
3  <cb:foo/>
4</sample>

expands to:
1<sample>
2  bar
3</sample>

Uses as sub-element:
1<cb:define name="baz">
2  <some_element>
3    <some_inner_element/>
4  </some_element>
5</cb:define>
6<sample>
7  <cb:baz/>
8</sample>

expands to:
1<sample>
2  <some_element>
3    <some_inner_element/>
4  </some_element>
5</sample>

If a constant reference refers to a constant which has not been defined and which does not exist as an OS environment variable, an error will occur.

Nested Expansions and Parameters

Constant references can be nested, i.e., the value of constant "zed" can contain a reference to constant "alpha".

1<cb:define alpha="alphaval"/>
2<cb:define zed="zedval/$(alpha)"/>
3
4<z>$(zed)</z>

expands to:
1<z>zedval/alphaval</z>

In addition, using the cb:varname call syntax outlined above, constant values can be passed as part of the call element. Consider the following definition, in which neither "gamma" nor "delta" have yet been defined:
1<cb:define name="beta">
2  <hello>
3    <cb:gamma/>
4    <hi attr1="$(delta)"/>
5  </hello>
6</cb:define>

Since gamma and delta have not been defined, they must be passed in with the reference to beta. This is done as follows:
1<cb:beta delta="deltaval">
2  <cb:define name="gamma">
3    <gamma_element>hi</gamma_element>
4  </cb:define>
5</cb:beta>

This will expand to:
1<hello>
2  <gamma_element>hi</gamma_element>
3  <hi attr1="deltaval" />
4</hello>

Scopes

The <scope> element can be used to control the scope of a preprocessor definition. You may not define the same constant twice within the same scope, however you may introduce a nested scope which redefines a particular value in an outer scope. A scope is semantically equivalent to a stack frame in traditional programming terms.

1<cb:scope a="a_val" b="b_val">
2  <test attr="$(a)" attr2="$(b)"/>
3  <cb:scope a="a_val_redefined">
4    <test attr="$(a)" attr2="$(b)"/>
5  </cb:scope>
6</cb:scope>

expands to:
1<test attr="a_val" att2="b_val"/>
2<test attr="a_val_redefined" att2="b_val"/>

The scope can be used to set the projectname in a variable, making copy and paste of projects less error prone.
Changing a certain folder is also more easy / safe.
 1<cruisecontrol xmlns:cb="urn:ccnet.config.builder">
 2  <cb:define WorkingMainDir="C:\Integration\"/>
 3  <cb:define WorkingDir="\WorkingDirectory"/>
 4  <cb:define ArtifactsDir="\Artifacts"/>
 5
 6  <cb:scope ProjectName="Alpha">
 7    <project name="$(ProjectName)" queue="Q1" queuePriority="1">
 8      <workingDirectory>$(WorkingMainDir)$(ProjectName)$(WorkingDir)</workingDirectory>
 9      <artifactDirectory>$(WorkingMainDir)$(ProjectName)$(ArtifactsDir)</artifactDirectory>
10
11    </project>
12  </cb:scope>
13
14  <cb:scope ProjectName="Beta">
15    <project name="$(ProjectName)" queue="Q1" queuePriority="1">
16      <workingDirectory>$(WorkingMainDir)$(ProjectName)$(WorkingDir)</workingDirectory>
17      <artifactDirectory>$(WorkingMainDir)$(ProjectName)$(ArtifactsDir)</artifactDirectory>
18
19    </project>
20  </cb:scope>
21
22</cruisecontrol>

expands to :
 1<cruisecontrol>
 2
 3    <project name="Alpha" queue="Q1" queuePriority="1">
 4      <workingDirectory>C:\Integration\Alpha\WorkingDirectory</workingDirectory>
 5      <artifactDirectory>C:\Integration\Alpha\Artifacts</artifactDirectory>
 6
 7    </project>
 8
 9    <project name="Beta" queue="Q1" queuePriority="1">
10      <workingDirectory>C:\Integration\Beta\WorkingDirectory</workingDirectory>
11      <artifactDirectory>C:\Integration\Beta\Artifacts</artifactDirectory>
12
13    </project>
14
15</cruisecontrol>

Or an even more advanced one (thanks to Arieh Schneier) :
 1<cruisecontrol xmlns:cb="urn:ccnet.config.builder">
 2 <cb:define WorkingMainDir="C:\Integration\"/>
 3 <cb:define WorkingDir="\WorkingDirectory"/>
 4 <cb:define ArtifactsDir="\Artifacts"/>
 5
 6 <cb:define name="OurProject">
 7   <project name="$(ProjectName)" queue="Q1" queuePriority="1">
 8     <workingDirectory>$(WorkingMainDir)$(ProjectName)$(WorkingDir)</workingDirectory>
 9     <artifactDirectory>$(WorkingMainDir)$(ProjectName)$(ArtifactsDir)</artifactDirectory>
10   </project>
11 </cb:define>
12
13 <cb:scope ProjectName="Alpha">
14   <cb:OurProject/>
15 </cb:scope>
16
17 <cb:scope ProjectName="Beta">
18   <cb:OurProject/>
19 </cb:scope>
20</cruisecontrol>

Comments

XML Comments whose first character is '#' will not be copied to the output. All other comments will.

1<!-- This comment will appear in the output file-->
2<!--# This comment will not -->

Including files

The <include> element is used to include the contents of another file. The element is replaced with the contents of that file, which must be a valid XML document. The file is specified as a URL, relative to the file that contains the <include> element.

Assuming that the CCNet configuration file "ccnet.config" includes the file "projects\project.config":

1... lines before ...
2<cb:include href="projects/project.config"/>
3... lines after ...

and that the "projects\project.config" file includes a file called "EmailConfig.xml" from the same directory as "ccnet.config":
1<project>
2  ... project definition ...
3  <publishers>
4    <cb:include href="../EMailConfig.xml"/>
5    ... more publishers ...
6  </publishers>
7</project>

and that the "EMailConfig.xml" file contains an [Email Publisher] definition:
1<email>
2  ... email configuration info ...
3</email>

the results would be:
 1... lines before ...
 2<project>
 3  ... project definition ...
 4  <publishers>
 5    <email>
 6      ... email configuration info ...
 7    </email>
 8    ... more publishers ...
 9  </publishers>
10</project>
11... lines after ...

Including files with multiple elements

A main ccnet.config file can easily contain dozens of project definitions, and to reduce the file size and so keeping the overview, it may be easy to group certain projects in separate files.
  • the main ccnet.config file grouping all includes, and small projects
  • a definition file, containing the global definitions
  • a CI_Projects file, containing all projects for the CI process
  • a DeployToQA file, containing all projects that will go to the QA environment,
  • ....

Setting this up :

The main ccnet.config file
 1<cruisecontrol xmlns:cb="urn:ccnet.config.builder">
 2
 3  <cb:include href="Definitions.xml" xmlns:cb="urn:ccnet.config.builder"/>
 4
 5  <cb:include href="CI_Projects.xml" xmlns:cb="urn:ccnet.config.builder"/>
 6
 7  <cb:include href="QA_Projects.xml" xmlns:cb="urn:ccnet.config.builder"/>
 8
 9</cruisecontrol>

The Definitions.xml file
 1<cb:config-template xmlns:cb="urn:ccnet.config.builder">
 2
 3  <queue name="Q1" duplicates="UseFirst" lockqueues="Q2, Q4" />
 4
 5  <cb:define name="EmailPublisher">
 6    <email from="buildmaster@mycompany.com" 
 7          mailhost="localhost" 
 8          mailhostUsername="TheMailer" 
 9          mailhostPassword="JohnWayne" 
10          includeDetails="TRUE">
11    <users />
12        <groups />
13
14        <modifierNotificationTypes>
15          <NotificationType>Failed</NotificationType>
16          <NotificationType>Fixed</NotificationType>
17        </modifierNotificationTypes>
18
19    </email>
20  </cb:define>
21
22  <cb:define name="common_publishers">
23    <artifactcleanup cleanUpMethod="KeepMaximumXHistoryDataEntries" cleanUpValue="500" />
24    <xmllogger />
25    <statistics />
26    <modificationHistory  onlyLogWhenChangesFound="true" />
27    <rss/>
28  </cb:define>
29
30  <cb:define name="common_nant">
31       <executable>c:\nant\nant.exe</executable>
32       <nologo>true</nologo>
33    <buildTimeoutSeconds>240</buildTimeoutSeconds>
34  </cb:define>
35
36  <cb:define name="nant_args_CI">
37      <buildArgs>clean compile</buildArgs>
38  </cb:define>
39
40</cb:config-template>

The CI_Projects.xml file
 1<cb:config-template xmlns:cb="urn:ccnet.config.builder">
 2
 3  <project name="Preprocesser2" queue="Q1" queuePriority="1">
 4      <workingDirectory>C:\Integration\Preprocesser2\WorkingDirectory</workingDirectory>
 5      <artifactDirectory>C:\Integration\Preprocesser2\Artifacts</artifactDirectory>
 6
 7      <triggers>
 8        <intervalTrigger name="continuous" seconds="60" buildCondition="IfModificationExists" />
 9      </triggers>
10
11      <sourcecontrol type="nullSourceControl" />
12
13      <tasks>
14         <cb:nant_local/>
15      </tasks>
16
17      <publishers>
18         <cb:common_publishers/>
19      </publishers>
20  </project>
21
22  <project name="FtpTransfer2" queue="Q1" queuePriority="102">
23     <workingDirectory>C:\Integration\FtpTransfer2\WorkingDirectory</workingDirectory>
24     <artifactDirectory>C:\Integration\FtpTransfer2\Artifacts</artifactDirectory>
25
26     <triggers>
27    <intervalTrigger name="continuous" seconds="60" buildCondition="IfModificationExists" />
28     </triggers>
29
30     <sourcecontrol type="nullSourceControl" />
31
32     <tasks>
33    <exec>
34       <executable>c:\tools\consolenok</executable>
35       <buildArgs>all</buildArgs>
36       <buildTimeoutSeconds>10</buildTimeoutSeconds>
37       </exec>
38
39    </tasks>
40
41    <publishers>
42       <cb:common_publishers/>
43    </publishers>
44  </project>
45
46</cb:config-template>

The same goes for the other included files, things to remember :
  • define a <cb:config-template xmlns:cb="urn:ccnet.config.builder"> root element in every included file
  • first include all definitions, and later the files containing the projects using them {note:title=Version 1.6 and higher only from now on}
    The configuration options below are for CCNet 1.6 and higher only. {note}

Conditional statements :

IFDEF / IFNDEF directives

The <cb:ifdef> / <cb:ifndef> constructs are used during evaluation to conditionally include processing of their contained elements based on the existence/absence of a defined name.

 1<cb:define some_symbol_name="some_symbol_value" />
 2
 3  <cb:ifdef name="some_symbol_name">
 4    <output>Value of some_symbol_name is $(some_symbol_name)</output>
 5  </cb:ifdef>
 6  <cb:else>
 7    <output>This will not appear because the symbol is defined</output>
 8  </cb:else>
 9
10  <cb:ifndef name="some_symbol_name">
11    <output>This will not appear in the output since some_symbol_name is defined</output>
12  </cb:ifndef>
13  <cb:else>
14    <output>This will appear because it is in the (optional) else clause</output>
15  </cb:else>

IF / ELSE directives

The <cb:if> / <cb:else> constructs evaluate a JSCRIPT.NET boolean expression, and perform branching depending on the value of the expression.

 1<cb:define testval="gibberish" testval2="40"/>
 2
 3  <cb:if expr="'$(testval)'=='gobbeldygook'">
 4    This will not appear, since the expression evaluates to false
 5  </cb:if>
 6  <cb:else>
 7    This will appear in the output.
 8  </cb:else>
 9
10  <cb:if expr="$(testval2) &lt; 50">
11    This will appear, since testval2 is less than 50
12  </cb:if>
13  <cb:else>
14    This will not appear in the output.
15  </cb:else>

EVAL directive

Evaluates the given JSCRIPT.NET expression (of any type). The pp:eval tag is replaced with the result of the evaluation.

 1<!-- outputs 54-->
 2  <cb:eval expr="9*6"/>
 3
 4  <!-- outputs 6. When referencing preprocessor variables in an expression, the value of
 5  the variables is substituted into the expression before it is submitted for evaluation.
 6  Therefore, the below expression is submitted to the JSCRIPT.NET runtime as "18/3" 
 7  -->
 8  <cb:define a="3" b="18"/>
 9  <cb:eval expr="$(b)/$(a)"/>
10
11  <!-- outputs the current date/time -->
12  <cb:eval expr="new Date()"/>
13
14  <!--
15   Since the preprocessor replaces variable references with their values before submitting the expression
16   to the JSCRIPT.NET engine, it is necessary take special steps to treat variables as strings. In the
17   example below, the preprocessor will expand the value of $(str) before submitting the expression, therefore
18   it is necessary to do two things:
19    1. Enclose the variable reference in single quotes, making it a string literal in the JSCRIPT.NET language.
20       You need to do this yourself, as in the example below.
21    2. Escape any funny characters (such as embedded single quotes) so that the string literal is properly-formed.
22       You achieve this by adding a :js (for JSCRIPT) format specifier after the variable name. This causes
23       the preprocessor to do the proper escaping for you.
24
25   This example outputs the value of str, converted to upper case. Literally, the JSCRIPT.NET expression
26   which is evaluated is:
27        'a string with \'things\' which may need to be "escaped"'.ToUpper()
28
29    Attributes:
30    expr: A JSCRIPT.NET expression
31  -->
32
33  <cb:define str="a string with 'things' which may need to be &quot;escaped&quot;"/>
34
35  <cb:eval expr="'$(str:js)'.ToUpper()"/>

FOR loop

Loops over a range of values and emits the loop body on each iteration.

Attributes:

counter-name: The name for the counter variable. Within the body of the loop, this variable can be referenced just like any other preprocessor variable.
init-expr: JSCRIPT.NET expression whose value will be assigned to the counter variable before the first iteration.
test-expr: JSCRIPT.NET boolean expression which will determine whether the loop should terminate. {note}
Note in the example below that it is necessary to refer to the counter variable as $(i) rather than just i. {note}
count-expr: JSCRIPT.NET expression that is evaluated after every loop iteration. The value is assigned to the counter variable. In a typical counting loop, this would increment the counter by 1. {note}
Note that in the example below, it is necessary to refer to the counter variable as $(i) rather than just i. Note also that the value of count-expr is only the RIGHT HAND SIDE of
of the assignment (i.e. it does not say $(i) =(i+1)) {note}
Scope:An implicit scope is introduced in the body of the loop, therefore any local definitions will only exist within the loop body.

This example will emit:
<hello attr="0"/>
<hello attr="1"/>
<hello attr="2"/>

1<cb:for counter-name="i" init-expr="0" test-expr="$(i)&lt;3" count-expr="$(i)+1">
2    <hello attr="$(i)"/>
3  </cb:for>

ELEMENT and ATTRIBUTE directives

These emit XML elements, with (optional) attributes. This is just like the XSLT xsl:element/xsl:attribute mechanism. The only reason to use it rather than literal XML markup is if you wish for your element or attribute NAMES to be calculated.
Both <cb:element> and <cb:attribute> have two attributes:
name: Name of emitted element/attribute
namespace (optional): XML namespace of emitted element/attribute

This example will emit:
<myel myattr1="myattrval1" myattr2="myattrval2">myelval</myel>

 1<cb:define elname="myel" elval="myelval" attrname="myattr" attrval="myattrval"/>
 2
 3  <cb:element name="$(elname)">
 4
 5    <cb:attribute name="$(attrname)1">$(attrval)1</pp:attribute>
 6
 7    <cb:attribute name="$(attrname)2">$(attrval)2</pp:attribute>
 8
 9    $(elval)
10
11  </cb:element>

Common Problems

XmlException: "There are multiple root elements" when including another file

The <include> directive requires the included file to be a valid XML document. All XML documents must have exactly one "root element" - i.e., there must be an outermost pair of start/end XML tags. The XML file being included in this case has more than one of these root elements (e.g., <project name="A">...</project> <project name="B">...</project>). The file must either be split apart so that each root element is in a separate file, or the group of root elements must be enclosed in a single outer element.

Preprocessor directives don't work in an included file

XML requires that all "namespace" definitions (i.e., xmlns:{*}{_}xxx{_}* attributes) must be declared in every file where they're used. Every included file must have an xmlns:cb="urn:ccnet.config.builder" attribute on its root element, even if the namespace was defined in the file that included it.