See also "Continuous Integration with Visual C++ and COM" at http://www.martinfowler.com/articles/ciWithCom.html
For information how to get reports for your C++ builds, see CustomiseJSPReporting.
I assert that it isn't worth trying, and no one is doing it successfully. Essentially you first have to get minimally supported C++ Ant contrib tasks running, and then get that working under CC.
Interesting POV. Others have claimed to be doing it successfully, and I can't see why they'd lie...
Very bad point of view! Hardly founded on fact either! It is worth doing and I'm doing successfully and have said so in the past. I know a reasonable number of CC folk doing C++ as we have been exchanging tips'n'tricks via the CC use group!
I've been running C++ continous integration on both Windows and Linux using Visual Studio and GNU gcc without any problems for 15 months now. As a last resort Ant gives you the <exec> task that at least gets you started with any command line tool. Yes you can run Visual Studio from the command line! The Ant community out there have already produced many of the taskdefs needed . Rant over - here's a sketch on how to do it...
Here are some resources I've used to get a system as capable as a javac and junit based one.
Running compilers/linkers
Visual Studio Ant taskdefs can be got at http://www.arrizza.com/taskdefs/ . This includes taskdefs that simplify using Visual C++ 6, Visual C++.NET, C#, NUnit, Visual Basic 6, Visual Basic.NET and Vb''''''Unit.
You register these with Ant, for example:
<taskdef name = "vc7" classname = "com.arrizza.ant.taskdefs.VC7"/>
Here's an example of a generic Visual C++.NET Ant target I use (Note Ant will only run it on a Windows platform - see way below for how os.windows got set):
<target name = "windowsbuilder" if = "os.windows">
<echo message = "*** Building ${log.name} ***" />
<vc7 compilerfolder = "${vstudio.dir}"
projectrootfolder = "${sandbox.dir}"
projectfolder = "${module}"
project = "${project}"
buildmode = "${target.dir}"
logfile = "${out.dir}/${project}.log"
failonerror = "false"
/>
<text2xml srcfile = "${out.dir}/${project}.log"
destfile = "${out.dir}/${project}_log.xml"
element = "${build.tag}"
attribute = "name"
value = "${log.name}"
/>
<delete file = "${out.dir}/${project}.log" />
<concat destfile = "${out.dir}/${project}.build">0</concat>
</target>
For Linux I use the following generic set of tasks to give the equivalent outcome of the WindowsBuilder shown above (there a few extras that not all people would need. ${build.script} for example is any shell script that runs your linux/unix build. In our case it depends on ${main.script} so that is manipulated as well in this example - you may not want this extra bit):
<target name = "unixbuilder"
depends = "unixhasbuild, unixbuild, unixnobuild"
if = "os.unix" />
<target name = "unixhasbuild" if = "os.unix">
<condition property = "build.exists">
<and>
<available file = "${sandbox.dir}/${module}/${build.script}" />
<available file = "${sandbox.dir}/${module}/${main.script}" />
</and>
</condition>
</target>
<target name = "unixbuild" if = "build.exists">
<echo message = "*** Building ${log.name} ***" />
<chmod file = "${sandbox.dir}/${module}/${build.script}"
perm = "ug+x" />
<chmod file = "${sandbox.dir}/${module}/${main.script}"
perm = "ug+x" />
<exec dir = "${sandbox.dir}/${module}"
executable = "${sandbox.dir}/${module}/${build.script}"
failonerror = "false"
failifexecutionfails = "false"
resultproperty = "result"
output = "${out.dir}/${project}.log" >
/>
<text2xml srcfile = "${out.dir}/${project}.log"
destfile = "${out.dir}/${project}_log.xml"
element = "${build.tag}"
attribute = "name"
value = "${log.name}"
/>
<delete file = "${out.dir}/${project}.log" />
<concat destfile = "${out.dir}/${project}.build">${result}</concat>
<echo message = "*** Built ${log.name}. Result = ${result} ***" />
</target>
<target name = "unixnobuild" unless = "build.exists" if = "os.unix">
<echo message = "*** Not Building ${log.name} ***" />
<concat destfile = "${out.dir}/${project}.log">
<![CDATA[The following files specified on the command line:
${sandbox.dir}/${module}/${build.script}
could not be found and will not be loaded.
]]</concat>
<text2xml srcfile = "${out.dir}/${project}.log"
destfile = "${out.dir}/${project}_log.xml"
element = "${build.tag}"
attribute = "name"
value = "${log.name}"
/>
<delete file = "${out.dir}/${project}.log" />
<concat destfile = "${out.dir}/${project}.build">0</concat>
</target>
The WindowsBuilder and Unix'Builders are tied into a single generic builder via:
<target name = "builder" depends = "unixbuilder, windowsbuilder" />
This now allows a simple set of build tasks to be specified which are not concerned with the actual details of the platform or tools that should be used (this is done by the generic tasks shown above):
Here is our stuff for building various language flavours of our software toolkit (not all plaforms need to build them - you simply ommit the build script to skip them):
<antcall target = "builder">
<param name = "module" value = "${wrapper.module}" />
<param name = "project" value = "${wrapper.proj}" />
<param name = "log.name" value = "Toolkit C++" />
</antcall>
<antcall target = "builder">
<param name = "module" value = "${java.module}" />
<param name = "project" value = "${java.proj}" />
<param name = "log.name" value = "Toolkit Java" />
</antcall>
<antcall target = "builder">
<param name = "module" value = "${vb.module}" />
<param name = "project" value = "${vb.proj}" />
<param name = "log.name" value = "Toolkit VB" />
</antcall>
<antcall target = "builder">
<param name = "module" value = "${vb.net.module}" />
<param name = "project" value = "${vb.net.proj}" />
<param name = "log.name" value = "Toolkit VB.NET" />
</antcall>
Footnotes:
FYI Finding out which platform is based on Ant stuff like:
<condition property = "os.unix">
<os family = "unix" />
</condition>
<condition property = "os.windows">
<or>
<os family = "windows" />
<os family = "win9x" />
</or>
</condition>
Processing non XML logs
Text2Xml. I have created as simple custom Ant taskdef Text2Xml that wraps text logs output from any command line tool in XML so that the CC publishers can process them. I've included the code for this taskdef verbatim at the bottom of this page. All you need to do is use this taskdef in your Ant script and add some custom XSL to CC to include compiler logs etc in your CC report. I've also got some XSL that will colour code Visual Studio/gcc compiler warnings and errors.
Running C++ Unit Tests
CppUnit
CppUnit 1.9.x (see http://sourceforge.net/projects/cppunit) can be easy customised via its Xml Hook classes to produce XML output that is matched to Junit XML. I actually found it was easier to create custom XSL for use by CC as I have extended CppUnit to include memory leak detection and memory overrun/underrun detection - stuff JUnit has no need for! It is easy to run a CppUnit test suite as a console app that spits out XML and have Ant run the test suite via the <exec> taskdef. CC and a bunch of custom XSL does the rest in terms of merging the XML and processing it for publication.
For my customisation of CppUnit I created a simple console application shell that link to the CppUnit libraries and this takes some switches that determine the output type and location:
- -f xml forces XML output of the unit test results
- -o <file> specifies output file.
Thus the following Ant task can run test suites on any pplatform that CppUnit supports (most)
<target name = "unittest">
<exec dir = "${sandbox.dir}/${path}/${project}"
executable = "${sandbox.dir}/${path}/${project}/${target.dir}/${program} "
failonerror = "false"
failifexecutionfails = "false"
resultproperty = "result" >
<arg line = "-f xml" />
<arg line = "-o ${basedir}/${out.dir}/${project}_results.xml" />
</exec>
<concat destfile = "${out.dir}/${project}.test">${result}</concat>
</target>
CC will transform the test results into the build results page if it's in the JUnit format. I use the following XSL to convert from the CppUnit output format to the JUnit output format. Put the resulting file in the directory your CC config file specifies for test results and it will work.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="utf-8" indent="yes"/>
<xsl:param name="suitename"/>
<xsl:template match="TestRun">
<testsuite name="{$suitename}"
tests="{normalize-space(Statistics/Tests)}"
failures="{normalize-space(Statistics/Failures)}"
errors="{normalize-space(Statistics/Errors)}" time="">
<xsl:apply-templates/>
</testsuite>
</xsl:template>
<xsl:template match="FailedTests">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="SuccessfulTests">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="Test">
<testcase name="{normalize-space(Name)}" time="" />
</xsl:template>
<xsl:template match="FailedTest">
<testcase name="{normalize-space(Name)}" time="">
<failure message="{normalize-space(FailureType)}: {string(text()[last()])}">
</failure>
</testcase>
</xsl:template>
<xsl:template match="Statistics">
</xsl:template>
</xsl:stylesheet>
Note that the XSL above uses a parameter to specify the suite name (which isn't supplied in the CppUnit output). Here's a modified version of the above Ant script that uses this XSL to transform the results. I changed the CppUnit output filename to have a .tempxml extension, transform to a file with a .xml extension, then delete the temporary output (.tempxml) file. For the suitename, I'm just using the name of the program--you can supply something more meaningful if you like.
<target name = "unittest">
<exec dir = "${sandbox.dir}/${path}/${project}"
executable = "${sandbox.dir}/${path}/${project}/${target.dir}/${program} "
failonerror = "false"
failifexecutionfails = "false"
resultproperty = "result" >
<arg line = "-f xml" />
<arg line = "-o ${basedir}/${out.dir}/${project}_results.tempxml" />
</exec>
<style
style="${product.root}/build/cppunit-to-junit.xsl"
basedir="."
includes="${basedir}/${out.dir}/${project}_results.tempxml"
destdir="${basedir}/${out.dir}"
extension=".xml">
<param name="suitename" expression="${program}" />
</style>
<delete file="./${project}_results.tempxml" />
<concat destfile = "${out.dir}/${project}.test">${result}</concat>
</target>
Note: take a look at http://confluence.public.thoughtworks.org/display/CCNET/Using+CruiseControl.NET+with+CppUnit for some more information on the subject.
CxxTest
CxxTest is a nice C++ unit testing framework, which does not provide an XML output option out of the box. I found some CxxTest XML output code at net.sf.webcat.eclipse.cxxtest.framework_1.3.1, and modified it to produce JUnit (and therefore CruiseControl) -compiant XML so far only for the assert and assert_equal methods. Note that I'm no C++ guru, but it works well. Any improvements welcome!
Usage: cxxtestgen.py --runner=XmlPrinter [etc]
XmlPrinter.h:
#ifndef __cxxtest__XmlPrinter_h__
#define __cxxtest__XmlPrinter_h__
#include <cxxtest/Flags.h>
#ifndef _CXXTEST_HAVE_STD
# define _CXXTEST_HAVE_STD
#endif
#include <cxxtest/XmlFormatter.h>
#include <cxxtest/StdValueTraits.h>
#ifdef _CXXTEST_OLD_STD
# include <iostream.h>
#else # include <iostream>
#endif
namespace CxxTest
{
class XmlPrinter : public XmlFormatter
{
public:
XmlPrinter( CXXTEST_STD(ostream) &o = CXXTEST_STD(cout), const char *preLine = ":", const char *postLine = "" ) :
XmlFormatter( new Adapter(o) ) {}
virtual ~XmlPrinter() { delete outputStream(); }
private:
class Adapter : public OutputStream
{
CXXTEST_STD(ostream) &_o;
public:
Adapter( CXXTEST_STD(ostream) &o ) : _o(o) {}
void flush() { _o.flush(); }
OutputStream &operator<<( const char *s ) { _o << s; return *this; }
OutputStream &operator<<( Manipulator m ) { return OutputStream::operator<<( m ); }
OutputStream &operator<<( unsigned i )
{
char s[1 + 3 * sizeof(unsigned)];
numberToString( i, s );
_o << s;
return *this;
}
};
};
}
#endif
XmlFormatter.h:
#ifndef __CXXTEST__XMLFORMATTER_H
#define __CXXTEST__XMLFORMATTER_H
#define CXXTEST_STACK_TRACE_ESCAPE_AS_XML
#define CXXTEST_STACK_TRACE_NO_ESCAPE_FILELINE_AFFIXES
#define CXXTEST_STACK_TRACE_INITIAL_PREFIX "<stack-frame function=\""
#define CXXTEST_STACK_TRACE_INITIAL_SUFFIX "\"/>\n"
#define CXXTEST_STACK_TRACE_OTHER_PREFIX CXXTEST_STACK_TRACE_INITIAL_PREFIX
#define CXXTEST_STACK_TRACE_OTHER_SUFFIX CXXTEST_STACK_TRACE_INITIAL_SUFFIX
#define CXXTEST_STACK_TRACE_ELLIDED_MESSAGE ""
#define CXXTEST_STACK_TRACE_FILELINE_PREFIX "\" location=\""
#define CXXTEST_STACK_TRACE_FILELINE_SUFFIX ""
#include <cxxtest/TestRunner.h>
#include <cxxtest/TestListener.h>
#include <cxxtest/TestTracker.h>
#include <cxxtest/ValueTraits.h>
#include <cxxtest/OutputStream.h>
#include <iostream>
#include <string>
#include <sstream>
namespace CxxTest
{
class XmlFormatter : public TestListener
{
public:
XmlFormatter( OutputStream *o ) : _o(o) { }
int run()
{
(*_o) << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" << endl;
_o->flush();
TestRunner::runAllTests( *this );
return tracker().failedTests();
}
void enterWorld( const WorldDescription & /*desc*/ )
{
(*_o) << "<world>" << endl;
_o->flush();
}
static void totalTests( OutputStream &o )
{
char s[WorldDescription::MAX_STRLEN_TOTAL_TESTS];
const WorldDescription &wd = tracker().world();
o << wd.strTotalTests( s ) << (wd.numTotalTests() == 1 ? " test" : " tests");
}
void enterSuite( const SuiteDescription& desc )
{
(*_o) << " <testsuite name=\"" << desc.suiteName() << "\" ";
(*_o) << "file=\"" << desc.file() << "\" ";
(*_o) << "line=\"" << desc.line() << "\"";
(*_o) << ">"<< endl;
_o->flush();
}
void leaveSuite( const SuiteDescription & )
{
(*_o) << " </testsuite>" << endl;
_o->flush();
}
void enterTest( const TestDescription & desc )
{
(*_o) << " <testcase name=\"" << desc.testName() << "\" ";
(*_o) << "line=\"" << desc.line() << "\"";
(*_o) << ">" << endl;
_o->flush();
}
void leaveTest( const TestDescription & )
{
(*_o) << " </testcase>" << endl;
_o->flush();
}
void leaveWorld( const WorldDescription &desc )
{
(*_o) << "</world>" << endl;
_o->flush();
}
void trace( const char *file, unsigned line, const char *expression )
{
startTag( "trace", file, line );
attribute( "message", expression );
endSingletonTag();
}
void suiteInitError( const char *file, unsigned line, const char *expression )
{
(*_o) << " <testsuite-error type=\"init\" line=\"" << line << "\">" << endl;
(*_o) << expression;
(*_o) << " </testsuite-error>" << endl;
_o->flush();
}
void warning( const char *file, unsigned line, const char *expression )
{
(*_o) << " <warning line=\"" << line << "\">" << endl;
(*_o) << expression;
(*_o) << " </warning>" << endl;
_o->flush();
}
void failedTest( const char *file, unsigned line, const char *expression )
{
testFailure( file, line, "failure", expression );
}
void failedAssert( const char *file, unsigned line, const char *expression )
{
testFailure( file, line, "failedAssert",
( std::string( "Assertion failed: " ) + expression ).c_str() );
}
void failedAssertEquals( const char *file, unsigned line,
const char *expectedStr, const char *actualStr,
const char *expected, const char *actual )
{
testFailure( file, line, "failedAssertEquals",
( std::string( "expected:<" ) + expected + "> but was:<" + actual + ">" ).c_str() );
}
void failedAssertSameData( const char *file, unsigned line,
const char *xStr, const char *yStr, const char *sizeStr,
const void *x, const void *y, unsigned size )
{
startTag( "failed-assert-same-data", file, line );
attribute( "lhs-desc", xStr );
attributeBinary( "lhs-value", x, size );
attribute( "rhs-desc", yStr );
attributeBinary( "rhs-value", y, size );
attribute( "size-desc", sizeStr );
attribute( "size-value", size );
endSingletonTag();
}
void failedAssertDelta( const char *file, unsigned line,
const char *xStr, const char *yStr, const char *dStr,
const char *x, const char *y, const char *d )
{
startTag( "failed-assert-delta", file, line );
attribute( "lhs-desc", xStr );
attribute( "lhs-value", x );
attribute( "rhs-desc", yStr );
attribute( "rhs-value", y );
attribute( "delta-desc", dStr );
attribute( "delta-value", d );
endSingletonTag();
}
void failedAssertDiffers( const char *file, unsigned line,
const char *xStr, const char *yStr,
const char *value )
{
startTag( "failed-assert-ne", file, line );
attribute( "lhs-desc", xStr );
attribute( "rhs-desc", yStr );
attribute( "value", value );
endSingletonTag();
}
void failedAssertLessThan( const char *file, unsigned line,
const char *xStr, const char *yStr,
const char *x, const char *y )
{
startTag( "failed-assert-lt", file, line );
attribute( "lhs-desc", xStr );
attribute( "lhs-value", x );
attribute( "rhs-desc", yStr );
attribute( "rhs-value", y );
endSingletonTag();
}
void failedAssertLessThanEquals( const char *file, unsigned line,
const char *xStr, const char *yStr,
const char *x, const char *y )
{
startTag( "failed-assert-le", file, line );
attribute( "lhs-desc", xStr );
attribute( "lhs-value", x );
attribute( "rhs-desc", yStr );
attribute( "rhs-value", y );
endSingletonTag();
}
void failedAssertRelation( const char *file, unsigned line,
const char *relation, const char *xStr, const char *yStr,
const char *x, const char *y )
{
startTag( "failed-assert-relation", file, line );
attribute( "relation", relation );
attribute( "lhs-desc", xStr );
attribute( "lhs-value", x );
attribute( "rhs-desc", yStr );
attribute( "rhs-value", y );
endSingletonTag();
}
void failedAssertPredicate( const char *file, unsigned line,
const char *predicate, const char *xStr, const char *x )
{
startTag( "failed-assert-predicate", file, line );
attribute( "predicate", predicate );
attribute( "arg-desc", xStr );
attribute( "arg-value", x );
endSingletonTag();
}
void failedAssertThrows( const char *file, unsigned line,
const char *expression, const char *type,
bool otherThrown )
{
startTag( "failed-assert-throws", file, line );
attribute( "expression", expression );
attribute( "type", type );
attribute( "threw", otherThrown ? "other" : "none" );
endSingletonTag();
}
void failedAssertThrowsNot( const char *file, unsigned line, const char *expression )
{
startTag( "failed-assert-nothrow", file, line );
attribute( "expression", expression );
endSingletonTag();
}
protected:
OutputStream *outputStream() const
{
return _o;
}
private:
XmlFormatter( const XmlFormatter & );
XmlFormatter &operator=( const XmlFormatter & );
void testFailure( const char *file, unsigned line, const char *failureType, const char *message = NULL )
{
std::cerr << "Test " << file << " FAILED" << std::endl;
std::stringstream output;
const char* tagName = "failure";
output << startTagText( tagName, file, line );
if ( message != NULL )
{
output << attributeText( "message", message );
}
output << attributeText( "type", failureType );
output << bodyText( file, line, failureType, message );
output << endTagText( tagName );
writeOut( output.str() );
}
void failedAssertGeneric( const char *file, unsigned line,
const char *failureType, const char *message = NULL )
{
const char* tagName = "failure";
startTag( tagName, file, line );
if ( message != NULL )
{
attribute( "message", message );
}
attribute( "type", failureType );
body( file, line, failureType, message );
endTag( tagName );
}
static const char * escape(const std::string& str)
{
std::string escStr = "";
for(size_t i = 0; i < str.length(); i++)
{
switch(str[i])
{
case '"': escStr += """; break;
case '\'': escStr += "'"; break;
case '<': escStr += "<"; break;
case '>': escStr += ">"; break;
case '&': escStr += "&"; break;
default: escStr += str[i]; break;
}
}
return escStr.c_str();
}
void startTag( const char* name, const char *file, unsigned line )
{
writeOut( startTagText( name, file, line ) );
}
static std::string startTagText( const char* name, const char *file, unsigned line )
{
std::stringstream output;
output << "<" << name;
return output.str();
}
void attribute( const char* name, const char *value )
{
writeOut( attributeText( name, value ) );
}
static std::string attributeText( const char* name, const char *value )
{
std::stringstream output;
output << " " << name;
output << "=\"" << escape(value) << "\"";
return output.str();
}
void attribute( const char* name, unsigned value )
{
(*_o) << name;
(*_o) << "=\"" << value << "\" ";
}
void attributeBinary( const char* name, const void *value, unsigned size )
{
(*_o) << name;
(*_o) << "=\"";
dump(value, size);
(*_o) << "\" ";
}
void body( const char *file, unsigned line, const char* failureType, const char* message )
{
writeOut( bodyText( file, line, failureType, message ) );
}
static std::string bodyText( const char *file, unsigned line, const char* failureType, const char* message )
{
std::stringstream output;
output << ">"; output << escape( failureType );
if ( message != NULL )
{
output << ": " << escape( message );
}
output << std::endl;
output << "at (" << file << ":" << line << ")" << std::endl;
return output.str();
}
void endSingletonTag()
{
(*_o) << " />" << endl;
_o->flush();
}
void endTag( const char* name )
{
writeOut( endTagText( name ) );
}
static std::string endTagText( const char* name )
{
std::stringstream output;
output << "</" << name << ">" << std::endl;
return output.str();
}
void writeOut( std::string output )
{
(*_o) << output.c_str();
_o->flush();
}
void dump( const void *buffer, unsigned size )
{
unsigned dumpSize = size;
if ( maxDumpSize() && dumpSize > maxDumpSize() )
dumpSize = maxDumpSize();
const unsigned char *p = (const unsigned char *)buffer;
for ( unsigned i = 0; i < dumpSize; ++ i )
(*_o) << byteToHex( *p++ ) << " ";
if ( dumpSize < size )
(*_o) << "... ";
}
static void endl( OutputStream &o )
{
OutputStream::endl( o );
}
OutputStream *_o;
};
};
#endif
Summary
Yes its not as simple as using CC/Ant/Junit with Java based projects - which for most part is "out of the box". Building C++, VB, .NET requires a little more leg work. Hopefully the above case study should you how it might be done. Continuous Integration is such a valuable process that the effort required to to get CC/Ant to host non Java projects is an effort well worth spending.
Enjoy! Tony Cook - 29th Feb 2004 - tony at bci gb com
Appendix
Example of Using Text2Xml from Ant
<text2xml srcfile = "${out.dir}/${project}.log"
destfile = "${out.dir}/${project}_log.xml"
element = "compilerLog"
attribute = "name"
value = "${project}"
/>
You will need to register the task def with Ant:
<taskdef name = "text2xml"
classname = "com.gb.bci.ant.taskdefs.TextToXml"/>
Text2Xml source code
/*
TextToXml takes a text file and wraps it as a basic
XML file of the form:
<?xml version="1.0" encoding="UTF-8"?>
<element attribute="value">
<line><![CDATA[(line of log text)]]></line>
...
</element>
*/
package com.gb.bci.ant.taskdefs ;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
/**
Wraps a text file as an XML file
*/
public class TextToXml extends Task {
/**
* The file from which to read the text.
*/
private File m_srcfile;
/**
* The file in which to write the xml wrapped text.
*/
private File m_destfile;
/**
* The the top level XML element name.
*/
private String m_element = "text" ;
/**
* An optional attribute.
*/
private String m_attribute ;
/**
* An optional attribute value.
*/
private String m_value ;
/**
* Initialize this task.
*
* @exception BuildException if an error occurs
*/
public void init() throws BuildException
{
}
/**
* Set the input file to be xml wrapped.
*
* @param file the input file for the xml wrap.
*/
public void setSrcFile(File file)
{
m_srcfile = file;
}
/**
* Set the output file for the xml wrap.
*
* @param file the output file for the xml wrap.
*/
public void setDestFile(File file)
{
m_destfile = file;
}
/**
* Set the top level element name.
*
* @param element the top level element name.
*/
public void setElement(String element)
{
m_element = element;
}
/**
* Set the top level attribute name.
*
* @param attribute the top level name.
*/
public void setAttribute(String attribute)
{
m_attribute = attribute;
}
/**
* Set the top level element name.
*
* @param value the top level attribute value.
*/
public void setValue(String value)
{
m_value = value;
}
/**
* Execute task.
*
* @exception BuildException if an error occurs
*/
public void execute() throws BuildException
{
validate ();
BufferedReader reader = null;
FileOutputStream output = null;
try
{
reader = new BufferedReader (new FileReader (m_srcfile));
output = new FileOutputStream (m_destfile);
PrintWriter writer = new PrintWriter (new OutputStreamWriter (output, "UTF-8"));
writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
writer.print("<" + m_element);
if (null != m_attribute)
{
writer.print (" " + m_attribute + "=\"" + m_value + "\"");
}
writer.println(">");
String line = reader.readLine();
while (null != line)
{
writer.print(" <line><![CDATA[");
line = line.replace('\0', ' ');
writer.print(line);
writer.println("]]></line>");
line = reader.readLine();
}
writer.println("</" + m_element + ">");
writer.flush();
writer.close();
}
catch (UnsupportedEncodingException uee)
{
log (uee.toString (), Project.MSG_ERR);
}
catch (IOException ioe)
{
throw new BuildException (ioe.toString (), ioe);
}
finally
{
if (null != reader)
{
try
{
reader.close();
}
catch (IOException e)
{
}
}
if (null != output)
{
try
{
output.close();
}
catch (IOException ioe)
{
}
}
}
}
/**
* Validate the parameters specified for task.
*
* @exception BuildException if a parameter is not correctly set
*/
private void validate() throws BuildException
{
if (null == m_srcfile)
{
throw new BuildException("SrcFile must be set.");
}
if (null == m_destfile)
{
throw new BuildException("DestFile must be set.");
}
if (null != m_attribute && null == m_value)
{
throw new BuildException("Value must be set when Attribute is set.");
}
}
}
An improved Text2Xml
I improved - or at least modified - Text2Xml to look for the words "warning" and "error" in its input (case insensitive) and, instead of the simple XML of the original, produce a log file that can be simply merged into a CC build log. I hereby release this code into the public domain.
Note that I used Tony's original package name... hopefully that doesn't bother anyone.
Example usage
<taskdef name="text2ccxml"
classname="com.gb.bci.ant.taskdefs.TextToCCXml"/>
...
<text2ccxml srcfile="${src.dir}/${cctimestamp}-configure.log"
destfile="${src.dir}/${cctimestamp}-configure.log.xml"
target="${ant.project.name}"
task="configure"
isError="${configure.failed}"/>
Srcfile and destfile are as in the original - target is the "name" attribute of the target element inside the build element of the logfile that we're producing (see your existing CC build log files for examples). Task is the "name" attribute of the task element. IsError is a boolean that defines whether the build element shall have the error property set. You could set it like so:
<exec executable="make"
failonerror="false"
resultproperty="result"/>
<condition property="build.failed">
<isfailure code="${result}"/>
</condition>
Source code
package com.gb.bci.ant.taskdefs;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.regex.PatternSyntaxException;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
/**
* Converts text log files from compilers e.g.
* To the format that CruiseControl expects when
* merging logfiles.
*
* Based on work by Tony Cook available on the CruiseControl wiki
* at http: *
* Created: Wed Mar 14 10:21:05 2007
*
* @author <a href="mailto:PoutanenIlkka@JohnDeere.com">Ilkka Poutanen</a>
* @version 1.0
*/
public class TextToCCXml extends Task {
private File m_srcfile;
private File m_destfile;
private boolean m_isError;
private String m_target;
private String m_task;
public void setSrcfile(File srcfile)
{
m_srcfile = srcfile;
}
public void setDestfile(File destfile)
{
m_destfile = destfile;
}
public void setIsError(boolean error)
{
m_isError = error;
}
public void setTarget(String target)
{
m_target = target;
}
public void setTask(String task)
{
m_task = task;
}
public void execute() throws BuildException
{
validate();
BufferedReader reader = null;
FileOutputStream output = null;
PrintWriter writer = null;
try {
reader = new BufferedReader(new FileReader(m_srcfile));
output = new FileOutputStream(m_destfile);
writer = new PrintWriter(new OutputStreamWriter(output, "UTF-8"));
writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
writer.print("<build");
if (m_isError) {
writer.print(" error=\"Target " + m_target + " failed\"");
}
writer.println(" time=\"0 hours 00 minuts 00 seconds\">");
writer.println("<target name=\"" + m_target + "\" time=\"ignored\">");
writer.println("<task location=\"ignored\" name=\"" + m_task + "\" time=\"ignored\">");
Pattern p_w = null;
Pattern p_e = null;
try {
p_w = Pattern.compile(".*\\bwarning\\b.*", Pattern.CASE_INSENSITIVE);
p_e = Pattern.compile(".*\\berror\\b.*", Pattern.CASE_INSENSITIVE);
} catch (PatternSyntaxException pse) {
throw new BuildException(pse.toString(), pse);
}
String line = reader.readLine();
while(null != line) {
writer.print(" <message priority=\"");
Matcher m_w = p_w.matcher(line);
Matcher m_e = p_e.matcher(line);
if (m_w.matches()) {
writer.print("warn");
} else if (m_e.matches()) {
writer.print("error");
} else {
writer.print("info");
}
writer.print("\">");
line = line.replace('\0', ' ');
writer.print("<![CDATA[");
writer.print(line);
writer.println("]]></message>");
line = reader.readLine();
}
writer.println("</task>");
writer.println("</target>");
writer.println("</build>");
writer.flush();
writer.close();
} catch(UnsupportedEncodingException uee) {
log(uee.toString(), Project.MSG_ERR);
} catch(IOException ioe) {
throw new BuildException(ioe.toString(), ioe);
} finally {
if (null != reader) {
try { reader.close(); } catch(IOException e) {}
}
if (null != output) {
try { output.close(); } catch(IOException e) {}
}
}
}
private void validate() throws BuildException
{
if (null == m_srcfile) {
throw new BuildException("SrcFile must be set.");
}
if (null == m_destfile) {
throw new BuildException("DestFile must be set.");
}
if (null == m_target) {
throw new BuildException("Target must be set");
}
if (null == m_task) {
throw new BuildException("Task must be set");
}
}
}
|
|