Continuous Integration with CruiseControl 2.7.1 and ClearCase
Abstract
Step-by-step installation of CruiseControl in Solaris 9 with ClearCase. This post details instructions on how to setup CruiseControl build directory in a flexible way and to write advanced CruiseControl project configuration files using plugins. Using the installation directory setup explained here we can run two continuous integration streams using 2 instances of CruiseControl running on 1 host machine. This installation guide can be suited to Windows machine with little efforts.
Common problems with solutions are listed at the end of this post.
Build environment and machine specs
The host machine architecture is SPARC with 2GB physical memory running Solaris 9, the SCM we are using is ClearCase.
Part 1: Installation and Directory Setup
Define two variables :
Let CC_INSTALL equal to /opt/cc2
and let CC_HOME equal to /opt/cc2/cruisecontrol-bin-2.7.1
Now download cruisecontrol 2.7.1 binary and extract it to /opt/cc2/cruisecontrol-bin-2.71
Create clearcase view for each product development streams (assuming you have not already created storage location for the views called asantoso_views, here I use asantoso_views storage location tag, and I have two ClearCase views to be integrated with CruiseControl):
% cleartool mkstgloc -view asantoso_views /private/asantoso/viewstore % cleartool mkview -tag apc_cruisecontrol_3.0_stream1 -stgloc asantoso_views % cleartool mkview -tag apc_cruisecontrol_3.0_stream2 -stgloc asantoso_views
We structure our directory like this (s1 and s2 are my stream names):
/opt/cc2
/opt/cc2/cruisecontrol-bin-2.7.1
/opt/cc2/streams/s1
/opt/cc2/streams/s1/logs
/opt/cc2/streams/s1/configs
/opt/cc2/streams/s2
/opt/cc2/streams/s2/logs
/opt/cc2/streams/s2/configs
Create this structure and set the directory read and write permissions to your user id.
The motivation using this setup:
- Upgrading to a new version of CruiseControl is a snap, just replace cruisecontrol-bin-2.7.1 and update the necessary path variables in file $CC_HOME/streams/s1/start.sh
- CruiseControl serializes a project into .ser file and as of 2.7.1 there’s no way to specify where the .ser files are stored, by default it is stored in the context where CruiseControl is started.
- We will have start.sh script in $CC_HOME/streams/s1 which executes CruiseControl so that the .ser files will be stored in there.
- We will store our configuration files in the $CC_HOME/streams/s1/configs directory and the build logs of each CruiseControl projects will be stored in $CC_HOME/streams/s1/logs directory
The CruiseControl server log will be stored in $CC_HOME/streams/s1.
Now, create start.sh in $CC_HOME/streams/s1
#!/bin/bash CC_INSTALL=/opt/cc2 CC_HOME=$CC_INSTALL/cruisecontrol-bin-2.7.1 CC_LOGS=$CC_INSTALL/streams/s1/logs CC_CONFIG=$CC_INSTALL/streams/s1/configs/config.xml CC_RMIPORT=1111 CC_WEBPORT=8081 CC_JMXPORT=8000 CC_NAME=APC_STREAM1 CLEARCASE_HOME=/opt/rational/clearcase/bin PATH=$JAVA_HOME/bin:$CC_HOME:$PATH:$CLEARCASE_HOME export PATH cleartool startview apc_cruisecontrol_3.0_stream1 cruisecontrol.sh -jmxport $CC_JMXPORT -rmiport $CC_RMIPORT -cchome $CC_HOME -webport $CC_WEBPORT -logDir $CC_LOGS $* -configfile $CC_CONFIG -ccname $CC_NAME
CruiseControl launcher options (case sensitive):
- -jmxport : jmx port
- -rmiport : rmi port
- -webport : jetty http server port
- -cchome : path to cc binary
- -logDir : path to base log directory where each CruiseControl project’s log is stored, if relative path is specifed, it is evaluated relative to context where CC is started.
- -configfile : the entry configuration file
- -ccname : the name to be displayed on jsp build reports page
Make sure you have JAVA_HOME environment variable set correctly.
The procedure for setting CC for second stream is nearly identical to the first, we need to assign distinct port and path values.
Open cruisecontrol.sh in $CC_HOME, add or modify the following lines on appropriate locations
Remove the line
CCDIR=`pwd`
Specify the cruisecontrol binary directory
CCDIR=`dirname $0` export CCDIR
Optional step: delete the last two lines
$EXEC & echo $! > cc.pid
Optional step: add two lines at the end of file
echo $EXEC exec $EXEC
Uncomment CC_OPTS variable, allocate large JVM initial and maximum heap size depending on size of the host machine’s memory,
-Xms256m -Xmx1024m
Note that CruiseControl itself does not consume huge memory to run, but if you do not use fork in javac task it is advisable to set enough memory for CruiseControl
Part 2: CruiseControl Configuration File
Create config.xml in $CC_HOME/streams/s1
<cruisecontrol> <property name="cruisecontrol.settings.webport" value="8081"/> <!-- the jetty port, as specified in the start.sh --> <property name="cruisecontrol.settings.host" value="localhost"/> <property name="cruisecontrol.web.url.base" value="http://${cruisecontrol.settings.host}:${cruisecontrol.settings.webport}" /> <property name="cruisecontrol.install" value="/opt/cc2" /> <!-- CC_INSTALL where cruisecontrol is installed --> <property name="cruisecontrol.log.dir" value="logs" /> <!-- this path is evaluated starting from the context where CC is started --> <system> <configuration> <threads count="1" /> </configuration> </system> <include.projects file="main.xml"/> </cruisecontrol>
Lastly we include main.xml which is to define project templates.
Part 3: Configuration Templates and CruiseControl plugins
Create main.xml in $CC_HOME/streams/s1/configs
From this point on, it is specific to the type of projects that you have. In my case:
I have to abstract out the possible build project into three: periodic, manual, and timed into build templates through the use of CruiseControl plugins. The combination of using plugins and extraction of all the configuration values from these templates allows me to (for example) set the default email to be notified across all projects by modifying one property and furthermore I can set add email to be notified to a specific project.
Note: Similarly in here, if relative value is used in saveLogDir attribute, the path is evaluated from the context of where CC is started.
For example: If CC is started in /opt/cc2/streams/s1 by calling start.sh and saveLogDir value is logs then the final path is /opt/cc2/streams/s1/logs
A little thing to point at that in my build configuration contains the Apache Ivy ant task elements.
<cruisecontrol> <!-- Global Default Properties --> <property name="buildresult.url.base" value="${cruisecontrol.web.url.base}/buildresults" /> <property name="clearcase.viewname" value="apc_cruisecontrol_3.0_stream1" /> <property name="branch.name" value="dbapck3.0"/> <property name="elisa.dev.dir" value="/view/${clearcase.viewname}/cm/elisa/dev" /> <property name="build.dir" value="${elisa.dev.dir}/ni/bigbuild/libs" /><!-- the place of compiled binaries --> <property name="components.dir" value="${elisa.dev.dir}/ni/components" /> <property name="applications.dir" value="${elisa.dev.dir}/ni/applications" /> <property name="product.prefix" value="apc" /> <property name="extension" value="sh" /> <!-- ant script invoker file extension, example: sh or bat--> <property name="mailhost" value="24.24.24.24" /> <!-- the ip of the outgoing mail server --> <property name="buildmaster.email" value="agus.santoso@alcatel-lucent.com" /> <property name="email.subject.prefix" value="[CruiseControl]" /> <property name="project.manual.build.timeout" value="3600"/> <property name="project.manual.build.target" value="publish_core_cruise_component" /> <property name="project.manual.id" value="apc-core"/> <property name="project.manual.components.dir" value="${components.dir}" /> <property name="project.manual.mail.return" value="${buildmaster.email}"/> <property name="project.manual.mail.subjectprefix" value="${email.subject.prefix}"/> <property name="project.periodic.checkinterval" value="180"/> <property name="project.periodic.components.dir" value="${applications.dir}" /> <property name="project.periodic.id" value="apc"/> <property name="project.periodic.build.timeout" value="3600"/> <property name="project.periodic.build.target" value="do_publish_components"/> <property name="project.periodic.pause.start" value="2030"/> <property name="project.periodic.pause.end" value="2230"/> <property name="project.periodic.mail.return" value="${buildmaster.email}"/> <property name="project.periodic.mail.subjectprefix" value="${email.subject.prefix}"/> <property name="project.periodic.quietperiod" value="600"/> <property name="project.periodic.requiremodification" value="true"/> <property name="project.periodic.buildafterfailed" value="false" /> <property name="project.timed.components.dir" value="${applications.dir}" /> <property name="project.timed.timeout" value="3600" /> <property name="project.timed.time" value="2100" /> <property name="project.timed.id" value="apc"/> <property name="project.timed.buildafterfailed" value="false" /> <property name="project.timed.mail.return" value="${buildmaster.email}"/> <property name="project.timed.mail.subjectprefix" value="${email.subject.prefix}"/> <property name="project.timed.build.target" value="do_publish_components" /> <!-- register cruisecontrol plugins --> <plugin name="ivybuildstatus" classname="fr.jayasoft.ivy.cruise.IvyBuildStatus" /> <plugin name="ivyconditional" classname="fr.jayasoft.ivy.cruise.IvyConditionalPublisher" /> <plugin name="antlistener" classname="com.alcatel.util.cc.AntListener" /> <!-- mail publisher templates , add user emails here--> <plugin name="project.m.emailset" skipusers="true" classname="net.sourceforge.cruisecontrol.publishers.LinkEmailPublisher" mailhost="${mailhost}" reportsuccess="always" subjectprefix="${project.manual.mail.subjectprefix}" buildresultsurl="${buildresult.url.base}/${project.name}" returnaddress="${project.manual.mail.return}"> <ignore user="User" /> <always address="${project.manual.mail.return}" /> </plugin> <plugin name="project.p.emailset" skipusers="true" classname="net.sourceforge.cruisecontrol.publishers.LinkEmailPublisher" mailhost="${mailhost}" reportsuccess="always" subjectprefix="${project.periodic.mail.subjectprefix}" buildresultsurl="${buildresult.url.base}/${project.name}" returnaddress="${project.periodic.mail.return}"> <ignore user="User" /> <always address="${project.periodic.mail.return}" /> </plugin> <plugin name="project.t.emailset" skipusers="true" classname="net.sourceforge.cruisecontrol.publishers.LinkEmailPublisher" mailhost="${mailhost}" reportsuccess="always" subjectprefix="${project.timed.mail.subjectprefix}" buildresultsurl="${buildresult.url.base}/${project.name}" returnaddress="${project.timed.mail.return}" returnname="CruiseControl"> <ignore user="User" /> <always address="${project.timed.mail.return}" /> </plugin> <plugin name="create.build.directories.bootstrapper" classname="net.sourceforge.cruisecontrol.bootstrappers.AntBootstrapper" antworkingdir="." buildfile="build.xml" uselogger="false" usedebug="false" target="create-build-directories"> <property name="target.dir" value="${build.dir}/${project.id}"/> </plugin> <!-- /*********************************************************************** * * Manual * ************************************************************************ --> <!-- MANUAL BUILD CONFIGURATION TEMPLATE --> <plugin name="project.m.default.antbuilder" classname="net.sourceforge.cruisecontrol.builders.AntBuilder" buildfile="${project.manual.components.dir}/${project.manual.id}/build.xml" target="${project.manual.build.target}" uselogger="false" antWorkingDir="${elisa.dev.dir}/ni" antscript="${elisa.dev.dir}/ni/ant.${extension}" saveLogDir="${cruisecontrol.log.dir}/${project.name}" usedebug="false" timeout="${project.manual.build.timeout}"/> <plugin name="project.m" buildafterfailed="false" classname="net.sourceforge.cruisecontrol.ProjectConfig"> <listeners> <currentbuildstatuslistener file="${cruisecontrol.log.dir}/${project.name}/status.txt"/> </listeners> <log dir="${cruisecontrol.log.dir}/${project.name}"> <merge dir="${build.dir}/${project.manual.id}/test/testdata"/> </log> <modificationset> <forceonly/> </modificationset> <bootstrappers> <create.build.directories.bootstrapper> <property name="target.dir" value="${build.dir}/${project.manual.id}" /> </create.build.directories.bootstrapper> </bootstrappers> <schedule interval="31536000" showProgress="true"> <project.m.default.antbuilder /> </schedule> <publishers> <artifactspublisher dir="${build.dir}/${project.manual.id}/test/testreports" dest="${cruisecontrol.log.dir}/${project.name}" subdirectory="test"/> <artifactspublisher dir="${build.dir}/${project.manual.id}/coverageHistoryReport" dest="${cruisecontrol.log.dir}/${project.name}" subdirectory="coverageHistoryReport"/> <artifactspublisher dir="${build.dir}/${project.manual.id}/coverageReport" dest="${cruisecontrol.log.dir}/${project.name}" subdirectory="coverageReport"/> <ivyconditional> <project.m.emailset /> </ivyconditional> </publishers> </plugin> <!-- /**************************************************************** * * Periodic * ==*************************************************************** --> <!-- PERIODIC BUILD CONFIGURATION TEMPLATE --> <plugin name="project.p.default.antbuilder" classname="net.sourceforge.cruisecontrol.builders.AntBuilder" buildfile="${project.periodic.components.dir}/${project.periodic.id}/build.xml" target="${project.periodic.build.target}" uselogger="false" antWorkingDir="${elisa.dev.dir}/ni" antscript="${elisa.dev.dir}/ni/ant.${extension}" saveLogDir="${cruisecontrol.log.dir}/${project.name}" usedebug="false" timeout="${project.periodic.build.timeout}"/> <plugin name="project.p" requiremodification="${project.periodic.requiremodification}" buildafterfailed="${project.periodic.buildafterfailed}" classname="net.sourceforge.cruisecontrol.ProjectConfig"> <listeners> <!-- <apc_lockfilelistener projectname="${project.name}"/> --> <currentbuildstatuslistener file="${cruisecontrol.log.dir}/${project.name}/status.txt" /> </listeners> <log dir="${cruisecontrol.log.dir}/${project.name}"> <merge dir="${build.dir}/${project.periodic.id}/test/testdata" /> </log> <bootstrappers> <!-- <apc_lockfilebootstrapper projectname="${project.name}"/> --> <create.build.directories.bootstrapper> <property name="target.dir" value="${build.dir}/${project.periodic.id}" /> </create.build.directories.bootstrapper> </bootstrappers> <modificationset quietperiod="${project.periodic.quietperiod}"> <ivybuildstatus ivyfile="${project.periodic.components.dir}/${project.periodic.id}/ivy.xml" /> <clearcase branch="${branch.name}" viewpath="${project.periodic.components.dir}/${project.periodic.id}"/> </modificationset> <schedule interval="${project.periodic.checkinterval}"> <pause starttime="${project.periodic.pause.start}" endtime="${project.periodic.pause.end}" /> <project.p.default.antbuilder /> </schedule> <publishers> <artifactspublisher dir="${build.dir}/${project.periodic.id}/test/testreports" dest="${cruisecontrol.log.dir}/${project.name}" subdirectory="test"/> <artifactspublisher dir="${build.dir}/${project.periodic.id}/coverageHistoryReport" dest="${cruisecontrol.log.dir}/${project.name}" subdirectory="coverageHistoryReport"/> <artifactspublisher dir="${build.dir}/${project.periodic.id}/coverageReport" dest="${cruisecontrol.log.dir}/${project.name}" subdirectory="coverageReport"/> <ivyconditional> <project.p.emailset /> </ivyconditional> </publishers> </plugin> <!-- /************************************************************** * * Timed * *************************************************************** --> <plugin name="project.t.default.antbuilder" classname="net.sourceforge.cruisecontrol.builders.AntBuilder" buildfile="${project.timed.components.dir}/${project.timed.id}/build.xml" target="${project.timed.build.target}" uselogger="false" antWorkingDir="${elisa.dev.dir}/ni" usedebug="false" antscript="${elisa.dev.dir}/ni/ant.${extension}" saveLogDir="${cruisecontrol.log.dir}/${project.name}" timeout="${project.timed.timeout}" time="${project.timed.time}" /> <!-- project template --> <plugin name="project.t" buildafterfailed="${project.timed.buildafterfailed}" requiremodification="false" classname="net.sourceforge.cruisecontrol.ProjectConfig"> <listeners> <currentbuildstatuslistener file="${cruisecontrol.log.dir}/${project.name}/status.txt"/> </listeners> <log dir="${cruisecontrol.log.dir}/${project.name}"> </log> <modificationset quietperiod="0"> <clearcase branch="${branch.name}" viewpath="${project.timed.components.dir}/${project.timed.id}"/> </modificationset> <schedule> <project.t.default.antbuilder/> <!-- note : using composite as parent element of antbuilder will render time based build of antbuilder not to take effect , therefore to execute two different targets instead of using the composite tag, make a new target in the ant file that calls those two targets. --> </schedule> <publishers> <ivyconditional><project.t.emailset /></ivyconditional> </publishers> </plugin> <!-- /****************************************************************** * * Project Stream * ******************************************************************* --> <include.projects file="projects.xml"/> </cruisecontrol>
Last on the bottom we include projects.xml
Part 4: Projects configuration
Create projects.xml in $CC_HOME/streams/s1/configs
Note that we can override the template and/or properties defined earlier in the parent config file but still bound to the context of the child config. The overriden template will still use properties in the parent config file unless overriden in the child.
<cruisecontrol> <project name="integration-periodic" requiremodification="true" buildafterfailed="false"> <listeners> <currentbuildstatuslistener file="${cruisecontrol.log.dir}/${project.name}/status.txt" /> </listeners> <log dir="${cruisecontrol.log.dir}/${project.name}"> </log> <modificationset quietperiod="900"> <clearcase branch="${branch.name}" viewpath="${components.dir}"/> <clearcase branch="${branch.name}" viewpath="${applications.dir}/apc"/> </modificationset> <schedule interval="${project.periodic.checkinterval}"> <pause starttime="${project.periodic.pause.start}" endtime="${project.periodic.pause.end}"/> <ant buildfile="${applications.dir}/apc/build.xml" target="do_publish_components" uselogger="false" antWorkingDir="${elisa.dev.dir}/ni" antscript="${elisa.dev.dir}/ni/ant.${extension}" saveLogDir="${cruisecontrol.log.dir}/${project.name}" usedebug="false" timeout="${project.periodic.build.timeout}" /> </schedule> <publishers> <ivyconditional> <project.p.emailset /> </ivyconditional> </publishers> </project> <!-- daily build --> <project.t name="integration-nightly"> <property name="project.timed.time" value="2100" /> <property name="project.timed.id" value="apc"/> <property name="project.timed.components.dir" value="${applications.dir}"/> <property name="project.timed.build.target" value="do_publish_components"/> <modificationset quietperiod="0"> <clearcase branch="${branch.name}" viewpath="${components.dir}"/> <clearcase branch="${branch.name}" viewpath="${applications.dir}/apc"/> </modificationset> <schedule interval="1"> <project.t.default.antbuilder /> </schedule> </project.t> <!-- override global properties in parent config --> <property name="project.periodic.components.dir" value="${components.dir}" /> <property name="project.periodic.build.target" value="publish_core_cruise_component"/> <!-- periodic component build --> <project.p name="alcatel-mvc-plugins" > <property name="project.periodic.id" value="alcatel-mvc-plugins"/> </project.p> <project.p name="apc-a7324-plug"> <property name="project.periodic.id" value="apc-a7324-plug"/> </project.p> <project.p name="apc-alcatel-plug"> <property name="project.periodic.id" value="apc-alcatel-plug"/> </project.p> <project.p name="apc-core" > <property name="project.periodic.id" value="apc-core"/> </project.p> <project.p name="apc-g7342-plug" > <property name="project.periodic.id" value="apc-g7342-plug"/> </project.p> <project.p name="apc-gpon-plug" > <property name="project.periodic.id" value="apc-gpon-plug"/> </project.p> <project.p name="apc-inventory" > <property name="project.periodic.id" value="apc-inventory"/> </project.p> <project.p name="apc-lkgplugin" > <property name="project.periodic.id" value="apc-lkgplugin"/> </project.p> <project.p name="apc-northbound" > <property name="project.periodic.id" value="apc-northbound"/> </project.p> <project.p name="application-framework" > <property name="project.periodic.id" value="application-framework"/> </project.p> <project.p name="aware" > <property name="project.periodic.id" value="aware"/> </project.p> <project.p name="axs-adapter" > <property name="project.periodic.id" value="axs-adapter"/> </project.p> <project.p name="axs-utils" > <property name="project.periodic.id" value="axs-utils"/> </project.p> <project.p name="belief-network" > <property name="project.periodic.id" value="belief-network"/> </project.p> <project.p name="bulkcollector" > <property name="project.periodic.id" value="bulkcollector"/> </project.p> <project.p name="bulkcollector-northbound" > <property name="project.periodic.id" value="bulkcollector-northbound"/> </project.p> <project.p name="capabilities" > <property name="project.periodic.id" value="capabilities"/> </project.p> </cruisecontrol>
Notice two important things:
- In integration nightly project I am overriding child elements of a template.
- I am overriding the global properties: project.periodic.components.dir and project.periodic.build.target to be used by the projects in this file. If I override one of the property twice or more, the last declared property will be used. This means that the order which the properties appear does not matter.
Common Problems and Solutions
- Invalid email user
As far as 2.7.1 is concerned, when you click on the JSP build button, CC generates one fake modification created by fake “user” and it will send email to this address “user@mailhost” which is most probably does not exist. One way to suppress this is by adding <ignore user=”User” /> as a child of the email publisher, another way is by mapping this user to a valid email address <map user=”User” address=”validuser@validhost.com” /> - Time based build operates as a periodic build if declared inside a composite element.
One solution is to create time based build as a separate project. - logDir not found
First note the context where CC is started, check the validity of the path -logDir and subsequently the validity of saveLogDir attribute value of the antbuilder. If relative path is used in saveLogDir the path is relative to the context of -logDir, and lastly check if read/write permission are set correctly. - Killing CruiseControl JMX
If some reasons, the JMX server is not terminated and stays in background you can kill its process
get the pid by typing: ps -ef | grep mx4j
About this entry
You’re currently reading “Continuous Integration with CruiseControl 2.7.1 and ClearCase,” an entry on simple blog
- Published:
- February 27, 2008 / 4:19 pm
- Category:
- Continuous Integration, CruiseControl
9 Comments
Jump to comment form | comment rss [?] | trackback uri [?]