読者です 読者をやめる 読者になる 読者になる

Java7/Java8のデュアルビルド&自動テスト - Jenkinsで簡単自動化②

Jenkins

 

こんにちは、2014年新卒エンジニアの玉川です。

今回のエンジニアブログは怠惰のすゝめ。今更聞けない!? Jenkins・Gradle・Githubで簡単自動化①の続きとして、Java7/Java8のデュアルビルド環境構築およびGitHub・ChatWorkによる連携により、実用的なCI環境を実現します。

 

Jenkinsのジョブ構成

今回はJava8とJava7のデュアルビルド環境をJenkinsで構築します。将来的にJava8化を行いますが、現状Java8に未対応のライブラリが多く存在しているため、Java7でビルドすることで未対応部分を補うための処置です。こういった場合、本来であればプロジェクトを分けるべきかもしれませんが、大人の事情によりプロジェクトを1つで管理しなければならないケースもあり、このようなケースを想定し、Jenkinsでの環境構築を行います。

 

今回のケース

同一プロジェクト内に、Java7でテスト&ビルドする必要があるbatchファイルのようなものとJava8でビルド可能なTomcatプロジェクトが含まれているという少し極端な例です。

Job-1ではGitHubのPull Requestを検知し、Pull Requestがあった際に、Job-2及びJob-3に分岐します。

Job-2ではJDK7/Tomcat7にてビルド&テストを行い、Jarファイルを出力します。

Job-3ではJDK8/Tomcat8にてビルド&テストを行い、Warファイルを出力します。

Job-4ではJob-2及びJob-3が成功したら、結果を出力します。

 

GitHubリポジトリへのフック

GitHubリポジトリのpushをJenkinsでキャッチするには大きく分けて2つの方法があります。

 

 

 

 

GitHub Hook

前者はGitHubにJenkinsのURLを登録し、Pushがあった際にそのURLにPushされた情報をPostすることで、Jenkinsジョブが動きます。利点はリアルタイムでジョブが実行される点ですが、Jenkinsサーバーが社外からのアクセスを認めていない環境にある場合に利用できない点が欠点です。

後者はJenkinsに定期的にGitHubリポジトリを監視してもらうことで、Pushを検知します。前者の欠点としてあげた環境でも動作しますが、定期的に監視し、その際にPushされていたらジョブを実行するという処理になるため、リアルタイムではありません。

今回は社内サーバー環境を想定していますので、後者の方法を利用します。

 

Jenkinsのシステム設定

JDKのインストール

事前にJenkinsサーバーにはJDK1.7とJDK1.8の両方をインストールし、Jenkinsのシステム設定に両方のJDKを追加する必要があります。

JDKの設定

プラグインのインストール

事前にJenkinsサーバーにはJDK1.7とJDK1.8の両方をインストールし、Jenkinsのシステム設定に両方のJDKを追加する必要があります。さらに、以下のプラグインが必要です。

 

Git Plugin

GitリポジトリをSCMとして利用できるようにします。

GitHub Plugin

GitHubプロジェクトをJenkinsで利用可能にします。

GitHub pull request builder plugin

プルリクエストがあったかどうかを判断します。

Parameterized Trigger plugin

Jenkinsのジョブをトリガとして、別のジョブを起動させるという処理が可能になります。パラメータを引き継ぐことも可能です。

 

GitHubアカウントの作成とSSHキーの登録

GitHubへアクセスし、Pull Requestを監視するため、事前にJenkins用GitHubアカウントの作成とSSHキーを登録する必要があります。GitHubアカウント作成後、以下の手順でSSHキーを登録します。

mkdir /var/lib/jenkins/.ssh

cd /var/lib/jenkins/.ssh

sudo -u jenkins -H ssh-keygen -t rsa -C jenkins@dev-test.com

rootで作成した場合は、所有権をjenkinsに変更する必要があります。

chown -R jenkins:jenkins /var/lib/jenkins/.ssh

