GradleとEclipseを使う場合のTipsをいくつか紹介

またまたGradleネタです。Gradleなどのツールでビルドしつつも普段の開発やユニットテストはIDEで行うというのはよくある話。GradleではIDE開発をサポートするプラグインも用意されています。

今回はこのうちEclipse Pluginに関していくつかTipsを紹介。

Eclipse Pluginを使うと何ができる?

プロジェクトからEclipseの各種設定ファイルを自動生成することができる。Mavenで言うところのMaven Eclipse Pluginと同じような物。
Eclipseのプロジェクト情報である.projectファイルや、クラスパスを定義した.classpathを生成することができる。
この時、Gradleで定義した依存関係も解決してくれるので、MavenみたいにJARを自動でダウンロードしてEclipseのクラスパスに反映させることもできる。

まずはシンプルな例:依存ライブラリをクラスパスに設定する

build.gradleを以下のように作成する。また、ソースフォルダ用にsrc/main/javaも事前に作っておく。

apply plugin: 'java'
apply plugin: 'eclipse'

repositories {
    mavenCentral()
}

dependencies {
    compile 'commons-lang:commons-lang:2.6'
}

普通のJavaプロジェクトとの違いは、apply plugin: 'eclipse'と書いただけ。これでgradle eclipseと実行する。

# gradle eclipse
:eclipseClasspath
:eclipseJdt
:eclipseProject
:eclipse

BUILD SUCCESSFUL

Total time: 5.294 secs

ビルドでは依存ライブラリのダウンロードが自動で行われ、プロジェクト直下に.projectと.classpathが生成される。.classpathを見てみると以下のように、dependenciesで依存関係として定義したcommons-langも設定されている。また、便利なことにデフォルトでsourcepathも設定されるので、依存ライブラリのソースを見たくなったときも簡単に参照できるようになっている*1。事前にsrc/main/javaフォルダを作成しておくことで、自動的にソースフォルダとしても設定もしてくれます*2

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
	<classpathentry kind="output" path="bin"/>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
	<classpathentry kind="src" path="src/main/java"/>
	<classpathentry sourcepath="/Users/wadahiro/.gradle/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/source/67313d715fbf0ea4fd0bdb69217fb77f807a8ce5/commons-lang-2.6-sources.jar" kind="lib" path="/Users/wadahiro/.gradle/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/jar/ce1edb914c94ebc388f086c6827e8bdeec71ac2/commons-lang-2.6.jar" exported="true"/>
</classpath>

クラスパス変数を利用した.classpathを生成する

先の例では、クラスパスの設定が「/Users/wadahiro/」のように開発者環境依存のパスになっている。これだと、.classpathSVNやGitなどのソース管理で共有することができない。ここはMavenのM2_HOMEみたいにクラスパス変数を使った方式にしたいところ。
GradleでEclipseの設定ファイルを生成するときにクラスパス変数GRADLE_REPOを使う - 豆無日記でGradleでの設定方法について紹介されているが、残念ながら最新バージョン(1.0-milestone-9)だと非推奨(deprecated)になっている。

#gradle eclipse
The eclipseClasspath.variables method has been deprecated and will be removed in the next version of Gradle. Please use the eclipse.pathVariables method instead.
:eclipseClasspath
:eclipseJdt
:eclipseProject
:eclipse

BUILD SUCCESSFUL

1.0-milestone-9だと、以下のように定義する。

eclipse {
    pathVariables 'GRADLE_USER_HOME': gradle.gradleUserHomeDir
}

これでgradle eclipseを実行すると以下のように生成される。ちゃんと「GRADLE_USER_HOME」に置き換わっている。後は、Eclipseのクラスパス変数の設定でGRADLE_USER_HOMEをユーザーホームに設定すれば良い*3

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
	<classpathentry kind="output" path="bin"/>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
	<classpathentry kind="src" path="src/main/java"/>
	<classpathentry sourcepath="GRADLE_USER_HOME/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/source/67313d715fbf0ea4fd0bdb69217fb77f807a8ce5/commons-lang-2.6-sources.jar" kind="var" path="GRADLE_USER_HOME/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/jar/ce1edb914c94ebc388f086c6827e8bdeec71ac2/commons-lang-2.6.jar" exported="true"/>
</classpath>

クラスパスの優先順位を制御する

