TFSでJavaのビルド環境構築 in 2012 その9:各種チェックのエラー有無判定とJarファイル作成

今まで行ってきた各種チェックのエラー有無判定とJarファイル作成部分についてです。

何も考えずにJUnit/CheckStyle/FindBugsを使うと
 ・JUnit/CheckStyleでエラー検知したら、それ以降の処理が実行されなかった
  →「FindBugsのレポートが作られない」とか。
 ・FindBugsでエラー検知したのに、ビルドエラーにできない
といったことになります。
これだと、エラーを見逃してしまうことになるので、
 ・エラーチェックは全て実行する
 ・何かしらエラーを検知した場合にはビルドをエラーにする
ようにしないといけません。

そのために、今までの各種チェックタスクで
 ・エラーを検知しても処理を継続する
 ・エラーを検知した場合のみ、プロパティをセットする
というオプションを指定していました。
処理の継続はチェック処理のタスク内で完結していますが、「何かしらエラーを検知した場合にはビルドをエラーにする」ために、プロパティがセットされていればAntをエラー終了させる処理を追加します。

027

最初の「echoproperites」タスクは、ビルドログファイルにエラーを検知したかどうかを簡単に出力するために入れています。「*.failed」が存在した場合、こんな感じで出力されます。

028

最初の2行はヘッダ部、残り3行がプロパティ名とセット内容になります。
全て正常であれば、ヘッダ部しか出力されません。

次の「condition」タスクで、Antをエラー終了させるかどうか判定しています。
この場合、junit.failed/findbugs.error.failed/findbugs.warnings.failed/findbugs.warnings.failedのどれかがセットされていれば、metrics.failedをセットすることにしています。
最後に、「fail」タスクでmetrics.failedがセットされていればAntをエラー終了させています。
今回は、この後にJarファイル作成(make-jar)を動かすようにしていますので、エラーがあったときにはJarファイルを作成しません。

残りはJarファイル作成部分です。

029

特別なことはしていません。
・出力先をBinaries配下に指定し、TFSのビルド画面にある「格納フォルダを開く」から参照できるようにする
・マニフェストファイルに、ビルド番号をセットする
 こんな感じでセットされます。

030

長々と書きましたが、TFS+Javaでのビルド自動化の基礎はこんな感じかなと思ってます。

気が付いた方がいらっしゃるかもしれませんが、今回の内容ではBuild Extensionsを追加するだけでTFS側に対する特別な設定/定義ファイルの変更ありません。
フォルダ構成に気を付ければ、Antを作成するだけでTFSでもこれだけできるということです。
なお、TFSのビルド定義からAntのターゲットを指定したいという方は「TFSでのJavaビルド環境作成:AntのTarget指定方法」を参考にしてみてください。

最後ですが、「VS2012 Premium/Ultimate+TFS 2012」だとちょっとした設定だけでこれだけのことができるので、どれだけ強力な環境かということも思い知りました。
(ようは.NETで開発したい orz)

TFSでJavaのビルド環境構築 in 2012 その8:FindBugs

FindBugs部分についてです。

026

ポイントですが、
 ・レポートの出力先をBinaries配下にする(outputFile="${findbugs.result.dir}/FindBugsResult.html")
 ・エラーを検知したときのみに、プロパティをセットする
  errorProperty="findbugs.error.failed"
  warningsProperty="findbugs.warnings.failed"

あと、その2でも書きましたが、サーバのOS環境変数に「LANG=ja_JP.UTF-8」が設定されていないと、レポートの日本語部分が文字化けします。

次が最後ですが、今まで行ってきた各種チェックのエラー有無判定とJarファイル作成部分についてです。

TFSでJavaのビルド環境構築 in 2012 その7:カバレッジ取得準備~テスト・カバレッジレポート作成

カバレッジ取得準備~テスト・カバレッジレポート作成部分についてです。

最初は、カバレッジ取得準備部分です。

022

Cobertura独自の取得処理を追加したクラスファイルの出力先を個別に指定する(todir=${cobertura.instrument.dir})だけです。後は普通に記述してください。^ ^;

