GradleでJavaのバージョンを指定するときはsourceCompatibilityじゃなくてToolchainsを使うと便利

追記 2021-02-14

Toolchains はまだ IDEA ではサポートされてないみたい。IDEA を使うときはこれまでの書き方をしておく方が良さそう。

Support detecting SDKs from Gradles toolchain support https://youtrack.jetbrains.com/issue/IDEA-252328

追記ここまで ===

昨日、Gradleのことを書いたのだけど。そういえば、触ってる中でもうひとつ学んだことがあったので、今日はそれについて。今日もタイトルの通り。

bufferings.hatenablog.com

昨日も書いたけど、Gradleって変化が速い印象あるので、しばらくするとこのやり方よりも良いやり方が出てくるかもしれない。今日は、2021年2月時点のGradle 6.8.2のお話。

これまでの書き方

これまでは、Gradleでビルドに使うJavaのバージョンを指定するのには sourceCompatibilitytargetCompatibility を使ってた。実際に、昨日の最後に出てきたやつは、Gradleの公式サイトから落としてきたやつなんだけど、こうなってる:

❯ cat build-logic/commons/src/main/groovy/com.example.commons.gradle 

...

java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

...

こういう設定でコンパイルしたclassファイルのバージョンをみるとJava 8のになってる:

❯ javap -v app/build/classes/java/main/toolchains/sample/App.class | grep major
  major version: 52

Toolchainsを使う

これを、Toolchainsを使うように変更する:

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
    }
}

Toolchainsを使うとどうなるの?

Toolchainsは

  1. 指定されたバージョンがないかをローカルマシーンの中で探す
  2. 見つからなかったらダウンロードする
  3. それを使ってくれる

この辺りを読んだ。

https://docs.gradle.org/6.8.2/userguide/toolchains.html

https://blog.gradle.org/java-toolchains

遊んでみよう

こういう build.gradle を用意してみた:

plugins {
    id 'application'
}

repositories {
    mavenCentral()
}

application {
    mainClass = 'toolchains.sample.App'
}

実行するJavaのバージョンを出してみようかな。App.java をこうしてみる。

package toolchains.sample;

public class App {
    public static void main(String[] args) {
        System.out.println(System.getProperty("java.version"));
        System.out.println(System.getProperty("java.home"));
    }
}

実行

❯ ./gradlew run

> Task :app:run
15.0.2
/home/bufferings/.sdkman/candidates/java/15.0.2.hs-adpt

BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed

いまAkkaで遊んでて 15.0.2 をデフォルトにしてるから、それが使われてる。

バージョンを指定してみる

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
    }
}

実行

❯ ./gradlew run

// こんな感じでダウンロードされた
<=============> 100% CONFIGURING [4s] 
> Provisioning toolchain adoptopenjdk-11-x64-linux.tar.gz > adoptopenjdk-11-x64-linux.tar.gz

> Task :app:run
11.0.10
/home/bufferings/.gradle/jdks/jdk-11.0.10+9

BUILD SUCCESSFUL in 2s
2 actionable tasks: 2 executed

ほほー。homeの.gradleの下に入るのか。便利。バージョンも上がってる↓

❯ javap -v app/build/classes/java/main/toolchains/sample/App.class | grep major
  major version: 55

既存のJDKを使う

指定したバージョンのJDKが、既にSDKMAN!でインストール済みの場合は、そっちが使われるんかな?14は入ってるから試してみる。

❯ ./gradlew run

> Task :app:run
14.0.1
/home/bufferings/.sdkman/candidates/java/14.0.1.hs-adpt

BUILD SUCCESSFUL in 2s
2 actionable tasks: 2 executed

ほほー。賢い。

ということで

Toolchainsを使うと、使いたいJDKがインストールされていない環境でも、ダウンロードしてきてビルドを実行してくれる。最初にダウンロードの時間はかかるけど。便利。