プロジェクトによっては、あるライブラリへのクラスパスの優先度を他より高くしたいときがたま〜にある。例えば、commons-langのうちあるクラスにだけ独自の修正を入れて、そのクラスのみを含んだJARを作ったとする。この場合、オリジナルのJARより優先度を高くしないと修正したクラスが反映されない*4
独自修正のクラスはMavenリポジトリには置いていなくて、プロジェクトのlibフォルダ以下に置いていたとする。この時、build.gradleを以下のように定義したとする。dependenciesの中で上に書けば優先度も高くなるんじゃない?と期待しつつ...

dependencies {
    compile files('lib/my-custom-commons-lang.jar')
    compile 'commons-lang:commons-lang:2.6'
}

しかしこれでgradle eclipseしても、以下のように、lib/commons-lang-2.6.jarへのクラスパス設定は最後になり、優先度としては一番低くなってしまう*5。また、もう1つ残念なことにクラスパスが絶対パスになってしまっている。これではまた.classpathを共有できなくなってしまう。

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
	<classpathentry kind="output" path="bin"/>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
	<classpathentry sourcepath="GRADLE_USER_HOME/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/source/67313d715fbf0ea4fd0bdb69217fb77f807a8ce5/commons-lang-2.6-sources.jar" kind="var" path="GRADLE_USER_HOME/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/jar/ce1edb914c94ebc388f086c6827e8bdeec71ac2/commons-lang-2.6.jar" exported="true"/>
	<classpathentry kind="lib" path="/Users/wadahiro/git/gradle-samples/eclipse-samples/classpath-order-sample/lib/commons-lang-2.6.jar" exported="true"/>
</classpath>

こういう時は、eclipse.classpath.file.whenMergedを使う。これを使うことで、.classpathの定義内容を細かく制御可能になる。以下は、kind="lib"のクラスパスは上位に移しつつ、絶対パスをやめて相対パスに変換する例。

eclipse {
    pathVariables 'GRADLE_USER_HOME': gradle.gradleUserHomeDir

    classpath {
        file {
            whenMerged { classpath ->
                def libs = classpath.entries.findAll { it.kind == 'lib' }
                libs = libs.collect { lib ->
                    def baseDir = project.projectDir.getAbsolutePath().replace('\\', '/')
                    if (lib.path.startsWith(baseDir)) {
                        lib.path = lib.path.replace(baseDir, ".")
                    }
                    return lib
                }
                def others = classpath.entries.findAll { it.kind != 'lib' }
                classpath.entries = libs + others
            }
        }
    }
}

classpath.entries に最終的に.classpathに出力するクラスパスのリストをセットすれば良い。上記では、kind="lib"とそれ以外を分けて連結しなおし、先頭に移動している。これでgradle eclipseを実行すると、以下のように望んだ形で出力されるようになる。lib/commons-lang-2.6.jarへのパスも絶対パスから相対パスに変換されているので、これで環境依存もなくなります。

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
	<classpathentry kind="lib" path="./lib/commons-lang-2.6.jar" exported="true"/>
	<classpathentry kind="output" path="bin"/>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
	<classpathentry kind="src" path="src/main/java"/>
	<classpathentry sourcepath="GRADLE_USER_HOME/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/source/67313d715fbf0ea4fd0bdb69217fb77f807a8ce5/commons-lang-2.6-sources.jar" kind="var" path="GRADLE_USER_HOME/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/jar/ce1edb914c94ebc388f086c6827e8bdeec71ac2/commons-lang-2.6.jar" exported="true"/>
</classpath>

追記

作成したサンプルはGitHubにあげています。

*1:もちろん、*-sources.jarがリポジトリで公開されていることが前提です。

*2:フォルダがないとソースフォルダの設定はされないので注意。

*3:残念ながらこの設定は手動になってしまう。[http://maven.apache.org/plugins/maven-eclipse-plugin/configure-workspace-mojo.html:title=Maven Eclipse Pluginのeclipse:configure-workspace]と同じ機能がGradleにあるといいけど、どうせ1回しか設定しないからなくてもほとんど困らないと思う。

*4:そもそもJARを2つにせず1つにすればいいのだが、色々事情があってできないこともあるのだ!!

*5:どうもfilesで指定した場合(lib="kind"となるもの)は優先度が低くなるようだ。