次はJUnitの実行部分です。

023

基本的には普通に記述すれば大丈夫ですが、
○テスト結果ファイルについて
 ・出力ファイルの形式をxmlにする
 ・出力先をBinaries配下にする
  こうすると、TFSのビルド結果に反映できるようになります。
  ※デフォルトで問題ないのですが、出力ファイル名が「TEST-*.xml」に従わないと、TFSのビルド結果に反映されません。
○処理継続について
 CheckStyleと同様に
 ・テストエラーを検知しても処理を継続させる(haltonfailure="off")
 ・テストエラーを検知したときのみ、プロパティをセットする(failureproperty="junit.failed")
を指定します。

さらに、テスト結果レポートの作成についてです。

024

レポートの出力先をBinaries配下にしておき、後でTFSの格納フォルダから参照できるようにしておきます。

次はカバレッジ取得レポートの作成です。

025

出力先をBinaries配下にすることぐらいで、基本通りで問題ありません。

次は、FindBugs部分についてです。

TFSでJavaのビルド環境構築 in 2012 その6:CheckStyle

CheckStyle部分についてです。

021

「src」配下にあるファイルを対象にして、チェックを実行します。
(テスト用ソースが格納される「testsrc」は対象になりませんので、テスト用ソースがいくら汚くても問題ありませんw)

ポイントですが、
 ・failOnViolation="false"
  エラーを検知しても処理を継続する(Antを終了しない)
 ・failureProperty="checkstyle.failed"
  エラーを検知したときだけ、指定したプロパティをセットする
 ・レポートの出力先(style out)を「Binaries」配下にする
  パス定義として「${checkstyle.result.dir}」を「${BinariesRoot}/CheckStyleResult」としています
の3つです。
「checkstyle.failed」は、後ほどエラー検知有無で使用します。

次はカバレッジ取得準備~テスト・カバレッジレポート作成部分についてです。

TFSでJavaのビルド環境構築 in 2012 その5:ビルド処理(javacタスク)

まず、ビルドタスクの順番はこうしました。

019

ソースのビルド(build)→テストソースのビルド(testbuild)→CheckStyle(checkstyle)→カバレッジ取得準備(instrument-cobertura)→JUnitテスト実行&レポート作成(junitreport)→カバレッジレポート作成(coverage)→FindBugs実行(findbugs)→エラー有無確認(checkmetrics)→Jarファイル作成(make-jar)

さて、最初のソースのビルド(build)/テストソースのビルド(testbuild)についてです。

020

元々のソースに対する出力先は「bin」配下に、テストソースに対する出力先は「testbin」に分離し、最後にJarファイルを作成する際の対象を「bin」配下のファイルだけにすればいいようにしています。

次はCheckStyle部分についてです。

TFSでJavaのビルド環境構築 in 2012 その4:パス定義

build.xmlの先頭には各種パスの定義を記載していますが、まずは全体のフォルダ構成はこうしました。

016

今回、TFSのプロジェクト名を「JavaProject1」、ビルド定義名を「CI-Build」にしています。

TFSのビルドを実行すると、「Sources」配下にソースファイルなどが展開され、ワークフォルダを作成しつつビルドが進み、最終生成物を「Binaries」配下に格納するという感じになります。

build.xmlのフォルダパス定義部分です。

017

「~_HOME」はOS環境変数を参照しています。
「${BinariesRoot}」は、TFS上での最終生成物の格納先フォルダを示す定義で、TFSのビルドサービスから実行されると自動的に設定されます。(今回は「\Build\1\JavaProject1\CI-Build\Binaries」がセットされます)

基本的には、HOMEの設定と、生成物の出力先の設定を行ってます。
1つ、「cobertura.instrument.dir」についてですが、カバレッジ取得のため、coberturaがオリジナルソースに独自の処理を追加するのですが、追加されたクラスファイルを格納するためのフォルダを指定しています。

次は各処理で指定するクラスパス定義とFindBugs/CheckStyle/Coberturaタスクを使うためのtaskdefです。

018

基本的には、各処理のマニュアルに記載されている通りです。

