Part 5: Continuous Integration - The CodeStatisticsBuild
This is the fifth post in a series where I document how to setup a Continuous Integration (CI) process using open source tools like CruiseControl.NET, NCover, NUnit, Subversion and Sandcastle together with MSBuild and VS 2005. Part 5 covers the CodeStatisticsBuild and the targets and tasks used by the CodeStatisticsBuild.
Post Updates
- 11/12/2007 - Integrated NDepend into the statistics build and updated the CC.Net configuration to use build queues and entity references.
MSBuild Scripts
The CodeStatisticsBuild happens at the end of every iteration - once a week as we have 1 week iterations. Its fires on successful completion of the DeploymentBuild. The purpose of the CodeStatisticsBuild is to generate metrics on our code base that we can use on a weekly basis to spot trends and identify areas of concern. We generate metrics using FxCop, NCover and NDepend.
The CodeStatisticsBuild is quite simple as it re-uses most of the targets that have already been defined for the DeveloperBuild. To run FxCop, the BuildAll target of the DeveloperBuild is invoked with the RunCodeAnalysis property set to True. To run NCover, the CodeCoverage target of the DeveloperBuild is invoked. To run NDepend, the NDepend target of the DeveloperBuild is invoked. The only additional tasks required for the CodeStatisticsBuild are tasks to copy the metrics to the CCNetArtifactDirectory to allow CC.NET to merge the results into the build report.
CopyCodeAnalysisResults
If you look at the BuildCode and BuildTests targets that the BuildAll target depends on, you will see notice that the FxCop results are added to a FxCopResults item group. The only thing left to do is therefore to copy these results for CC.NET to include in the build report.
1 <Target Name="CopyCodeAnalysisResults"
2 Condition=" '$(CCNetProject)' != '' ">
3
4 <Message Text="FxCopResults:$(NEW_LINE)$(TAB)@(FxCopResults->'%(Identity)', '$(NEW_LINE)$(TAB)')$(NEW_LINE)" Importance="low"/>
5
6 <CreateItem Include="$(CCNetArtifactDirectory)\*.$(FxCopFile)">
7 <Output TaskParameter="Include" ItemName="ExistingFxCopResults"/>
8 </CreateItem>
9
10 <Delete Files="@(ExistingFxCopResults)"/>
11 <Copy SourceFiles="@(FxCopResults)"
12 DestinationFolder="$(CCNetArtifactDirectory)"
13 ContinueOnError="true"/>
14
15 </Target>
Observations:
- Line 2: We test on the CCNetProject property to determine if the build was invoked via CC.NET. The CCNetProject property is one of the properties created and passed on by CC.NET when using the MSBuild CC.NET task.
- Lines 6-10: The existing FxCop results are deleted.
- Line 11-13: The new set of FxCop results are copied to the CCNetArtifactDirectory.
CopyCoverageResults
If you look at the CodeCoverage target, you will see notice that the NCover results are merged into a single NCoverSummaryFile. The only thing left to do is therefore to copy this file for CC.NET to include in the build report.
1 <Target Name="CopyCodeCoverageResults"
2 Condition=" '$(CCNetProject)' != '' ">
3
4 <CreateItem Include="$(CCNetArtifactDirectory)\$(NCoverSummaryFile)">
5 <Output TaskParameter="Include" ItemName="ExistingNCoverResults"/>
6 </CreateItem>
7
8 <Delete Files="@(ExistingNCoverResults)"/>
9 <Copy SourceFiles="$(CodeMetricsFolder)\$(NCoverSummaryFile)"
10 DestinationFolder="$(CCNetArtifactDirectory)"
11 ContinueOnError="true"/>
12
13 </Target>
Observations:
- Line 2: We test on the CCNetProject property to determine if the build was invoked via CC.NET.
- Lines 4-8: The existing NCover summary file is deleted.
- Line 9-11: The new NCover summary file is copied to the CCNetArtifactDirectory.
CopyNDependResults
If you look at the NDepend target, you will see notice that the NDepend results are created in a NDependOutFolder. Of these results, we are interested in the NDependResultsFile as it contains the combined metrics for the NDepend run. Unfortunately CC.NET currently is unable to display images as part of the web dashboard so the graphs created by NDepend like Abstractness vs. Instability cannot be displayed. This will be fixed in a future version of CC.NET. In the mean time, you can use this workaround if you absolutely want to include the images into your reports. I have opted to exclude images for now.
1 <Target Name="CopyNDependResults"
2 Condition=" '$(CCNetProject)' != '' ">
3
4 <CreateItem Include="$(CCNetArtifactDirectory)\NDepend\$(NDependResultsFile)">
5 <Output TaskParameter="Include" ItemName="ExistingNDependResults"/>
6 </CreateItem>
7
8 <Delete Files="@(ExistingNDependResults)"/>
9
10 <Copy SourceFiles="$(NDependOutputFolder)\$(NDependResultsFile)"
11 DestinationFolder="$(CCNetArtifactDirectory)\NDepend"
12 ContinueOnError="true"/>
13
14 </Target>
Observations:
- Line 2: We test on the CCNetProject property to determine if the build was invoked via CC.NET.
- Lines 4-6: The existing NDepend results file is deleted.
- Line 9-11: The new NDepend results file is copied to the CCNetArtifactDirectory.
CruiseControl.NET Configuration
The build server is set use the following CC.NET configuration. The config is really self explanatory if you are familiar with the different CruiseControl.NET configuration options.
1 <project name="CodeStatisticsBuild" queue="CCNet.Demo" queuePriority="3">
2 &header;
3 <category>CodeStatistics</category>
4 <artifactDirectory>C:\Projects\CCNet.Demo\Builds\CodeStatisticsBuild\Artifacts</artifactDirectory>
5
6 <triggers>
7 <projectTrigger serverUri="tcp://buildserver.yourdomain.co.za:21234/CruiseManager.rem" project="DeploymentBuild">
8 <triggerStatus>Success</triggerStatus>
9 <innerTrigger type="intervalTrigger" seconds="30" buildCondition="ForceBuild"/>
10 </projectTrigger>
11 </triggers>
12
13 <state type="state" />
14
15 <labeller type="defaultlabeller"/>
16
17 <!-- Remove the log files from the previous build as we are creating a complete new build -->
18 <prebuild>
19 <exec>
20 <executable>clean.bat</executable>
21 <baseDirectory>C:\Projects\CCNet.Demo\Builds\CodeStatisticsBuild</baseDirectory>
22 </exec>
23 </prebuild>
24
25 <tasks>
26 <msbuild>
27 <executable>C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe</executable>
28 <workingDirectory>C:\Projects\CCNet.Demo</workingDirectory>
29 <projectFile>CCNET.Demo.proj</projectFile>
30 <buildArgs>/noconsolelogger /v:normal /p:FxCopDir="C:\Program Files\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop";SolutionName=CCNet.Demo.sln;Configuration=Release;Platform=AnyCPU;DeploymentBuild=false;RunCodeAnalysis=true;Environment=DEV;DocumentationFile=</buildArgs>
31 <targets>BuildAll,CopyCodeAnalysisResults,CodeCoverage,CopyCodeCoverageResults,NDepend,CopyNDependResults</targets>
32 <timeout>3600</timeout> <!-- 1 hour -->
33 <logger>C:\Program Files\CruiseControl.NET\server\ThoughtWorks.CruiseControl.MSBuild.dll</logger>
34 </msbuild>
35 </tasks>
36
37 <publishers>
38 <merge>
39 <files>
40 <file>C:\Projects\CCNet.Demo\Builds\CodeStatisticsBuild\Artifacts\*.CodeAnalysisLog.xml</file>
41 <file>C:\Projects\CCNet.Demo\Builds\CodeStatisticsBuild\Artifacts\CoverageSummary.xml</file>
42 <file>C:\Projects\CCNet.Demo\Builds\CodeStatisticsBuild\Artifacts\NDepend\*.xml</file>
43 </files>
44 </merge>
45
46 <statistics>
47 <statisticList>
48 <firstMatch name="Svn Revision" xpath="//modifications/modification/changeNumber" />
49 <firstMatch name="Coverage" xpath="//coverageReport/project/@coverage" />
50 <firstMatch name="ILInstructions" xpath="//ApplicationMetrics/@NILInstruction" />
51 <firstMatch name="LinesOfCode" xpath="//ApplicationMetrics/@NbLinesOfCode" />
52 <firstMatch name="LinesOfComment" xpath="//ApplicationMetrics/@NbLinesOfComment" />
53 <statistic name='FxCop Warnings' xpath="count(//FxCopReport/Targets/Target/Modules/Module/Namespaces/Namespace/Types/Type/Members/Member/Messages/Message/Issue[@Level='Warning'])" />
54 <statistic name='FxCop Errors' xpath="count(//FxCopReport/Targets/Target/Modules/Module/Namespaces/Namespace/Types/Type/Members/Member/Messages/Message/Issue[@Level='CriticalError'])" />
55 </statisticList>
56 </statistics>
57
58 &pub;
59 </publishers>
60
61 &links;
62 </project>
63
Observations:
- Lines 6-11: The build is set to trigger on successful completion of the DeploymentBuild
- Lines 18-23: Before starting the build, the previous build's log files are removed
- Lines 38-44: The NCover, FxCop and NDepend results are merged into the build results
- Lines 47-55: The Statistics publisher is used to publish some additional metrics - see this post for more details.
Next Steps
Finally! That takes care of all the targets used by the CodeStatisticsBuild. The next post will highlight all the targets required for the CodeDocumentationBuild. Keep watching this space 