CCNETでNDependのレポートを表示(^o^)

Vilの開発が停滞しているようで永らくコードメトリクスツールを使用していなかったのだが、id:toshio-hさんから以前に教えていただいたNDependを試してみた。全てを正常に動作させるまでには少し苦労するが強力なレポートが出力できることを考えれば苦労の甲斐はあるだろう。

まずは、NDepend 2.0 FinalをダウンロードしてProgram Files直下に展開する。

次に、ここを参考にしてNDepend用のプロジェクトファイル(XML)を作成する。この際にプロダクトアセンブリだけを対象にして、テストアセンブリは除外することをお奨めする。作成したNDepend用のプロジェクトファイル(NDependProject.xml)はVS 2005のソリューションに追加しておく(常時結合時にバージョン管理ツールから取得するため)。

次に、NAntのビルドスクリプトにNDependを実行するためのターゲットを追加する。NDepend用のプロジェクトファイルにはレポートの出力先とアセンブリの参照先が絶対パスで記述されているので、ビルドスクリプトでファイルコピー時にパスを適切に書き換えてやる。NDepend 2.0 Finalでは相対パスがサポートされているので、パスを変更する必要はない。NDependにはNAnt用のタスクも付属しているが、コマンドプロンプトが別途立ち上がってしまうので、execタスクでNDepend.Console.exeを実行することにした。

<property name="build.log.dir" value="${project::get-base-directory()}/build/log" />
<property name="ndepend-console.exe" value="${programfiles.dir}\NDepend_2_0_4_640\NDepend.Console.exe" />
...

<!-- 各種レポートの収集 -->
<target name="reporting" depends="build">
  ...
  <call target="ndepend" />
  ...
</target>

<!-- コードメトリクス レポートの収集 -->
<target name="ndepend" >
  <exec program="${ndepend-console.exe}">
    <arg value="NDependProject.xml" />
  </exec>
</target>

次に、CCNETのccnet.configを以下のように編集する。

<project>
  ...
  <publishers>
    <merge>
      <files>
        ...
        <file>NDependOut\ApplicationMetrics.xm3l</file>
        <file>NDependOut\AssembliesBuildOrder.xml</file>
        <file>NDependOut\AssembliesDependencies.xml</file>
        <file>NDependOut\AssembliesMetrics.xml</file>
        <file>NDependOut\CQLResult.xml</file>
        <file>NDependOut\InfoWarnings.xml</file>
        <file>NDependOut\NDependMain.xml</file>
        <file>NDependOut\TypesDependencies.xml</file>
        <file>NDependOut\TypesMetrics.xml</file>
        </files>
    </merge>
  </publishers>
</project>

次に、\Program Files\NDepend_2_0_4_640\CruiseControl.NET\ndependreport-ccnet.v2.xslをC:\Program Files\CruiseControl.NET\webdashboard\xslにコピーする。

あとはdashboard.configを以下の様に編集してやる。

<dashboard>
  ...
  <plugins>
    ...
    <buildPlugins>
      <buildReportBuildPlugin>
        <xslFileNames>
          ...
          <xslFile>xsl\ndependreport-ccnet.v2.xsl</xslFile>
        </xslFileNames>
      </buildReportBuildPlugin>
      <buildLogBuildPlugin />
      ...
      <xslReportBuildPlugin description="NDepend Report" actionName="NDependBuildReport" xslFileName="xsl\ndependreport-ccnet.v2.xsl" />
      ...
    </buildPlugins>
  </plugins>
</dashboard>

これでようやくレポートがCCNETで出力されるようになる。

#NDpend 2.0 FinalではCode Query Language(COL)を使用したレポート出力はProfessional Editionしかサポートされていないようだ。

ただし、VisualNDepend View / Assemblies Abstracness vs. Instability / Assemblies Dependencies Diagramでそれぞれリンク切れが発生して画像が表示されない。そこで、以下の追加作業が必要になる。

まず、下記の内容でimage.ashxというファイルを作成し、\Program Files\CruiseControl.NET\webdashboardに保存する。

このファイルによって、プロジェクト名(TddStudy)、ビルドラベル(20070130010904)、画像ファイル名(AbstractnessVSInstability.png)というパラメータでhttp://localhost/ccnet/image.ashx?project=TddStudy&label=20070130010904&name=AbstractnessVSInstability.pngというようなリクエストがあった場合、指定した画像ファイルの保存先(この場合はC:\Projects\ccnet-integration\TddStudy.Daily\build\artifacts\20070130010904)から対象のイメージが取得されて出力されるようになる。

<%@ webhandler language="C#" class="ImageHandler" %>
using System;
using System.Configuration;
using System.Data; 
using System.Data.SqlClient; 
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Net;
using System.Web;using System.Web.Caching;

public class ImageHandler : IHttpHandler
{
  public bool IsReusable
  { 
    get { return true; }
  }