次はソースのビルド処理(javacタスク)部分についてです。

TFSでJavaのビルド環境構築 in 2012 その3:build.xml

とりあえず、今回使ったbuild.xmlをそのまま置いておきます。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project basedir="." default="buildall" name="JavaProject1">
    <property environment="env"/>
    <property name="debuglevel" value="source,lines,vars"/>
    <property name="target" value="1.6"/>
    <property name="source" value="1.6"/>
    <property name="jar.output.dir" value="${BinariesRoot}/Jar"/>
    <property name="junit.home" value="${env.JUNIT_HOME}"/>
    <property name="junit.result.dir" value="${BinariesRoot}/JUnitResult"/>
    <property name="findbugs.home" value="${env.FINDBUGS_HOME}"/>
    <property name="findbugs.result.dir" value="${BinariesRoot}/FindBugsResult"/>
    <property name="checkstyle.home" value="${env.CHECKSTYLE_HOME}"/>
    <property name="checkstyle.result.dir" value="${BinariesRoot}/CheckStyleResult"/>
    <property name="cobertura.home" value="${env.COBERTURA_HOME}"/>
    <property name="cobertura.instrument.dir" value="instrument-cobertura"/>
    <property name="cobertura.result.dir" value="${BinariesRoot}/CoberturaResult"/>
    <property name="log4j.home" value="${env.LOG4J_HOME}"/>

    <path id="JavaProject1.classpath">
        <pathelement location="bin"/>
        <pathelement location="testbin"/>
    </path>
<path id="cobertura.classpath">
     <fileset dir="${cobertura.home}">
         <include name="cobertura.jar" />
         <include name="lib/**/*.jar" />
     </fileset>
</path>
    <path id="junit.classpath">
        <pathelement location="${cobertura.instrument.dir}"/>
     <path refid="JavaProject1.classpath"/>
        <pathelement location="testbin"/>
        <pathelement location="${junit.home}/junit.jar"/>
     <path refid="cobertura.classpath"/>
    </path>

<taskdef name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask"
   classpath="${findbugs.home}/lib/findbugs-ant.jar"/>
<taskdef resource="checkstyletask.properties"
   classpath="${checkstyle.home}/checkstyle-5.6-all.jar"/>
<taskdef classpathref="cobertura.classpath" resource="tasks.properties"/>

<target name="init">
        <mkdir dir="bin"/>
        <copy includeemptydirs="false" todir="bin">
            <fileset dir="src">
                <exclude name="**/*.launch"/>
                <exclude name="**/*.java"/>
            </fileset>
        </copy>
    </target>

<target name="buildall"
  depends="build,testbuild,checkstyle,instrument-cobertura,junitreport,
     coverage,findbugs,checkmetrics,make-jar"/>

<target name="build" depends="init">
        <echo message="${ant.project.name}: ${ant.file}"/>
        <javac debug="true" debuglevel="${debuglevel}" destdir="bin"
          includeantruntime="false" source="${source}"
          target="${target}" encoding="utf-8">
            <src path="src"/>
            <classpath refid="JavaProject1.classpath"/>
        </javac>
    </target>

<target name="testbuild" depends="build">
        <mkdir dir="testbin"/>
        <javac debug="true" debuglevel="${debuglevel}" destdir="testbin"
          includeantruntime="false" source="${source}"
          target="${target}" encoding="utf-8">
            <src path="testsrc"/>
            <classpath refid="JavaProject1.classpath"/>
         <classpath path="${junit.home}/junit.jar"/>
        </javac>
    </target>

<target name="make-jar" depends="build">
  <mkdir dir="${jar.output.dir}"/>
  <jar jarfile="${jar.output.dir}/JavaProject1.jar" basedir="bin">
   <manifest>
    <attribute name="Build-Id" value="${BuildNumber}" />
   </manifest>
  </jar>
