Gradle で Scala プロジェクトをクロスビルドする方法

Gradle を用いて Scala プロジェクトを複数の Scala バージョンをターゲットにクロスビルドする方法をメモしておきます。

はじめに

Scala プロジェクトでビルドツール、というと sbt を選択するのが一般的かと思いますが、やんごとなき事情 1 で Gradle で Scala プロジェクトをビルドしたい、それも 2.10 と 2.11 といった 複数の Scala バージョンをターゲットにクロスビルド したい、というケースに出くわしたので、具体的にそれを実現する方法を調べてみたのでした。

結論から言うと、

  • Scala のクロスビルドをサポートする機能は当然 Gradle にはないし、そのような Gradle プラグインも Gradle のプラグインポータル には存在しない 😵
  • でも、(Gradle プラグインとしてパッケージング & パブリッシュされてはいないものの) Gradle で Scala プロジェクトをクロスビルドを実現する方法は LinkedIn の OSS、photon-ml で実現されている
  • 同 OSS の buildSrc に Scala プロジェクトのクロスビルドを実現するプラグインが存在するので、これを拝借すれば自前の Gradle プロジェクトでも Scala プロジェクトのクロスビルドが実現できる 😼

となります。

Gradle で Scala プロジェクトをビルドする方法

まずはじめに、「クロスビルド」という制約を考えない場合の Scala プロジェクトをビルドする方法を確認しておきます。

Gradle では Scala プラグイン が提供されており、これを利用することで容易に Scala プロジェクトのビルドが Gradle で実現できるようになります。

例えば、次のような build.gradle ファイルを用意すれば OK です。

apply plugin: 'scala'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.scala-lang:scala-library:2.11.1'
}

しかし、この方法だと当然、 dependencies で指定している scala-library のバージョン向けのバイナリ (2.11) しかビルドできません。

photon-ml の scala-cross-build プラグインを利用する

この Gradle における Scala プロジェクトのクロスビルド問題に対して、LinkedIn の OSSである photon-ml では、自前で Gradle のカスタムプラグイン scala-cross-build を用意し、これを利用することで解決しています。

scala-cross-build プラグインは次の 4 つのファイルで構成されています。

このプラグインを利用するには、上記ファイルをお手元の Gradle プロジェクトの buildSrc ディレクトリ以下に配置する必要があります。また、(build.gradle ではなく) settings.gradle に次のようなクロスビルドの設定を記述する必要があります。

apply plugin: 'scala-cross-build'

scalaCrossBuild {
    defaultScalaVersion '2.10.6'
    buildDefaultOnly false

    // クロスビルド対象の Scala バージョンを指定する
    targetScalaVersions '2.10.6', '2.11.8'

    projectsToCrossBuild(
        // ここにクロスビルドしたい Scala プロジェクトの名前 (文字列) を
        // カンマ区切りで列挙する
    )
}

scala-resolver プラグインで依存ライブラリのバイナリバージョンを切り替える

クロスビルドをしたい Scala プロジェクトにおいて、他の Scala のライブラリに依存している場合は、scala-cross-build プラグインに加えて scala-resolver を利用することになります。

こちらのプラグインは次の 4 つのファイルで構成されています (ScalaUtils.groovy は先のプラグインのと同一ファイルです)。

利用方法は次の通りで、Scala のライブラリのサフィックス部分にあるバイナリバージョン指定を ${scalaSuffix} で置き換えます。

dependencies {
    compile group: 'org.json4s', name: "json4s-native${scalaSuffix}", version: '3.5.0'
}

サンプルプロジェクトで Scala のクロスビルドを試す

実際に、この scala-cross-build プラグインを用いて、サンプルプロジェクトで Scala プロジェクトをクロスビルドしてみましょう。今回のサンプルプロジェクトは以下になります。

上述したとおり、scala-cross-build プラグインを利用するために buildSrc ディレクトリの下には photon-ml の同プラグインのファイルを配置しておきます。

そして settings.gradle では、Gradle のサブプロジェクト scala-proj-fooscala-proj-bar をクロスビルド対象に指定し、Scala のバージョンは 2.10 と 2.11 をそれぞれ指定しています。

また、scala-proj-foo / scala-proj-bar それぞれの build.gradle では以下のように、

  • Gradle の Scala プラグインを利用する
  • scala-library のバージョンは、scalaVersion を指定する
    • scalaVersion は scala-cross-build プラグインが提供するプロパティ

と記述します。

apply plugin: 'scala'

dependencies {
    compile group: 'org.scala-lang', name: 'scala-library', version: scalaVersion
}

ここまで設定したら、後は ./gradlew clean build で Scala プロジェクトのクロスビルドが実現できます。クロスビルドにより生成されたファイルは、build ディレクトリ配下に出力されます。

サンプルプロジェクトで ./gradlew clean build とすれば、次のようなディレクトリ構成でクロスビルド結果が得られるでしょう。

build
├── scala-proj-bar_2.10
│   └── libs
│       └── scala-proj-bar_2.10-1.0-SNAPSHOT.jar
├── scala-proj-bar_2.11
│   └── libs
│       └── scala-proj-bar_2.11-1.0-SNAPSHOT.jar
├── scala-proj-foo_2.10
│   └── libs
│       └── scala-proj-foo_2.10-1.0-SNAPSHOT.jar
└── scala-proj-foo_2.11
    └── libs
        └── scala-proj-foo_2.11-1.0-SNAPSHOT.jar

まとめ

  • Gradle にも Scala プラグインにも、Scala のクロスビルド機能は現時点 (2017-02-19) では存在しないよ
  • photon-ml に含まれている scala-cross-build プラグインを使えば、Gradle でも Scala プロジェクトのクロスビルドができるようになるよ

  1. 具体的には、マルチプロジェクト構成の Gradle プロジェクトで、当初のサブプロジェクトはすべて Java プロジェクトだったのだけれど、途中から Scala プロジェクトを追加することになって、いまから sbt に乗り換えるのも面倒だな… というケースです