Jenkins使い方メモ

Jenkins の使い方をメモする。

#環境
##OS
Windows7 64bit

##Jenkins
1.544

##APサーバ
Tomcat 7.0.42

#インストール
##war ファイルのダウンロード
[Welcome to Jenkins CI! | Jenkins CI](http://jenkins-ci.org/) にアクセスして、 war ファイルをダウンロードする。

##JENKINS_HOME の設定
環境変数 `JENKINS_HOME` を設定する。

この `JENKINS_HOME` には、バージョン管理システムからチェックアウトしてきたファイルなどが保存される。
デフォルトでは、実行ユーザのホームフォルダ以下に `.jenkins` というフォルダが作成され、そこが利用される。

##デプロイ
Tomcat の `webapps` フォルダにダウンロードした war ファイルを配置する。

##動作確認
Tomcat を起動して `http://localhost:8080/jenkins/` にアクセスする（ホストとポートは適宜読み替え）。

![jenkinsインストール直後.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/094e3510-ef4d-bbcd-b920-98cdef03e757.jpeg)

#簡単なプロジェクトを作って Jenkins で CI してみる
次のような構成の簡単なプロジェクトを作って、 Jenkins で CI してみる。

- Java の CUI プログラム
- ビルドは Ant で実施
- ユニットテストは JUnit で作成
- バージョン管理に SVN を使用

##プロジェクトの構成
```test:フォルダ構成
├─build
│      build.xml
│
├─lib
│  ├─runtime
│  │      commons-lang3-3.1.jar
│  │
│  └─test
│          hamcrest-core-1.3.jar
│          junit-4.11.jar
│
├─scripts
│      execute.bat
│
├─src
│  └─sample
│      └─jenkins
│              Main.java
│
└─test
   └─sample
       └─jenkins
               MainTest.java
```

```java:src/sample/jenkins/Main.java
package sample.jenkins;

import org.apache.commons.lang3.StringUtils;

public class Main {
    public static void main(String[] args) {
        String str = args[0];
        int repeat = Integer.parseInt(args[1]);
        System.out.println(repeat(str, repeat));
    }
    
    public static String repeat(String str, int repeat) {
        return StringUtils.repeat(str, repeat);
    }
}
```

```java:test/sample/jenkins/MainTest.java
package sample.jenkins;

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

public class MainTest {
    
    @Test
    public void repeatメソッドにhogeと5を渡したら_hogehogehogehogehoge_が返ってくること() {
        // exercise
        String actual = Main.repeat("hoge", 5);
        
        // verify
        assertThat(actual, is("hogehogehogehogehoge"));
    }
}
```

```xml:build/build.xml
<?xml version="1.0" encoding="Shift_JIS"?>
<project basedir=".">
  <property name="work.dir"        location="work" />
  <property name="classes.dir"     location="${work.dir}/classes" />
  <property name="assemble.dir"    location="${work.dir}/assemble" />
  <property name="src.dir"         location="../src" />
  <property name="test.src.dir"    location="../test" />
  <property name="lib.dir"         location="../lib" />
  <property name="scripts.dir"     location="../scripts" />
  <property name="start.script"    location="${scripts.dir}/execute.bat" />
  <property name="test.report.dir" location="${work.dir}/test-report" />
  
  <property name="src.encoding" value="Shift_JIS" />
  <property name="main.class"   value="sample.jenkins.Main" />
  <property name="test.class"   value="sample.jenkins.MainTest" />
  <property name="jar.name"     value="jenkins-sample.jar" />
  <property name="zip.name"     value="jenkins-sample.zip" />
  
  <path id="runtime.classpath">
    <fileset dir="${lib.dir}">
      <include name="runtime/*.jar" />
    </fileset>
  </path>
  
  <path id="test.classpath">
    <fileset dir="${lib.dir}">
      <include name="test/*.jar" />
    </fileset>
  </path>
  
  <target name="clean" description="Ant タスクで生成された全てのファイルを削除します">
    <delete dir="${work.dir}" />
    <delete>
      <fileset dir="." includes="TEST*" />
    </delete>
  </target>
  
  <target name="compile" description="ソースコードをコンパイルして class ファイルを生成します">
    <mkdir dir="${classes.dir}" />
    
    <javac srcdir="${src.dir}"
           destdir="${classes.dir}"
           encoding="${src.encoding}"
           classpathref="runtime.classpath" />
  </target>
  
  <target name="compile-test" depends="compile" description="テストコードをコンパイルして class ファイルを生成します">
    <mkdir dir="${classes.dir}" />
    
    <javac srcdir="${test.src.dir}"
           destdir="${classes.dir}"
           encoding="${src.encoding}"
           classpathref="test.classpath" />
  </target>
  
  <target name="exec" depends="compile" description="コンパイルしたアプリケーションを実行します">
    <java classname="${main.class}"
          classpath="${classes.dir}"
          classpathref="runtime.classpath">
      <arg value="hoge" />
      <arg value="5" />
    </java>
  </target>
  
  <target name="test" depends="compile-test" description="JUnit のテストを実行します">
    <mkdir dir="${test.report.dir}" />
    
    <junit>
      <classpath location="${classes.dir}" />
      <classpath refid="runtime.classpath" />
      <classpath refid="test.classpath" />
      <test name="${test.class}" todir="${test.report.dir}" />
      <formatter type="plain" extension=".txt" />
    </junit>
  </target>
  
  <target name="jar" depends="test" description="ソースコードをコンパイルして jar ファイルに固めます">
    <jar destfile="${work.dir}/${jar.name}"
         basedir="${classes.dir}"
         excludes="**/*Test.class" />
  </target>
  
  <target name="build" depends="jar" description="プロジェクトをビルドして配布可能な zip ファイルを生成します">
    <mkdir dir="${assemble.dir}/lib" />
    <mkdir dir="${assemble.dir}/bin" />
    
    <copy file="${start.script}" todir="${assemble.dir}/bin" />
    <copy todir="${assemble.dir}/lib">
      <fileset dir="${lib.dir}/runtime" includes="**/*.jar" />
      <fileset file="${work.dir}/${jar.name}" />
    </copy>
    
    <zip destfile="${work.dir}/${zip.name}"
         basedir="${assemble.dir}" />
  </target>
</project>
```

```bat:scripts/execute.bat
@echo off

set BASE_DIR=%~dp0

java -cp "%BASE_DIR%../lib/*" sample.jenkins.Main %*
```

build.xml ファイル書いてて、心底早く Gradle に移るべきだと感じた。。。

##新規ジョブを作成する
Jenkins で上記プロジェクトを CI できるようにする。

- メニューの [新規ジョブ作成] を選択。

- [ジョブ名] を入力し、 [フリースタイル・プロジェクトのビルド] を選択して [OK]。

- [説明] や [オプション] を必要に応じて設定。

- [ソースコード管理] で [subversion] を選択。

 - [リポジトリ URL] を入力（不要なファイルをチェックアウトしているとビルドが遅くなるので、最小限になるようにパスを指定する）。

 - [ローカルモジュールディレクトリ（オプション）] を入力（デフォルトは `%JENKINS_HOME%/workspace/<ジョブ名>`）。

 - [Repository depth] と [Ignore externals] はデフォルト（`infinity` と未選択）。

- [チェックアウト方式] と [リポジトリ・ブラウザ]はデフォルト（`'svn update' を実行` と `（自動）`）。

- [ビルド・トリガ] は、今はとりあえず「チェックなし」で置いておく。

- [ビルド] で [Ant の呼び出し] を選択する。

 - [ターゲット] に実行する Ant タスク名を記述する。今回は `build` タスクを呼び出すので、 `build` とだけ入力。

 - [高度な設定] をクリックして [ビルドファイル] に `./build/build.xml` と入力（デフォルトはルートフォルダの `build.xml` が使用される）。

- [ビルド後の処理] は今は設定しない。

- [保存] をクリック。

ジョブができ上がったら、早速メニューにある [ビルド実行] をクリックする。
ビルドが完了すると、 [ビルド履歴] にビルド結果が表示される。
青丸ならビルドは成功。赤丸ならビルドは失敗。

![_Jenkinsビルド結果.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/b3b9b6df-3f0a-ff8f-cf93-b12a9f38dd61.jpeg)

##定期的にビルドを実行する
ビルドを定期的に実行するように設定する。
設定は、先ほどスルーした [ビルド・トリガ] で行う。

- メニューの [設定] をクリックして、ジョブの設定画面に移動する。

- [ビルド・トリガ] で [定期的に実行] にチェックを入れる。

- [スケジュール] に `H/5 * * * *` と入力（5 分に 1 回実行）。
※入力域横の？をクリックすれば、詳細な入力方法が見られる。

- [保存] をクリック。

あとは 5 分毎にジョブが実行されるか wktk しながら待つ。

##JUnit のテスト結果を集計する
JUnit のテスト結果を確認する場合、デフォルトではジョブ結果の [コンソール出力] を確認するしかない。
分かりにくい上に、テストで失敗していてもビルドプロセス自体がエラー無しで完了していると、ビルドは成功（青丸）になってしまう。

JUnit のテスト結果を集計するようにすることで、テスト結果が見やすくなり、かつテストで失敗があるとビルド結果が黄色になるようにできる。

###XML 形式でテスト結果が出力されるように `build.xml` を修正する
Jenkins にテスト結果を集計させるためには、テスト結果を xml 形式で出力しなければならない。

なので、 `build.xml` のテスト結果出力形式を以下のように変更する。

```xml:変更前
  <target name="test" depends="compile-test" description="JUnit のテストを実行します">
    <mkdir dir="${test.report.dir}" />
    
    <junit>
      <classpath location="${classes.dir}" />
      <classpath refid="runtime.classpath" />
      <classpath refid="test.classpath" />
      <test name="${test.class}" todir="${test.report.dir}" />
      <formatter type="plain" extension=".txt" />
    </junit>
  </target>
```

```xml:変更後
  <target name="test" depends="compile-test" description="JUnit のテストを実行します">
    <mkdir dir="${test.report.dir}" />
    
    <junit>
      <classpath location="${classes.dir}" />
      <classpath refid="runtime.classpath" />
      <classpath refid="test.classpath" />
      <test name="${test.class}" todir="${test.report.dir}" />
      <formatter type="xml" extension=".xml" /> <!-- type を xml に変更 -->
    </junit>
  </target>
```

###Jenkins 上でテスト結果を出力する
設定を始める前に、テスト結果の xml ファイルを Jenkins のワークスペース上に出力しておく必要がある。
テスト結果を xml で出力するように `build.xml` を編集したら、再度 Jenkins 上でビルドを実行してテスト結果ファイルを出力しておく。

###Jenkins の設定を変更する
- [設定] をクリック。

- [ビルド後の処理] で [ビルド後の処理の追加] から [JUnit テスト結果の集計] を選択。

 - [テスト結果 XML] にテスト結果の xml ファイルが出力されるファイルへのパスを入力する（[ワークスペースルート] のリンクを開いて、そこからパスを調べて入力すれば間違えないと思う）。

- [保存] をクリック。

###テスト結果を確認する
ビルドを実行してテストが失敗すると、以下のようにテスト結果が黄色になり、テストが失敗したことがわかる。

![Jenkinsでテスト結果が失敗の場合.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/c34a36ff-bf61-fce1-8725-f82d702839e1.jpeg)

##ビルド結果をメールで通知する
ビルド結果をメールで通知できるように設定する。

###Jenkins の URL を設定する
通知メールには失敗したジョブの URL が貼り付けられる。

デフォルトだと、その URL のホストが `localhost` になっている。
このままだと、メールを受け取ってもリンクを開くことができないので、正しいホスト名になるよう設定を変更する必要がある。

- トップページに移って、 [Jenkins の管理] を選択。

- [Jenkins の位置] の [Jenkins の URL] が `http://localhost:8080/jenkins` となっているので、外部からでも見れるように URL を変更する。

###SMTP サーバの設定をする
メールを送信するのに使用する SMTP サーバの設定をする。
とりあえず Gmail のアカウントを使って送信してみる。

- トップページに移って、 [Jenkins の管理] を選択。

- [システムの設定] を選択。

- [E-mail 通知] を設定する。

 - [SMTP サーバー] に `smtp.googlemail.com` と入力。
 - [E-mail のサフィックス] に `@gmail.com` と入力。
 - [SMTP 認証] にチェックを入れる。
 - [ユーザ名] と [パスワード] に Gmail のメールアドレスとパスワードを入力。
 - [SSL] にチェックを入れる。
 - [SMTP ポート] に `465` を入力。

- [メールを送信して設定を確認] にチェックを入れる。

- [テストメールの宛先] にメールの送信先アドレスを入力して [設定を確認] をクリック。

- [テストメールの宛先] に指定したアドレスにメールが届いたら [保存] をクリック。

###ビルドが完了したらメールで通知するように設定する
- メール通知したいジョブを開き、 [設定] を選択。

- [ビルド後の処理] で [ビルド後の処理の追加] から [E-mail 通知] を選択する。

- [宛先] にメールの送信先アドレスを入力する。

- [保存] をクリック。

###動作確認
わざとビルドが失敗するようにファイルをコミットしてから、ビルドを実行する。

指定したメールアドレスに届いていたら OK。

![ビルド失敗メール.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/6a5a178f-3c07-9d59-1d46-5cc3430377ea.jpeg)

#Gradle を使ったプロジェクトも CI してみる
本命の Gradle を使ったプロジェクトを Jenkins で CI してみる。

##プロジェクトの構成
```text:フォルダ構成
│  build.gradle
└─src
   ├─main
   │  └─java
   │      └─sample
   │          └─jenkins
   │                  Main.java
   └─test
       └─java
           └─sample
               └─jenkins
                       MainTest.java
```

```groovy:build.gradle
apply plugin: 'application'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.apache.commons:commons-lang3:3.1'
    testCompile 'junit:junit:4.11'
    testCompile 'org.hamcrest:hamcrest-library:1.3'
}

mainClassName = 'sample.gradle.Main'

distZip {
    archiveName = 'jenkins-sample.zip'
}
```

※Java の実装は Ant と同じ。

##Jenkins の Gradle プラグインをインストールする
Jenkins から Gradle のビルドファイルを実行できるようにするため、プラグインをインストールする。

- トップページに移って、 [Jenkins の管理] を選択。

- [プラグインの管理] を選択。

- [利用可能] タブを開き、 [フィルター] に `gradle` と入力。

- [Gradle Plugin] の [インストール] にチェックを入れて [再起動せずにインストール] をクリック。

- インストールが完了したら、再び [Jenkins の管理] を選び、次は [システムの設定] を選択する。

- [Gradle] の項目が追加されているので、 [Gradle 追加] をクリックする。

 - [name] に任意の名前を入力。
 - 既に Gradle はインストール済みなので、 [自動インストール] のチェックを外す。
 - [GRADLE_HOME] に、 Gradle のインストールフォルダのパスを入力。

- [保存] をクリック。

##ジョブを追加する
Gradle プロジェクトを Jenkins にジョブとして登録する。
基本的な手順は Ant のときと同じ。違うのは、 [ビルド] の [ビルド手順の追加] で `Invoke Gradle script` を選択する点。

- 先ほど Jenkins に登録した Gradle を使うなら、 [Invoke Gradle] を選択する。 Gradle Wrapper を使用する場合は、そちらを選択する。今回は、 [Invoke Gradle] を選択。

- [Tasks] に `test distZip` と入力。

- それ以外はデフォルト（空）のまま [保存] をクリック。
※`build.gradle` が `%JENKINS_HOME%/workspace/<ジョブ名>` の直下にない場合は、 [Build File] を設定すること。

##Gradle の 起動ファイルを修正する
このままビルドを実行すると、次のようなエラーが発生する。

```text:
Could not load Logmanager "org.apache.juli.ClassLoaderLogManager"
java.lang.ClassNotFoundException: org.apache.juli.ClassLoaderLogManager
	at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
（以下略）
```

どうやら、 Tomcat の catalina.bat で `JAVA_OPTS` という環境変数に `-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager` という設定をしているのだが、この設定が Gradle の起動ファイルにまで伝わってしまっているのが原因らしい。

Gradle のクラスパス上には `org.apache.juli.ClassLoaderLogManager` が無いので、 `ClassNotFoundException` が発生してしまうという残念な状態。

```bat:catalina.bat
（前略）

if not "%LOGGING_MANAGER%" == "" goto noJuliManager
set LOGGING_MANAGER=-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
:noJuliManager
set JAVA_OPTS=%JAVA_OPTS% %LOGGING_MANAGER%

（後略）
```

```bat:gradle.bat
（前略）

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.launcher.GradleMain %CMD_LINE_ARGS%

（後略）
```

対策として、 gradle.bat を直接書き換えて、実行前に `JAVA_OPTS` に空を設定する方法があるらしい。

> Anyway, I modified the gradle.bat file to unset JAVA_OPTS (set JAVA_OPTS=) and that takes care of this issue - at least as a workaround.

[[GRADLE-1245] Hudson and org.apache.juli.ClassLoaderLogManager - Gradle Issues](http://issues.gradle.org/browse/GRADLE-1245)

```bat:修正後のgradle.bat
REM Tomcat にデプロイした Jenkins から呼ばれたときに Tomcat の JAVA_OPTS が伝播するのを防ぐ
set JAVA_OPTS=

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.launcher.GradleMain %CMD_LINE_ARGS%
```

Tomcat と Gradle と Jenkins のそれぞれで同様のバグ報告がされてて若干カオスな感じです。

- [Tomcat のバグ報告](https://issues.apache.org/bugzilla/show_bug.cgi?id=54601)
- [Gradle のバグ報告](http://issues.gradle.org/browse/GRADLE-1245)
- [Jenkins のバグ報告](https://issues.jenkins-ci.org/browse/JENKINS-7702)

##動作確認
ようやくビルドが実行できる。

- ジョブのメニューから [ビルド実行] を選択して、ビルドが実行できることを確かめる。

#参考
- [Jenkins実践入門](http://www.amazon.co.jp/Jenkins%E5%AE%9F%E8%B7%B5%E5%85%A5%E9%96%80-%EF%BD%9E%E3%83%93%E3%83%AB%E3%83%89%E3%83%BB%E3%83%86%E3%82%B9%E3%83%88%E3%83%BB%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%82%92%E8%87%AA%E5%8B%95%E5%8C%96%E3%81%99%E3%82%8B%E6%8A%80%E8%A1%93-WEB-DB-PRESS-plus/dp/4774148911)
- [JenkinsでGradleを使おう - しおしおの雑記帳](http://siosio.hatenablog.com/entry/2012/05/26/220858)