</target>

    <target name="junit" depends="build">
        <mkdir dir="${junit.result.dir}"/>
        <junit fork="yes" printsummary="withOutAndErr" haltonfailure="off"
    showoutput="true" failureproperty="junit.failed">
         <sysproperty key="net.sourceforge.cobertura.datafile" file="cobertura.ser"/>
            <classpath refid="junit.classpath"/>
            <formatter type="xml"/>
         <batchtest todir="${junit.result.dir}">
          <fileset dir="testsrc">
           <include name="**/*Test.java" />
          </fileset>
         </batchtest>
        </junit>
    </target>

    <target name="junitreport" depends="junit">
        <junitreport todir="${junit.result.dir}">
            <fileset dir="${junit.result.dir}">
                <include name="TEST-*.xml"/>
            </fileset>
            <report format="frames" todir="${junit.result.dir}"/>
        </junitreport>
    </target>

    <target name="findbugs" depends="build">
        <mkdir dir="${findbugs.result.dir}"/>
  <findbugs home="${findbugs.home}"
            output="html"
            outputFile="${findbugs.result.dir}/FindBugsResult.html"
      errorProperty="findbugs.error.failed"
      warningsProperty="findbugs.warnings.failed">
   <sourcePath path="src" />
   <class location="bin" />
  </findbugs>
    </target>

<target name="checkstyle">
        <mkdir dir="${checkstyle.result.dir}"/>
  <checkstyle config="${checkstyle.home}/sun_checks.xml" failOnViolation="false"
     failureProperty="checkstyle.failed">
   <fileset dir="src" includes="**/*.java" />
   <formatter type="xml" tofile="CheckStyleResult.xml" />
  </checkstyle>
  <style in="CheckStyleResult.xml"
    out="${checkstyle.result.dir}/CheckStyleResult.html"
    style="${checkstyle.home}/contrib/checkstyle-simple.xsl" />
</target>

<target name="instrument-cobertura">
        <mkdir dir="${cobertura.instrument.dir}"/>
  <cobertura-instrument todir="${cobertura.instrument.dir}">
      <ignore regex="org.apache.log4j.*" />
   <fileset dir="bin">
    <include name="**/*.class"/>
   </fileset>
  </cobertura-instrument>
</target>

<target name="coverage">
  <cobertura-report srcdir="src" destdir="${cobertura.result.dir}"/>
  <cobertura-report srcdir="src" destdir="${cobertura.result.dir}" format="xml"/>
</target>

<target name="checkmetrics">
  <echoproperties>
   <propertyset>
    <propertyref regex=".*.failed"/>
   </propertyset>
  </echoproperties>

  <condition property="metrics.failed"><or>
   <isset property="junit.failed"/>
   <isset property="findbugs.error.failed"/>
   <isset property="findbugs.warnings.failed"/>
   <isset property="checkstyle.failed"/>
  </or></condition>

  <fail if="metrics.failed" message="各種チェックでエラーがありました。さぁ、とっとと片づけましょう!"/>
</target>
</project>

通常(?)、各種ワークフォルダを削除する処理が入りますが、TFSからビルド実行した場合、TFS側で基底フォルダを削除後新規作成しますので、build.xml内では不要です。

「オンラインストレージ使え」とか聞こえそうですが、会社からは使えないんです^ ^;

個別の内容説明について次から書きます。

TFSでJavaのビルド環境構築 in 2012 その2:サーバ環境とプロジェクト構成

サーバ環境ですが、
 Windows Server 2012
 Team Foundation Server 2012
 Microsoft Visual Studio Team Foundation Server 2012 Build Extensions(11.0.50912.0)
 JDK 1.7
 Ant 1.8.4
 JUnit 4.1
 Cobertura 1.9.4.1
 FindBugs 2.0.1
 CheckStyle 5.6
 Log4j 1.2.17
を使いました。
基本的にはインストール後、環境変数にそれぞれのHOME(JAVA_HOME、JUNIT_HOMEなど)を定義するのですが、1つだけ追加で設定するものがあります。
追加設定するのは、「LANG=jaJP.UTF-8」です。
これを設定しないと、FindBugsのレポート結果に出力される日本語がちゃんと表示されません。(というか、作成されるレポートがShift-JISで作成され、ヘッダ部にエンコード情報が記載されていないので文字化けとなります)

プロジェクト構成はこんな感じです。
Eclipseから