  public void ProcessRequest(HttpContext ctx)
  {
    try
    {
      string name = ctx.Request.QueryString["name"];
      string project = ctx.Request.QueryString["project"];
      string label = ctx.Request.QueryString["label"];
      string cacheKey = string.Format("{0}|{1}|{2}", name, project, label); 

      if (name != null && name.Length > 0)
      {
        Byte[] imageBytes = null; 
        object cachedImageBytes = ctx.Cache.Get(cacheKey); 

        if (cachedImageBytes != null)
        {
          imageBytes = cachedImageBytes as byte [];
        }
        else
        { 
          string imagePath = ConfigurationSettings.AppSettings["imagePath"];

          if (imagePath == null || imagePath.Length == 0)
          {
            imagePath = @"C:\Projects\ccnet-integration\{0}.Daily\build\artifacts\{1}";
          }
          
          imagePath = string.Format(imagePath, project, label); 

          if (!Path.IsPathRooted(imagePath))
          {
            imagePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, imagePath);
          }

          try
          { 
            using (FileStream fs = new FileStream(Path.Combine(imagePath, name), FileMode.Open, FileAccess.Read))
            {
              using(Image inputImage = Image.FromStream(fs))
              {
                using(Image outputImage = new Bitmap(inputImage))
                { 
                  using(MemoryStream stream = new MemoryStream())
                  {
                    outputImage.Save(stream, ImageFormat.Jpeg);
                    imageBytes = stream.GetBuffer();
                  }
 
                  ctx.Cache.Add(cacheKey, imageBytes, null,
                    DateTime.MaxValue, new TimeSpan(2, 0, 0),
                    CacheItemPriority.Normal, null); 
                }
              }
            }
          }
          catch (Exception)
          {
          }
        } 

        ctx.Response.Cache.SetCacheability(HttpCacheability.Public);
        ctx.Response.ContentType = "image/jpg";
        ctx.Response.BufferOutput = false;
        ctx.Response.OutputStream.Write(imageBytes, 0, imageBytes.Length);
      }
    }
    finally
    {
      ctx.Response.End();
    }
  }
}

via:Integrating Images Into Custom CruiseControl.NET Build Reports

次に、C:\Program Files\CruiseControl.NET\webdashboard\xsl\ndependreport-ccnet.v2.xslの内容を以下の様に修正する

...
<img>
  <!-- <xsl:attribute name="src">VisualNDependView.png</xsl:attribute> -->
  <xsl:attribute name="src">/ccnet/image.ashx?project=<xsl:copy-of select="$project" />&label=<xsl:copy-of select="$label" />&name=VisualNDependView.png</xsl:attribute>
</img>
...

<img>
  <!-- <xsl:attribute name="src">AbstracnessVSInstability.png</xsl:attribute> -->
  <!-- 上記画像ファイル名は間違っているので注意が必要(Abstractnessの2つ目のtが抜けている) -->
  <xsl:attribute name="src">/ccnet/image.ashx?project=<xsl:copy-of select="$project" />&label=<xsl:copy-of select="$label" />&name=AbstractnessVSInstability.png</xsl:attribute>
</img>
...

<img>
  <!-- <xsl:attribute name="src">ComponentDependenciesDiagram.png</xsl:attribute> -->
  <xsl:attribute name="src">/ccnet/image.ashx?project=<xsl:copy-of select="$project" />&label=<xsl:copy-of select="$label" />&name=ComponentDependenciesDiagram.png</xsl:attribute>
</img>
...

次にNAntのビルドスクリプトに以下の内容を追加する。

<tstamp property="build.datetime" pattern="yyyyMMddhhmmss"/>
...

<property name="nant.onsuccess" value="OnSuccess" />
<property name="build.artifacts.dir" value="${project::get-base-directory()}/build/artifacts" />
...

<!-- 各種レポートの収集 -->
<target name="reporting" depends="build">
  ...
  
  <mkdir dir="${build.artifacts.dir}/${build.datetime}" />
  <copy todir="${build.artifacts.dir}/${build.datetime}">
    <fileset basedir="NDependOut">
      <include name="*.png" />
    </fileset>
  </copy>
</target>

<!-- ビルド成功時のイベント -->
<target name="OnSuccess">
  <echo file="${build.log.dir}/buildinfo.xml" append="false" failonerror="false">
    <![CDATA[<buildproject>${project::get-name()}</buildproject><buildlabel>${build.datetime}</buildlabel>]]>
  </echo>
</target>

ここでは以下のような処理を行っている。

  • NDependの最新レポート出力先から画像ファイルをビルドラベル毎に生成される新しいフォルダへコピーする。
  • nant.onsuccessプロパティを使用して、ビルド成功時にndependreport-ccnet.v2.xslで使用される変数(プロジェクト名とビルドラベル)に格納するための値をbuildinfo.xmlに出力する。

最後にccnet.configに以下の行を追加してbuildinfo.xmlをビルドログにマージしてやれば、画像が表示されるようになる。

<project>
  ...
  <publishers>
    <merge>
      <files>
        <file>build\log\buildinfo.xml</file>
        ...
      </files>
    </merge>
  </publishers>
</project>



#2007/02/05 NDepend 2.0 Final用に内容を修正した。