Paul Julius > Blog

Refactoring CruiseControl Configuration Files (From Agile 2009 Demo)

September 1st, 2009

Building a stable CI system requires some crafty architecture, just like building a functional central business district.

Building a stable CI system requires some crafty architecture, just like building a functional central business district.

At Agile 2009, Tom Sulston refereed a “cage match” with all the Continuous Integration tool vendors available. That included me representing CruiseControl. Each vendor had 10 minutes to share a bit about their tool and take questions from the audience. I got to go first!

I decided to focus on a couple characteristics that distinguish CruiseControl. The things I chose were project pre-configuration and a usage pattern for supporting multiple projects. Here’s a textual version of the demonstration that I gave at Agile 2009.

Let’s start with the sample CruiseControl configuration that comes with the binary distribution when you download it.


<cruisecontrol>
    <project name="connectfour">

        <listeners>
            <currentbuildstatuslistener file="logs/${project.name}/status.txt"/>
        </listeners>

        <bootstrappers>
            <antbootstrapper anthome="apache-ant-1.7.0" 
                     buildfile="projects/${project.name}/build.xml" target="clean" />
        </bootstrappers>

        <modificationset quietperiod="30">
            <!-- touch any file in connectfour project to trigger a build -->
            <filesystem folder="projects/${project.name}"/>
        </modificationset>

        <schedule interval="300">
            <ant anthome="apache-ant-1.7.0" 
                buildfile="projects/${project.name}/build.xml"/>
        </schedule>

        <log>
            <merge dir="projects/${project.name}/target/test-results"/>
        </log>

        <publishers>
            <onsuccess>
                <artifactspublisher dest="artifacts/${project.name}" 
                       file="projects/${project.name}/target/${project.name}.jar"/>
            </onsuccess>
        </publishers>

    </project>
</cruisecontrol>

That works great to have a little sample project running out of the box. Now, I want to add another project. In this case, I am going to use HttpUnit. I checked it out from subversion in my projects directory. After that, I need to configure CruiseControl to run the build for it. I’ll start by copying and pasting the project configuration used for the connectfour project. I only need to change one thing: the location of the final jar file. Instead of the jar being built into the target directory, it goes into the lib directory. So now, our configuration file contains the following after the end of the connectfour project definition.


    ...
    <project name="httpunit">
    
            <listeners>
                <currentbuildstatuslistener 
                    file="logs/${project.name}/status.txt"/>
            </listeners>
    
            <bootstrappers>
                <antbootstrapper anthome="apache-ant-1.7.0" 
                    buildfile="projects/${project.name}/build.xml" 
                    target="clean" />
            </bootstrappers>
    
            <modificationset quietperiod="30">
                <filesystem folder="projects/${project.name}"/>
            </modificationset>
    
            <schedule interval="300">
                <ant anthome="apache-ant-1.7.0" 
                    buildfile="projects/${project.name}/build.xml" />
            </schedule>
    
            <log>
                <merge dir="projects/${project.name}/target/test-results"/>
            </log>
    
            <publishers>
                <onsuccess>
                    <artifactspublisher dest="artifacts/${project.name}" 
                        file="projects/${project.name}/lib/${project.name}.jar"/>
                </onsuccess>
            </publishers>    
    </project>
</cruisecontrol>

That will work. Both the connectfour and httpunit projects will build in CruiseControl. Looking at the full configuration file, though, makes me feel a little ill. The file contains a ton of duplication. As noted, the only thing that differs between the two is the location of the project distributable, i.e. the jar file. Let’s refactor that a bit to pull out some of the duplication.

For CruiseControl, we can take advantage of the plugin preconfiguration features. Projects are plugins. So, we can extract all the common elements to a project “template” that all our projects use.

Within the individual projects we can override the elements that are unique to that project. Sort of like creating a base class in object oriented programming. Subclasses extend the base class and override the methods for which they need to define explicit behavior.

Here’s the full configuration file after the refactoring:


<cruisecontrol>
    <plugin name="project">
        <bootstrappers>
            <antbootstrapper anthome="apache-ant-1.7.0" 
                buildfile="projects/${project.name}/build.xml" 
                target="clean" />
        </bootstrappers>

        <modificationset quietperiod="30">
            <filesystem folder="projects/${project.name}"/>
        </modificationset>

        <schedule interval="300">
            <ant anthome="apache-ant-1.7.0" 
                buildfile="projects/${project.name}/build.xml"/>
        </schedule>

        <log>
            <merge dir="projects/${project.name}/target/test-results"/>
        </log>
        
        <!-- Publishers are up to the individual projects for now. -->
        
    </plugin>
    
    <project name="connectfour">
        <publishers>
            <onsuccess>
                <artifactspublisher dest="artifacts/${project.name}" 
                    file="projects/${project.name}/target/${project.name}.jar"/>
            </onsuccess>
        </publishers>
    </project>
    
    <project name="httpunit">
        <publishers>
            <onsuccess>
                <artifactspublisher dest="artifacts/${project.name}" 
                    file="projects/${project.name}/lib/${project.name}.jar"/>
            </onsuccess>
        </publishers>    
    </project>
    
</cruisecontrol>

That’s a lot better. Now the projects share a common template. They each define their own publishers, which allows them to override the location of the jar file.

One more step. The project definition could be extracted from this central configuration file and included with the files that the project owns. I use the include.projects plugin for CruiseControl.

I like to create a file right next to my build file, e.g. build.xml in the case of Ant. Then the project, or component if you prefer, team can take ownership for their project definition. As a CruiseControl administrator, all I have to do is point to their configuration to include their build in a particular CruiseControl instance. Here’s what my main configuration file looks like after that change:


<cruisecontrol>
    <plugin name="project">
        <bootstrappers>
            <antbootstrapper anthome="apache-ant-1.7.0" 
                buildfile="projects/${project.name}/build.xml" 
                target="clean" />
        </bootstrappers>

        <modificationset quietperiod="30">
            <filesystem folder="projects/${project.name}"/>
        </modificationset>

        <schedule interval="300">
            <ant anthome="apache-ant-1.7.0" 
                buildfile="projects/${project.name}/build.xml"/>
        </schedule>

        <log>
            <merge dir="projects/${project.name}/target/test-results"/>
        </log>
        
        <!-- Publishers are up to the individual projects for now. -->
        
    </plugin>
    
    <include.projects file="projects/connectfour/cruisecontrol.config.xml" />
    
    <include.projects file="projects/httpunit/cruisecontrol.config.xml" />
    
</cruisecontrol>

Then, the project team gets to own the cruisecontrol.config.xml file. The one for httpunit looks like the following:


<cruisecontrol>
    <project name="httpunit">
        <publishers>
            <onsuccess>
                <artifactspublisher dest="artifacts/${project.name}" 
                    file="projects/${project.name}/lib/${project.name}.jar"/>
            </onsuccess>
        </publishers>    
    </project>
</cruisecontrol>

The httpunit team can version that file as an artifact important to their project, just like the build file.

As a CruiseControl administrator at a big company, I might have tens or hundreds of different projects that I want to include in my server. I also might want to include the same project in lots of different CI servers. Extracting the project specific definitions allows me to do this with ease!

One Response to “Refactoring CruiseControl Configuration Files (From Agile 2009 Demo)”

  1. Michael Maguire says:

    Paul,

    This looks cool but I have a question with your last example. Do file=”projects/connectfour/cruisecontrol.config.xml” and file=”projects/httpunit/cruisecontrol.config.xml” also need to be added as bootstrappers in their own project definitions so that they’ll be read in if they change?

    Cheers,
    Michael

Leave a Reply