In my last post I discussed integration CCNet/NAnt with Delphi/BDS 2006. That post however did not cover how to include the build results from Delphi in the project build report.
ERR File
When the bds.exe executes, it doesn’t output anything to STDOUT or STDERR. Instead, it outputs all of the compiler information into the file [ProjectName].err. Here’s an example of what you can expect to find in the file(ignore the various paths):
Delphi Command Line for "Project1.dpr"
$A8 $B- $C+ $D+ $E- $F- $G+ $H+ $I+ $J- $K- $L+ $M- $N+ $O- $P+ $Q- $R- $S- $T- $U- $V+ $W- $X+ $YD $Z1 -B -H+ -W
$M16384,1048576 $K4194304 -CG -Ee:\cc_sandbox\projects\Project1\ OutPutDir=out -N0DCUs
-Ue:\program files\borland\bds\4.0\units;e:\program files\borland\bds\4.0\vcl;
-Oe:\program files\borland\bds\4.0\units;e:\program files\borland\bds\4.0\vcl;
-Re:\program files\borland\bds\4.0\units;e:\program files\borland\bds\4.0\vcl;
PkgDllDir=C:\Documents and Settings\CCBUILD\My Documents\Borland Studio Projects\Bpl
PkgDcpDir=C:\Documents and Settings\CCBUILD\My Documents\Borland Studio Projects\Bpl
-AWinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; -C0 OBJOutputDir=DCUs
BPIOutputDir=C:\Documents and Settings\CCBUILD\My Documents\Borland Studio Projects\Bpl -W-UNSAFE_TYPE -W-UNSAFE_CODE
-W-UNSAFE_CAST Project1.dpr
[Pascal Warning] BrowserForm.pas(444): W1036 Variable 'list' might not have been initialized
Knowing the basic format of this file, it’s not very hard to scrape it so the information can be included the build report.
NAnt script for .ERR file scraping
Here’s a bit of script to include in your NAnt build scripts. It’s embedded c# code that reads all of the lines of a text file, and dumps them into a single xml document. Each individual line is wrapped inside an xml element named “line”. The document element is named “delphi-console”.
Here’s the script to include in your NAnt builds:
<script language="C#" prefix="script">
<references>
<include name="System.dll" />
<include name="System.Xml.dll" />
</references>
<imports>
<import namespace="System" />
<import namespace="System.IO" />
<import namespace="System.Xml" />
</imports>
<code>
<![CDATA[
[Function( "read-console-output" )]
public static string ReadConsoleOutput( string path )
{
string[] lines = File.ReadAllLines( path );
XmlDocument doc = new XmlDocument();
XmlElement root = doc.CreateElement( "delphi-console" );
doc.AppendChild( root );
foreach( string line in lines )
{
XmlElement lineNode = doc.CreateElement( "line" );
root.AppendChild( lineNode );
lineNode.InnerText = line;
} //END line loop
return doc.OuterXml;
} //END ReadConsoleOutput method
]]>
</code>
</script>
And here is the content of the Project1.err file converted to xml:
<delphi-console> <line>Delphi Command Line for "Project1.dpr"</line> <line> $A8 $B- $C+ $D+ $E- $F- $G+ $H+ $I+ $J- $K- $L+ $M- $N+ $O- $P+ $Q- $R- $S- $T- $U- $V+ $W- $X+ $YD $Z1 -B -H+ -W </line> <line> $M16384,1048576 $K4194304 -CG -Ee:\cc_sandbox\projects\Project1\ OutPutDir=out -N0DCUs </line> <line> -Ue:\program files\borland\bds\4.0\units;e:\program files\borland\bds\4.0\vcl;</line> <line> -Oe:\program files\borland\bds\4.0\units;e:\program files\borland\bds\4.0\vcl;</line> <line> -Re:\program files\borland\bds\4.0\units;e:\program files\borland\bds\4.0\vcl;</line> <line> PkgDllDir=C:\Documents and Settings\CCBUILD\My Documents\Borland Studio Projects\Bpl </line> <line> PkgDcpDir=C:\Documents and Settings\CCBUILD\My Documents\Borland Studio Projects\Bpl </line> <line> -AWinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; -C0 OBJOutputDir=DCUs </line> <line> BPIOutputDir=C:\Documents and Settings\CCBUILD\My Documents\Borland Studio Projects\Bpl -W-UNSAFE_TYPE -W-UNSAFE_CODE </line> <line> -W-UNSAFE_CAST Project1.dpr </line> <line>[Pascal Warning] BrowserForm.pas(444): W1036 Variable 'list' might not have been initialized</line> </delphi-console>
Now that you have the nant script to generate the build output xml file, you need to call it. If the bds.exe process fails, and you have failonerror attribute set to true, the err file scraping process won’t be executed. So for my Delphi build scripts, I wrap my “build” target with a new target that I inventively have named “trybuild”. This target calls the “build” target within a try/catch/finally block(available from the NAntContrib project). This way no matter what happens during the compile, the .err file will be scraped.
The following code listing demonstrates the use of .err file scraping:
<target name="trybuild">
<trycatch>
<try>
<call target="build" />
</try>
<catch property="failure">
<if test="${file::exists(file.result)}">
<fail>Delphi build failure: ${failure}</fail>
</if>
<if test="${not file::exists(file.result)}">
<fail>General failure:${failure}</fail>
</if>
</catch>
<finally>
<if test="${file::exists(file.result)}">
<echo file="${dir.logs}/delphi-console.xml">${script::read-console-output(file.result)}</echo>
</if>
</finally>
</trycatch>
</target>
Notice the various *if conditions. If the bds.exe fails and the .err file exists, then there was some build related failure. If the .err file does not exist, then there was some other problem.
Also, notice that the way file final xml file is generated is via a call to the echo task, which explicitly calls the read-console-output script we defined earlier.
Once you have that all in place, you must now modify your ccnet.config file to include this output file in the build log via the merge publisher. Example:
<publishers> <merge> <files> <file>logs/delphi-console.xml</file> </files> </merge> </publishers>
Now that the .err file has been scraped, and the contents have been merged into ccnet’s build log, we can work on outputting the content to the build report.
This part is relatively simple. I started with a copy of the file compile.xsl, located in the /xsl folder of the dashboard directory. From there, I created an xsl transform that looked for each line in the build log that was prefixed with [Pascal Error] or [Pascal Hint]. The script does not currently look for [Pascal Warning] lines, and treats hints as warnings. I named the xsl script “delphi-console-out.xsl”, and here is its full listing:
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="/">
<xsl:variable name="lines" select="/cruisecontrol/build/delphi-console/line" />
<xsl:if test="count($lines) > 0">
<xsl:variable
name="lines.error"
select="$lines[(contains(text(), '[Pascal Error]')) or (contains(text(), '[Pascal Fatal Error]'))]" />
<xsl:variable
name="lines.error.count"
select="count($lines.error)" />
<xsl:variable
name="lines.hint"
select="$lines[(contains(text(), '[Pascal Hint]'))]" />
<xsl:variable
name="lines.hint.count"
select="count($lines.hint)" />
<xsl:variable
name="lines.total"
select="count($lines.error) + count($lines.hint)"/>
<xsl:if test="$lines.error.count > 0">
<table class="section-table" cellpadding="2" cellspacing="0" border="0" width="98%">
<tr>
<td class="sectionheader">
Delphi Console Output - Errors: (<xsl:value-of select="$lines.error.count"/>)
</td>
</tr>
<tr>
<td>
<xsl:apply-templates select="$lines.error"/>
</td>
</tr>
</table>
</xsl:if>
<xsl:if test="$lines.hint.count > 0">
<table class="section-table" cellpadding="2" cellspacing="0" border="0" width="98%">
<tr>
<td class="sectionheader">
Delphi Console Output - Hints: (<xsl:value-of select="$lines.hint.count"/>)
</td>
</tr>
<tr>
<td>
<xsl:apply-templates select="$lines.hint"/>
</td>
</tr>
</table>
</xsl:if>
</xsl:if>
</xsl:template>
<xsl:template match="line">
<pre class="section-error">
<xsl:value-of select="text()"/>
</pre>
</xsl:template>
</xsl:stylesheet>
After that, you must modify the dashboard.config file to include the xsl. Fortunately, that’s easily done. Locate the dashboard/plugins/buildPlugins/buildReportBuildPlugin/xslFilenames element, and add a new <xslFile> element like so: <xslFile>xsl\delphi-console-out.xsl</xslFile>
Once all of this comes together, you can see at Delphi hints and errors reflected in ccnet’s build report. Here’s a screencap:
I hope this helps some one else.