Dashboard > CruiseControl > UsingCruiseControlWithCplusPlus
UsingCruiseControlWithCplusPlus Log In View a printable version of the current page.

Added by Robert Watkins , last edited by David Jackman on Apr 26, 2007  (view change) show comment
Labels: 
(None)

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:

<!-- select os specific builder -->
<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:

// Licenced under the GLPL, see http://www.gnu.org/licenses/lgpl.html

#ifndef __cxxtest__XmlPrinter_h__
#define __cxxtest__XmlPrinter_h__

//
// The XmlPrinter is a simple TestListener that
// prints JUnit style xml to the output stream
//


#include <cxxtest/Flags.h>

#ifndef _CXXTEST_HAVE_STD
#   define _CXXTEST_HAVE_STD
#endif // _CXXTEST_HAVE_STD

#include <cxxtest/XmlFormatter.h>
#include <cxxtest/StdValueTraits.h>

#ifdef _CXXTEST_OLD_STD
#   include <iostream.h>
#else // !_CXXTEST_OLD_STD
#   include <iostream>
#endif // _CXXTEST_OLD_STD

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 // __cxxtest__XmlPrinter_h__


XmlFormatter.h:

// Licenced under the GLPL, see http://www.gnu.org/licenses/lgpl.html

#ifndef __CXXTEST__XMLFORMATTER_H
#define __CXXTEST__XMLFORMATTER_H

//
// The XmlFormatter is a TestListener that
// prints reports of the errors to an output
// stream in the form of an XML document.
//

// The following definitions are used if stack trace support is enabled,
// to give the traces an easily-parsable XML format.  If stack tracing is
// not enabled, then these definitions will be ignored.
#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() );

            // TODO FIXME - use xStr and yStr too, which are the original strings typed into assertequals -
            // Sample output -- Error: Expected (1 == 2 + 3), found (1 != 5)
            // Note: less JUnit-like, but very useful!
        }

        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() );
        }

        // xxx kill me
        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 += "&quot;"; break;
                    case '\'': escStr += "&apos;"; break;
                    case '<':  escStr += "&lt;"; break;
                    case '>':  escStr += "&gt;"; break;
                    case '&':  escStr += "&amp;"; 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 << ">";    //finish the start tag before writing body
            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 // __CXXTEST__ERRORFORMATTER_H



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 the input parameters

    validate ();
    BufferedReader reader = null;
    FileOutputStream output = null;
    try
    {

      // Setup file input and output

      reader = new BufferedReader (new FileReader (m_srcfile));

      output = new FileOutputStream (m_destfile);
      PrintWriter writer = new PrintWriter (new OutputStreamWriter (output, "UTF-8"));

       // Add XML header and lop level tag

      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(">");

      // Filter out non XML CDATA compatable characters

      String line = reader.readLine();
      while (null != line)
      {
        writer.print(" <line><![CDATA[");
        line = line.replace('\0', ' ');
        writer.print(line);
        writer.println("]]></line>");

        line = reader.readLine();
      }

      // Add XML closing tags

      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);
    }

    // Close files

    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://confluence.public.thoughtworks.org/display/CC/UsingCruiseControlWithCplusPlus
 *
 * 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"));

	    // add header and toplevel tags
	    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\">");

	    // prepare regex patterns for warning/error matching
	    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);
	    }

	    // okay, output filtered messages from sourcefile
	    String line = reader.readLine();
	    while(null != line) {
		// print the tag
		writer.print(" <message priority=\"");
		// check if it's a warning or error
		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("\">");

		// filter nulls
		line = line.replace('\0', ' ');

		// print the rest
		writer.print("<![CDATA[");
		writer.print(line);
		writer.println("]]></message>");

		// next line
		line = reader.readLine();
	    }

	    // close the XML
	    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");
	}
    }
}



Powered by a free Atlassian Confluence Open Source Project / Non-profit License granted to ThoughtWorks, Inc.. Evaluate Confluence today.
Powered by Atlassian Confluence 2.7.1, the Enterprise Wiki. Bug/feature request - Atlassian news - Contact administrators