Circle CIでJavaを使ってビルドする

ここ何日かCircle CIを使ってみて何となく分かってきた事をまとめておきます。

完成したcircle.ymlだけ欲しい方はこちらをどうぞ。

checkout:  
  post:
    - chmod +x ./gradlew

machine:  
  timezone: Asia/Tokyo
  environment:
    GRADLE_OPTS: -Xmx4G -Dorg.gradle.daemon=true
    JAVA_HOME: /usr/lib/jvm/java-8-oracle
  post:
    - sudo service mysql stop
    - sudo service postgresql stop

dependencies:  
  pre:
    - sudo apt-get install software-properties-common
    - sudo add-apt-repository -y ppa:webupd8team/java
    - sudo apt-get update
    - echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | sudo /usr/bin/debconf-set-selections
    - sudo apt-get install oracle-java8-installer
  override:
    - ./gradlew -v
    - ./gradlew testClasses

test:  
  override:
    - ./gradlew --full-stacktrace check

シェルスクリプトの実行権限

Windowsを使っているとつい忘れがちですけども、シェルスクリプトに実行権限を付けるのを忘れないようにしましょう。

checkout:  
  post:
    - chmod +x ./gradlew

タイムゾーンを自分達が作業しているものに変えよう

ログの中の時間が分かり易いようにタイムゾーンをデフォルト値から変更しておきましょう。

僕は概ね日本時間の夜中に活動しているのでAsia/Tokyoに変更しています。

CircleCIはRDBは二つのRDBが起動している

mysqlとpostgresqlという二つのRDBが標準で動作しています。デフォルトで使えるメモリは4GBなので、必要のないものは止めてしまいましょう。

machine:  
  post:
    - sudo service mysql stop
    - sudo service postgresql stop

尚、サポートに連絡すればメモリは増やして貰えるようです。

CircleCIは言語ランタイムのバージョンが固定されている

Circle CIでは、machine設定でプログラミング言語のバージョンを指定できるようになっていますが、細かいリビジョンを指定できません。

例えば、Oracle JDK 8を利用する場合、以下のように設定します。

machine:  
  java:
    version: oraclejdk8

2015/11/30 現在、この指定で動作するのは1.8.0_40です。これがリリースされたのは2015/03/03なのでちょっと古すぎるように感じます。

最新のJavaを使ってビルドする

Circle CI上で最新のJavaを使ってビルドする方法について模索してみましょう。

方法としては2種類あります。

  • Dockerを使って最新のJavaが入ったイメージをビルドして使う方法
  • apt-getコマンドで最新のJavaをインストールする方法

今回はapt-getコマンドでJavaをインストールしてみましょう。Circle CIでビルドする際に動作するOSは、公式のマニュアルによるとUbuntu 12.04です。

これにコマンドだけでJava 8をインストールする場合、以下のように実行します。

sudo apt-get install software-properties-common  
sudo add-apt-repository -y ppa:webupd8team/java  
sudo apt-get update  
echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | sudo /usr/bin/debconf-set-selections  
sudo apt-get install oracle-java8-installer  

まず、software-properties-commonをインストールして、add-apt-repositoryコマンドを使えるようにしています。

次に、add-apt-repositoryコマンドでOracle Java 8のメタデータが格納されているppa:webupd8team/javaリポジトリを追加します。

次の、 apt-get updateはパッケージのメタデータをローカルに取込んでいます。

次は、少し長めのコマンドになっています。Circle CI上では対話的にパッケージをインストールすることは出来ませんが、Oracle Javaのインストーラはかなり細かくウィザードに回答しなければなりません。そこで、debconf-utilsのdebconf-set-selectionsを使ってウィザードに自動回答するようにしています。

最後に、oracle-java8-installerをインストールしています。これによってインストールされたJavaは、/usr/lib/jvm/java-8-oracleというパスに格納されています。

GradleやMavenからJavaを使う際には、JAVA_HOME環境変数の書き換えが必要になりますので忘れないようにしましょう。以下のように記述します。

machine:  
  environment:
    JAVA_HOME: /usr/lib/jvm/java-8-oracle

Gradle Daemonを使おう

Gradleの起動時間は決して速いとは言えませんので、起動時間を少しでも短縮するためにGradle Daemonを使いましょう。

gradlewコマンドのオプションとして--daemonを付けても良いのですが、--daemonが有効なものとそうでないものを切り分けるのが面倒なので、-Dorg.gradle.daemon=trueを常に起動オプションとして指定してしまいましょう。
その場合、環境変数GRADLE_OPTSが使えます。

Circle CIで環境変数を指定するにはmachine設定のenvironmentセクションを使って以下のように記述します。

machine:  
  environment:
    GRADLE_OPTS: -Xmx4G -Dorg.gradle.daemon=true

ライブラリの依存性解決はdependenciesフェイズ内でやろう

Gradleを使ってビルドする場合、必要になるギリギリまで依存ライブラリのダウンロードが行われません。

Circle CIでは、databaseフェイズ中にダウンロードキャッシュの保存を行うためtestフェイズ中に依存ライブラリのダウンロードを行ってしまうと上手く保存されず、ビルドのたびに依存ライブラリをダウンロードしてしまいます。

これに対応するために、dependencies設定内でテストコードのビルドまではやってしまいましょう。Gradleでテストコードのビルド及びテストに必要なリソースのコピーを行う標準のタスクはtestClassesでしたね。

つまり、以下のようになります。

dependencies:  
  override:
    - ./gradlew -v
    - ./gradlew testClasses

ここでは、GradleやJavaのバージョンをまとめて確認できるように、-vを付けてGradleをとりあえず起動しています。以下のような出力が得られますので、上手くビルド出来ない時の手掛かりになります。

------------------------------------------------------------
Gradle 2.9  
------------------------------------------------------------

Build time:   2015-11-17 07:02:17 UTC  
Build number: none  
Revision:     b463d7980c40d44c4657dc80025275b84a29e31f

Groovy:       2.4.4  
Ant:          Apache Ant(TM) version 1.9.3 compiled on December 23 2013  
JVM:          1.8.0_66 (Oracle Corporation 25.66-b17)  
OS:           Linux 3.14.28-031428-generic amd64  

テストだけでなく静的解析の類も実行しよう

CIサーバでユニットテストだけ実行するのでは物足りませんので、checkタスクを実行しましょう。その際に、--full-stacktraceオプションを追加しておくと失敗した際に原因が分かり易くなります。

test:  
  override:
    - ./gradlew --full-stacktrace check

以上です。

より良い方法をご存じの方は@ryushiまでメンションを下さい。