014

そもそものソースは「src」配下に、JUnitテスト用ソースは「testsrc」配下に入れてみました。

TFSのソース管理エクスプローラーから

015

「.tfignore」は何?というかたは、こちらをどうぞ。
MS長沢さんのブログ
Team Foundation Service Preview で Java / JUnit の継続的インテグレーションを行う
(この機能、密かに欲しかったんですw)

次は、build.xmlの内容についてです。

TFSでJavaのビルド環境構築 in 2012 その1:概要

TFS2010での環境構築は以前に書きましたが、改めてTFS2012で構築してみました。
ただ、同じものを再構築しても面白くないので、
 ・FindBugsだけじゃなく、JUnitもCobertura(JCoverage)もCheckStyleも動かす
 ・チェック実行は全部行うが、どれかでエラーが検知されたらビルドをエラーとする
  「JUnitでエラー検知したら、FindBugsのチェックが実行されない」なんてことはありません。
 ・ビルド結果からの直接リンクは使わない(TFServiceで使えないので)
という感じで変更しました。

まずはどう動くかですが、
○正常時
 ビルド結果はこんな感じです

001

JUnitの実行結果がちゃんと表示できています。
「格納フォルダーを開く」でビルドで生成したファイルの内容を確認します。

002

JUnit

003

FindBugs

004

Cobertura

005


006

CheckStyleは次(エラー検知時)に載せます。
(ソースが汚くエラー消し込みできなかったので、正常時のスナップショット取るときはCheckStyle通しませんでした<(_ _)>)

○エラー検知時
「ビルドに失敗しました」となってます。

007

JUnitのテストエラーがちゃんと反映されています。
「ログファイルの表示」でビルドログを確認してみます。

008

「checkmetrics」のところに、どれでエラーを検知したかを追加しています。
(今回はCheckStyle、FindBugs、JUnitの全部でエラーを出しています)
エラーを検知したので、最終生成物のJarファイルは作成していません。

009

それぞれのチェック結果も出力できています。
JUnit

010

FindBugs

011

Cobertura

012

CheckStyle

013

これぐらいのことができれば、仕事で環境構築する際にも最低限使える内容かなと思ってます。
次はサーバ環境とプロジェクト構成についてです。

TFSサーバはスペックの良いものを使用しましょう

確認用としてサーバ環境を構築し、チームプロジェクトを作ってみようとしたところ、こんなエラー...。

201

「タイムアウト」...orz
しかも「ロールバックしようとしましたが、成功しませんでした」と出てます><
SharePoint入れたのに、メモリをケチりすぎたみたいです...。

「困った」ということで、まずはログを確認してみます。
サーバの管理コンソールで「TFS構成」のログをのぞいてみます。
(「TFS構成」のレコードをダブルクリックすると開けます)

202

下の方から追っかけていくと、「ロールバック中です。」というログがありました。

203

推定ロールバック完了率100%と言いながら、そのあとの数値が「161 / 169」となっているので、一部ロールバック処理がタイムアウトしたと判断されたようです。

どうしようと思いながら、ログの続きを確認すると、

204

「removing all items.(169 / 169)」とか「Node returned:Success」とか出力されています。
これだと、タイムアウト後も処理をロールバックを継続していたように見えます。

「物は試し」で、もう一度チームプロジェクトの作成を実行したところ、正常に作成できました。

ちょっとびっくりしましたが、結論は「サーバはケチるな!」です。
SQL ServerがDiskを、SharePointがメモリを相当消費しますし、ビルドも同一マシンで行うのであれば、さらにリソースを消費しますので、結構スペックを必要とします。
チームプロジェクトがギリギリ作れたにしても、リソースが足りないとビルド/自動テストが遅くなり、「時間を短縮するために自動化したのに短縮できない」という残念な状況になりますので、サーバはスペックの良いものを使用しましょう。

ちなみに、今回は仮想マシンで構築していたので、
 ・仮想ディスクをHDDからSSDに移動
 ・割り当てメモリを4GBから10GBに変更
したところ、数分で作成が完了しました。最初からこうしておけばよかったorz