Gotcha: XML include processing in CruiseControl.NET

Release 1.4 of CruiseControl.NET includes a new Configuration Preprocessor. The great thing is that this provides a C preprocessor-like macro syntax, so projects can be made using relatively powerful templates.

The bad thing is that it’s new, and so there are some gotcha’s laying around. I just found one:

I defined a ccnet.config file structured like this:


<cruisecontrol
  xmlns:cb="urn:ccnet.config.builder"
  >
  <!-- Define some local configuration symbols -->
  <cb:define
    FOO="bar"
    />
  <!-- Define a CCnet project template called "cb:PROJECT" -->
  <cb:define
    name="PROJECT"
    >
    <project
      name = "$(projectName)"
      >
      <!--
          guts go here
          -->
    </project>
  </cb>
  <!-- Use template -->
  <cb:PROJECT
    projectName = "My dog has fleas"
    />
</cruisecontrol>

That worked fine, and I could see how to rubber-stamp that project template to all my various continuous-integration areas and other workspaces.

But then I got clever, and decided to separate out the template from the local configuration.


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

  <!-- Define some local configuration symbols -->
  <cb:define
    FOO="bar"
    />

  <!-- Include template(s) file -->
  <cb:include
    href = "project-template.xml"
    />

  <!-- Use template -->
  <cb:PROJECT  projectName = "My dog has fleas"  />
</cruisecontrol>

By unfortunate coincidence, I had to reboot my laptop at this point — I kicked the power strip under the desk, removing power to the docking station. The Lenovo I’m using (client provided) doesn’t handle this at all, so it was restart time. When I came back up, the CruiseControl.NET service wouldn’t start.

Debugging is simple and easy with the “ccnet.exe” command line utility. Just run

ccnet.exe -config:ccnet.config -validate

The error, though, was incomprehensible:

C:\Program Files\CruiseControl.NET\server>ccnet -config:ccnet.config -validate
CruiseControl.NET Server 1.4.0.3400 -- .NET Continuous Integration Server
Copyright c 2008 ThoughtWorks Inc.  All Rights Reserved.
.NET Runtime Version: 2.0.50727.1433    Image Runtime Version: v2.0.50727
OS Version: Microsoft Windows NT 5.1.2600 Service Pack 3        Server locale: e
n-US

[CCNet Server:DEBUG] The trace level is currently set to debug.  This will cause
 CCNet to log at the most verbose level, which is useful for setting up or debug
ging the server.  Once your server is running smoothly, we recommend changing th
is setting in C:\Program Files\CruiseControl.NET\server\ccnet.exe.config to a lo
wer level.
[CCNet Server:INFO] Reading configuration file "C:\Program Files\CruiseControl.NET\server\ccnet.config"
[CCNet Server:ERROR] Exception: The configuration file contains invalid xml: C:\Program Files\CruiseControl.NET\server\ccnet.config
----------
ThoughtWorks.CruiseControl.Core.Config.ConfigurationException: The configuration file contains invalid xml: C:\Program Files\CruiseControl.NET\server\ccnet.config ---> System.Xml.XmlException: 'cb' is an undeclared namespace. Line 1, position 2.
   at System.Xml.XmlTextReaderImpl.Throw(Exception e)
   at System.Xml.XmlTextReaderImpl.Throw(String res, String arg, Int32 lineNo, Int32 linePos)
   at System.Xml.XmlTextReaderImpl.LookupNamespace(NodeData node)
   at System.Xml.XmlTextReaderImpl.ElementNamespaceLookup()
   at System.Xml.XmlTextReaderImpl.ParseAttributes()
   at System.Xml.XmlTextReaderImpl.ParseElement()
   at System.Xml.XmlTextReaderImpl.ParseDocumentContent()
   at System.Xml.XmlTextReaderImpl.Read()
   at System.Xml.XPath.XPathDocument.LoadFromReader(XmlReader reader, XmlSpace space)
   at System.Xml.XPath.XPathDocument..ctor(Stream stream)
   at ThoughtWorks.CruiseControl.Core.Config.Preprocessor.ConfigPreprocessorEnvironment.push_include(String href)
   at ThoughtWorks.CruiseControl.Core.Config.Preprocessor.ConfigPreprocessor.PreProcess(XmlReader input, XmlWriter output, PreprocessorUrlResolver resolver, Uri input_uri)
   at ThoughtWorks.CruiseControl.Core.Config.DefaultConfigurationFileLoader.CreateXmlValidatingLoader(FileInfo configFile)
   at ThoughtWorks.CruiseControl.Core.Config.DefaultConfigurationFileLoader.AttemptLoadConfiguration(FileInfo configFile)
   --- End of inner exception stack trace ---
   at ThoughtWorks.CruiseControl.Core.Config.DefaultConfigurationFileLoader.AttemptLoadConfiguration(FileInfo configFile)
   at ThoughtWorks.CruiseControl.Core.Config.DefaultConfigurationFileLoader.Load(FileInfo configFile)
   at ThoughtWorks.CruiseControl.Core.Config.FileConfigurationService.Load()
   at ThoughtWorks.CruiseControl.Core.Config.FileWatcherConfigurationService.Load()
   at ThoughtWorks.CruiseControl.Core.Config.CachingConfigurationService.Load()
   at ThoughtWorks.CruiseControl.Core.CruiseServer..ctor(IConfigurationService configurationService, ProjectIntegratorListFactory projectIntegratorListFactory, IProjectSerializer projectSerializer)
   at ThoughtWorks.CruiseControl.Core.CruiseServerFactory.Create(Boolean remote, String configFile)
   at ThoughtWorks.CruiseControl.Core.ConsoleRunner.Run()
   at ThoughtWorks.CruiseControl.Console.ConsoleMain.Main(String[] args)
----------

Even worse, the error message is presented in white text on a glaring red background, so as to be all but unreadable. Looking at it now, in retrospect (and black & white), I can see clues that I missed the first time. Specifically, the string “push_include.”

Anyway, the problem turned out to be the way the Configuration Processor deals with XML includes. It is not a C preprocessor — it includes subtrees, not text.

The answer turns out to be in the included file. References in the included file to the “cb:” XML namespace have to be accompanied in that file by some definition. Adding the same xmlns= statement does the trick:


<?xml version="1.0" encoding="UTF-8"?>
<cb:define  xmlns:cb="urn:ccnet.config.builder"  name = "PROJECT"  >
  <!-- blah blah -->
</cb:define>

Problem solved. Now let the rubber-stamping begin!

Leave a Reply