This recipe is useful for those who use CVS as a code repository. It assumes that you also use View CVS to access the historical information in the repository.
Goal
When the build is broken and unit tests have failed, it is useful to quickly pull up side-by-side code changes for each of the files modified since the last working build. This aids rapid code inspection to understand the changes you and others have made that broke the build.
Cruise Control (2.4.x), Ant (1.6.x), CVS and View CVS (0.9.2 or 1.0) provide the components needed to enhance
the Cruise Control web page or HTML Email that will give you this infomation at the click of a hyperlink.
Key Elements
- View CVS provides side-by-side color coded difference pages in its web interface. This is where we need to get to, to achieve our goal. We need the current revision and last working revision numbers of a modified file when selecting the difference page.
- Cruise Control has the knowlege of the time of the last build and the time of the last working build. These timestamps will be needed to locate the ''good'' and ''bad'' revision numbers for each modified file.
- Ant provides the <cvstagdiff> task that converts the timestamps into these revision numbers. It conveniently outputs this information in XML
- Cruise Control JSP pages and/or the HTML Email Publisher can be readily adapted to include additional XSLT transforms to process the <cvstagdiff> XML output and generate a table of file names that are hyperlinked to the View CVS difference pages.
Key Integration Tasks
- We need to add small scriptlets to the Ant build script to convert the Cruise Control time stamps to a format accepted by the Ant <cvstagdiff> task
- We need to add a <cvstagdiff> task to the Ant build file to generate an XML file listing the changed files and the good and bad revision numbers
- We need to a parameter to CC's Reporting application's deployment descriptor (WEB-INF/web.xml).
Converting Timestamps in an Ant script
Until Cruise Control provides its own means of tailoring the formats of timestamps it passes to Ant it is simplest to use the Ant <script> element to encapsulate a scriplet that does this conversion. The "cctimestamp" property will be converted to "cvstimestamp" and "cclastgoodbuildtimestamp" will be converted to "cvslastgoodbuildtimestamp" in this scriplet. The Ant <script> task supports a number of languages. I'll give examples both in javascript and python.
Javascript scriptlet version:
<project name="project" default="main" basedir=".">
<target name="cc2cvstimestamps">
<script language="javascript'>
<![CDATA[
importClass(java.text.SimpleDateFormat);
cc_formatter = new SimpleDateFormat("yyyyMMddHHmmss");
cvs_formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
date = cc_formatter.parse(project.getProperty("cctimestamp"));
project.setProperty("cvstimestamp", cvs_formatter.format(date));
date = cc_formatter.parse(project.getProperty("cclastgoodbuildtimestamp"));
project.setProperty("cvslastgoodbuildtimestamp", cvs_formatter.format(date));
]]>
</script>
</target>
...
</project>
Python scriptlet version:
<project name="project" default="main" basedir=".">
<target name="cc2cvstimestamps">
<script language="jython">
<![CDATA[
from java.text import SimpleDateFormat
cc_formatter = SimpleDateFormat("yyyyMMddHHmmss")
cvs_formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
date = cc_formatter.parse(project.getProperty("cctimestamp"))
project.setProperty("cvstimestamp", cvs_formatter.format(date))
date = cc_formatter.parse(project.getProperty("cclastgoodbuildtimestamp"))
project.setProperty("cvslastgoodbuildtimestamp", cvs_formatter.format(date))
]]>
</script>
</target>
...
</project>
[Note: Python requires that the script lines must start at the left margin]
Adding the <cvstagdiff> task to the Ant build script
This is an example of using the <cvstagdiff> task to generate an XML file listing the files modified (with revision numbers) between the two dates derived from Cruise Control via the Ant scriplets detailed above.
<target name = "cvstagdiff">
<cvstagdiff cvsroot = "${cvsroot}"
package = "${package}"
destfile = "cvs_diff.xml"
startdate = "${'''cvslastgoodbuildtimestamp'''}"
enddate = "${'''cvstimestamp'''}" />
</target>
The cvs_diff.xml (or whatever you name it) should be referenced by the Cruise Control config.xml file in the <log> section. For example to include the <cvstagdiff> output in the Cruise Control log you need something like:
<log dir = "logs">
<merge file = "cvs_diff.xml" />
...
</log>
Customize CC's Reporting application
Uncomment the following line in web.xml and change param-value to the URL of the View CVS cgi script
<!--
<context-param>
<param-name>xslt.viewcvs.url</param-name>
<param-value>http://cvs.sourceforge.net/viewcvs.py/cruisecontrol</param-value>
<description>The URL of the ViewCVS website used by cvstagdiff.xsl, checkstyle-details.xsl
and pmd-details.xsl
</description>
</context-param>
-->
If you get this far, whenever a build is broken, you should see on the Build Results JSP page or HTML email, a table of modified files that hyperlink to View CVS side-by-side differences between the good and bad file versions.
Enjoy!
Author: Tony Cook, tony at bci gb com - 1 March 2003
Using older release of CruiseControl or ant
- We need to apply a small patch to Cruise Control to ensure the time stamp for the last good build is passed to the Ant script. The patch is a one liner. Not needed if using CC from CVS HEAD as from 9 Jan 2004 or from 2.1.5 onwards
- We need to apply a couple of simple patches and one vital enhancement to the Ant <cvstagdiff> task as the version shipped with Ant 1.5.1 is buggy. The three patches are all one liners. These bugs are now ALL fixed in Ant 1.6.x. The enhancement has also been added
- We need to create a XSLT transform to process the <cvstagdiff> XML output to provide the hyperlinks to your View CVS URL. Not needed if using CC 2.4.0 onwards
- We need to customise the Cruise Control JSP page to include the new XSLT transform and/or specialise the HTML Email Publisher to include the same XSLT transform output. Not needed if using CC 2.4.0 onwards
Patching Cruise Control
This only needed for CC versions 2.0.x upto 2.1.4 as cclastbuildtimestamp and cclastgoodbuildtimestamp properties were added to CC 8 Jan 2004 in CC CVS and hence will be available from 2.1.5 onwards
To ensure the timestamp for the last good build of your project is sent to Ant you need to add one line of code to Project.java. The file is located in the source code distribution at:
cruisecontrol\main\src\net\sourceforge\cruisecontrol\Project.java
In Project.java locate the method getProjectPropertiesMap(Date now)' and add the line (this is for versions 2.0.x):
buildProperties.put("cclastgoodbuildtimestamp", getFormatedTime(_lastSuccessfulBuild));
or (this is for version 2.1.4):
buildProperties.put("cclastgoodbuildtimestamp", getFormatedTime(getLastSuccessfulBuild()));
after the line:
buildProperties.put("cctimestamp", getFormatedTime(now));
Rebuild and deploy the Cruise control jar. A word of warning here. This patch causes some of the Cruise Control unit tests to fail (not suprisingly), so run "build jar" to skip the unit tests or patch ProjectTest.java accordingly (increment the number of expected properties by one).
Patching the Ant <cvstagdiff> task
This is not needed in Ant 1.6.0 onwards as ALL these bugs are now fixed
Get the Ant 1.5.1 code base from the source code distribution and read up how to build from the sources.
We only need to patch one file (CvsTagDiff.java) located in
jakarta-ant-1.5.1\src\main\org\apache\tools\ant\taskdefs\cvslib
1) In CvsTagDiff.java locate the execute() method and change the line:
String rdiff = "rdiff -s "
+ (m_startTag != null ? ("-r " + m_startTag) : ("-D " + m_startDate))
+ " "
+ (m_endTag != null ? ("-r " + m_endTag) : ("-D " + m_endDate))
+ " " + m_package;
to:
String rdiff = "rdiff -s "
+ (m_startTag != null ? ("-r " + m_startTag) : ("-D '" + m_startDate + "'"))
+ " "
+ (m_endTag != null ? ("-r " + m_endTag) : ("-D '" + m_endDate + "'"))
+ " " + m_package;
This fix correctly ensures that dates with spaces are enclosed within single quotes.
2) In CvsTagDiff.java locate the parseRDiff(File tmpFile) method and change the line:
int headerLength = getHeaderLength(line);
to:
int headerLength = (line == null) ? 0 : getHeaderLength(line);
This patch ensures that CvsTagDiff does not raise a NPE when no changes are detected
Ant 1.5.4: headerLength is initialized with
int headerLength = 5 + m_package.length() + 1;
and line is checked for null, making this step unnecessary.
3) In CvsTagDiff.java locate the writeTagDiff(CvsTagEntry[] entries)' method and insert before the line:
a line to add a "package" attribute to the output XML file:
writer.print("package=\"" + m_package + "\" ");
writer.println(">");
This ensures that sufficient information is present in the XML to create a suitable href to link to the View CVS web pages
Transforming <cvstagdiff> Output
The following XSLT file is an example of setting up a table of file names that hyperlink to the View CVS edit difference pages. The only customisation that is need is to set up your values for:
| urlroot |
the URL of the View CVS cgi script |
| cvsroot |
the alias name for the CVS repository set in the viewcvs.conf file via the cvs_roots assignment |
This XSLT file also assumes that you have added entries to the Cruise Control CSS file. The assumed entries are:
| .differences-sectionheader |
define text and background style for the table header |
| .differences-data |
define text style for each table cell |
| .differences-oddrow |
define background color for odd rows |
| .differences-evenrow |
define background color for even rows |
Here is the sample XSLT file (cvstagdiff.xsl) to process the patched <cvstagdiff> output [Note this version only generates output when the build is broken - remove the *<xsl:if test="$broken">* test if you want side by side differences with OK builds]:
<?xml version="1.0"?>
<!-- ********************************************************************************
* Custom addition to Cruise Control
*
* This formats the Ant cvstagdiff output
*
*********************************************************************************-->
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:variable name="broken" select="cruisecontrol/build/@error"/>
<xsl:variable name="difference.list" select="cruisecontrol/tagdiff/entry"/>
<xsl:variable name="urlroot" select="' <View CVS CGI script URL> '"/>
<xsl:variable name="cvsroot" select="' <ViewCVS cvs root alias> '"/>
<xsl:template match="/">
<xsl:if test="$broken">
<table align="center" cellpadding="2" cellspacing="1" border="0" width="98%">
<tr>
<td class="differences-sectionheader" colspan="2">
 Differences since last good build: 