Jenkins用GitHubアカウントでログインし、SSH Keysにキーを登録します。id_rsa.pubの中身を貼付けるだけです。

SSH設定

GitHub Pull Request Builderのシステム設定

Jenkinsシステム設定からGitHub Pull Request Builderの設定を行います。最低限変更が必要な設定は以下の通りです。

 

Access Token

GitHubの設定→Applications→Personal Access tokensからtokenを作成し、入力します。repo及びrepo:statusにチェックを入れておく必要があります。

SSHを登録しておけば、下部にあるUsername、Passwordに入力し、Create access tokenボタンを押すと自動でtokenを作成・追加してくれます。

Admin list

Pull Requestを送った際にジョブを実行させる人を登録

Published Jenkins URL

ジョブの実行結果をGitHubのコメントとして残す場合は入力

Close failed pull request automatically?

ビルド&テストに失敗した場合に自動でPull Requestを閉じる場合はチェック

Crontab line

ポーリングを行う間隔を設定します。『*/5 * * * *』で5分おきに監視します。

 

ジョブの追加

Jenkinsに新規ジョブを追加します。

メインジョブ (Job-1)

GitHub project・・・GitHubプロジェクトのURLを入力(https://~~~~)

ソースコード管理・・・Gitを選択

Repository URL・・・git@github.com:~~~を入力

高度な設定 Name・・・Job

高度な設定 Refspec・・・+refs/pull/*:refs/remotes/origin/pr/*

Branches to build・・・${sha1}

ビルド・トリガ・・・Build when a change is pushed to GitHubにチェック、GitHub pull requests builder Admin listにチェック

Admin list・・・ビルドを行うユーザーを登録。システム設定で設定している場合は追加されているはずです。

 

Pull Requestを受け取ったらJava7・Java8の2つのジョブを実行し、GitHubハッシュ値であるsha1を渡します。

 

Trigger/Call builds on other projectsを選択

Projects to build・・・Job-2

Block until the triggered projects finish their builds・・・チェックを入れる。

Predefined parameters・・・sha1=${sha1}を追加

Projects to build・・・Job-3

Block until the triggered projects finish their builds・・・チェックを入れる。

Predefined parameters・・・sha1=${sha1}を追加

 

終了後のジョブを登録します。

 

Trigger parameterized build on other projects・・・選択する。

Projects to build・・・Job-4

Trigger when build is・・・Stable or unstable but not failed

 

Jobから送っておきたいパラメータがある場合は以下を設定します。

Predefined parameters・・・『受け取り先パラメータ=受け渡したいパラメータ』で追加します。

Current build parameters・・・選択します。

Java7でのテスト&ビルドジョブ (Job-2)

JDK・・・JDK1.7を選択

ビルド設定

./gradlew test

./gradlew build

./gradlew jar

Java7でのテスト&ビルドジョブ (Job-3)

JDK・・・JDK1.8を選択

 

ビルド設定

./gradlew -Pjdk8 test

./gradlew -Pjdk8 build

./gradlew -Pjdk8 war

結果ジョブ (Job-4)

結果として出力したい情報を追加します。以下の例ではシェルの実行からChatWork APIを利用して、指定した部屋にプルリクがあったことを伝えています。各ジョブから受け取ったパラメータを指定することもできます。

 

シェルの実行に以下を追加

_body=`cat << _EOT_

[info]

[title]

GitHub Pull Request Information

[/title]

以下のPRがありました。

Info : ${ghprbPullTitle} from ${ghprbPullAuthorEmail}

Description : ${ghprbPullDescription}

PRLink : ${ghprbPullLink}

Jenkins : ${jenkins_url}

[/info]

 

_EOT_`

 

curl -X POST -H "X-ChatWorkToken: chatwork APIのToken" -d "body=${_body}" "https://api.chatwork.com/v1/rooms/XXXXXX/messages"

Gradleファイルの設定

今回利用したgradleファイルは以下となっています。分岐にproject.hasProperty('jdk8')と記述することで、gradle(gradlew)コマンドを実行した際に-Pjdk8というプロパティがあるかどうかを確認することができます。

allprojects {

apply plugin: 'java'

apply plugin: 'war'

 

configurations {

provided

provided.extendsFrom(compile)

}

 

repositories {

mavenCentral()

}

 

dependencies {

compile fileTree(dir: "lib", include: '*.jar')

compile("junit:junit:4.11") {

exclude group: 'org.hamcrest'

}

compile('org.hamcrest:hamcrest-all:1.3')

compile('org.mockito:mockito-all:1.9.5') {

exclude group: 'org.hamcrest'

}

if (project.hasProperty('jdk8')) {

compile 'org.apache.tomcat:tomcat-servlet-api:8.0.8'

compile 'org.apache.tomcat:tomcat-jsp-api:8.0.8'

} else {

compile 'org.apache.tomcat:tomcat-servlet-api:7.0.53'

compile 'org.apache.tomcat:tomcat-jsp-api:7.0.53'

}

compile 'org.apache.httpcomponents:httpclient:4.3.4'

}

 

if (project.hasProperty('jdk8')) {

[compileJava, compileTestJava].each{

it.options.encoding = 'UTF-8'

it.options.compilerArgs += ['-source', '1.8', '-target', '1.8']

}

 

sourceSets {

main {

java {

srcDir 'WEB-INF/src'

}

}

test {

java {

srcDir 'WEB-INF/test/'

}

}

}

} else {

[compileJava, compileTestJava].each{

it.options.encoding = 'UTF-8'

it.options.compilerArgs += ['-source', '1.7', '-target', '1.7']

}

 

sourceSets {

main {

java {

srcDir 'WEB-INF/src'

}

}

test {

java {

srcDir 'WEB-INF/test/'

}

}

}

}

 

[compileJava, compileTestJava]*.options.collect { options -> options.encoding = 'UTF-8' }

 

war {

destinationDir = file('./export/')

archiveName = 'test.war'

webXml = file('WEB-INF/web.xml')

}

 

jar {

copy {

from configurations.compile

into "build/distributions/lib"

}

def manifestClasspath = configurations.compile.collect{ 'lib/' + it.getName() }.join(' ')

manifest {

attributes 'Class-Path': manifestClasspath

}

from (configurations.compile.resolve().collect { it.isDirectory() ? it : fileTree(it) }) {

}

destinationDir = file('./export/')

archiveName = 'test.jar'

}

 

}

実行

全ての設定が終わった後、指定したリポジトリにPull Requestを送るとJenkinsのジョブが実行されます。ビルド&テストが成功するとGitHubのコメントにて結果が確認できます。

GitHubコメント

ChatWorkにも結果が出力されました。

ChatWorkコメント

ハマりポイント

①GradleによるJava7/Java8の切り替え

→プロパティにより分岐させる方法を取りました。その際にプロパティは『-P文字列』で指定するので注意が必要。

②ジョブ分岐時のブロック

→Block until the triggered projects finish their buildsにチェックを入れないと、ジョブが進行してしまう。ジョブが完了するまで待つ場合は必ずチェックする。

③ジョブ失敗時の中止処理

→Stable or unstable but not failedを選択しないと、途中でジョブが失敗しても継続してしまう。失敗時に次のジョブに移行しない場合は必ず選択する。

④ChatWork APIキーの設定

→ChatWork APIを使用する際に、APIが利用申請をしてから利用ができるようになるまで数日かかる。

おわりに

今回はJenkinsとGitHubの連携により、Java7/Java8のデュアルビルドを行い、結果をGitHubのコメント・ChatWorkのコメントとして出力するという流れを説明しました。

Jenkinsのジョブで分岐させ、Gradleに分岐の設定をすることで、Javaバージョンごとに処理を分けることが可能です。皆さんもこれを機に、各開発環境にマッチしたCI環境を構築し、どんどん怠惰していきましょう!