(<xsl:value-of select="count($difference.list)"/>)
</td>
<td class="differences-sectionheader">Last Working Version</td>
<td class="differences-sectionheader">Current Version</td>
</tr>
<xsl:apply-templates select="$difference.list">
<xsl:sort select="filename" order="descending" data-type="text" />
</xsl:apply-templates>
</table>
<hr/>
</xsl:if>
</xsl:template>
<xsl:template match="entry">
<xsl:variable name="package" select="../@package"/>
<xsl:variable name="filename" select="file/name"/>
<xsl:variable name="prevrevision" select="file/prevrevision"/>
<xsl:variable name="revision" select="file/revision"/>
<tr>
<xsl:if test="position() mod 2=0">
<xsl:attribute name="class">differences-oddrow</xsl:attribute>
</xsl:if>
<xsl:if test="position() mod 2!=0">
<xsl:attribute name="class">differences-evenrow</xsl:attribute>
</xsl:if>
<xsl:if test="$prevrevision and $revision">
<td class="differences-data"> modified </td>
<td class="differences-data">
<a href="{$urlroot}/{$package}/{$filename}.diff?r1={$prevrevision}&r2={$revision}&cvsroot={$cvsroot}">
<i><xsl:value-of select="$filename"/></i>
</a>
</td>
<td class="differences-data">
<a href="{$urlroot}/{$package}/{$filename}?r1={$prevrevision}&cvsroot={$cvsroot}#rev{$prevrevision}">
<xsl:value-of select="$prevrevision"/>
</a>
</td>
<td class="differences-data">
<a href="{$urlroot}/{$package}/{$filename}?r1={$revision}&cvsroot={$cvsroot}#rev{$revision}">
<xsl:value-of select="$revision"/>
</a>
</td>
</xsl:if>
<xsl:if test="not ($prevrevision) and not ($revision)">
<td class="differences-data"> deleted </td>
<td class="differences-data">
<a href="{$urlroot}/{$package}/{$filename}?cvsroot={$cvsroot}">
<i><xsl:value-of select="$filename"/></i>
</a>
</td>
<td class="differences-data"> </td>
<td class="differences-data"> </td>
</xsl:if>
<xsl:if test="not ($prevrevision) and $revision">
<td class="differences-data"> added </td>
<td class="differences-data">
<a href="{$urlroot}/{$package}/{$filename}?rev={$revision}&cvsroot={$cvsroot}&content-type=text/vnd.viewcvs-markup">
<i><xsl:value-of select="$filename"/></i>
</a>
</td>
<td class="differences-data"> </td>
<td class="differences-data">
<a href="{$urlroot}/{$package}/{$filename}?r1={$revision}&cvsroot={$cvsroot}#rev{$revision}">
<xsl:value-of select="$revision"/>
</a>
</td>
</xsl:if>
</tr>
</xsl:template>
</xsl:stylesheet>
Customising the Cruise Control JSP page
The Cruise Control buildresults.jsp file can be modified to reference the cvstagdiff.xsl file to include the table of source code differences hyperlinks. Here is the key modified section of the buildresults.jsp file:
<td valign="top" bgcolor="#FFFFFF">
<cruisecontrol:xsl xslFile="/xsl/header.xsl"/>
<p>
<cruisecontrol:xsl xslFile="/xsl/compile.xsl"/>
<p>
<cruisecontrol:xsl xslFile="/xsl/javadoc.xsl"/>
<p>
<cruisecontrol:xsl xslFile="/xsl/unittests.xsl"/>
<p>
<cruisecontrol:xsl xslFile="/xsl/modifications.xsl"/>
<p>
<cruisecontrol:xsl xslFile="/xsl/cvstagdiff.xsl"/>
<p>
<cruisecontrol:xsl xslFile="/xsl/distributables.xsl"/>
</td>
Customising the Cruise Control HTML Email Publisher'
The easiest aproach to add additional XSLT transforms to the Cruise Control HTML Email Publisher, is to derive your own class from HTMLEMailPublisher and add the aditional cvstagdiff.xsl transformation file to the file list. You then register this new class in your Cruise Control config.xml file and use in in place of the regular HTML Email Publisher.
public class CustomHTMLEmailPublisher extends HTMLEmailPublisher
{
private String[] newXslFileNames =
{
"header.xsl",
"compile.xsl",
"javadoc.xsl",
"unittests.xsl",
"modifications.xsl",
"cvstagdiff.xsl", "distributables.xsl"
};
public CustomHTMLEmailPublisher()
{
super();
setXSLFileNames(newXslFileNames);
}
}