最近のGradleはビルドの際のJDKを自動でダウンロード&インストールする機能がついている
みなさんこんにちは! Gradle 初心者です!
Gradle は 7.6 から Toolchain という機能があり、ビルドに使うJDKを自動インストールすることができます。
具体的には以下のように書くと、Javaのビルド時にGradleが事前インストール無しで自動的にJDK 22をダウンロードしてビルドに使ってくれます。1
plugins { id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' }
java {
toolchain {
// Java のビルドは JDK 22 を使ってビルドする
languageVersion = JavaLanguageVersion.of(22)
}
}
通常、Javaのビルドツール(JDK)のバージョンは sdkman
, jenv
などを使って管理していると思いますが、この機能を使えばGradle一本でビルド時のJVMのバージョンを指定することが可能です。
Gradleの実行に使っているJavaバージョンは特に縛らず、ビルドに使うJavaのバージョンはきちんと指定しておくといったことも簡単に実現できます。
実際に切り替わるのか?
ためしにJava 22では通り、Java 20では通らないコードを用意してみます。
// Java 22 のプレビュー機能を有効にすると使える JEP 463 (暗黙のMainメソッド/クラス) を使ったソースコード
void main (String... args) {
System.out.println("Hello");
}
settings.gradle
, build.gradle
は以下を使います。
plugins {
// 最近のバージョンの Gradle (8.x) だとJDKの自動ダウンロードはプラグインを指定する必要がある
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}
plugins { id 'java' }
java {
toolchain {
// ~.of(20) だとビルドが通らないが ~.of(21) や ~.of(22) だとビルドが通る
languageVersion = JavaLanguageVersion.of(22)
}
}
// プレビュー機能使わないなら指定は不要
tasks.withType(JavaCompile) {
options.compilerArgs += "--enable-preview"
}
ビルドに利用するJavaのバージョンを切り替えてビルドが通るか試してみます。
# Gradle 自体は Java11 で動かす予定 (古すぎ?!)
$ java -version
openjdk version "11.0.3" 2019-04-16 LTS
# ~~.of(22) にしてビルド → ビルドは通る
$ sed -i '' 's|of(..)|of(22)|g' build.gradle && ./gradlew build
... Unpacking toolchain archive OpenJDK22U-jdk_x64_mac_hotspot_22_36.tar.gz ...
BUILD SUCCESSFUL in 23s
# ~~.of(20) にしてビルド → ビルドは通らない
$ sed -i '' 's|of(..)|of(20)|g' build.gradle && ./gradlew build
... Unpacking toolchain archive OpenJDK20U-jdk_x64_mac_hotspot_20.0.2_9.tar.gz ...
BUILD FAILED in 27s
きちんとバージョンは切り替わっているようですね!
ちなみにビルドだけではなく、Javaを動かす際もバージョン指定は可能です。
// ... ソースの続き ...
task run(type: JavaExec) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(22)
}
mainClass = 'Main'
classpath = sourceSets.main.runtimeClasspath
}
// プレビュー機能使わないなら指定は不要
tasks.withType(JavaExec) {
jvmArgs += '--enable-preview'
}
$ ./gradlew run
# Java 22 で動いた!
> Task :run
Hello
自動でダウンロード&インストールされたJDKは ~/.gradle/jdks/
で確認できました。
$ ls -al ~/.gradle/jdks/
drwxr-xr-x 8 ksaitou staff 256 3 25 16:37 .
drwxr-xr-x 14 ksaitou staff 448 10 5 10:52 ..
-rw-r--r-- 1 ksaitou staff 197502362 3 25 16:37 OpenJDK20U-jdk_x64_mac_hotspot_20.0.2_9.tar.gz
-rw-r--r-- 1 ksaitou staff 17 3 25 16:37 OpenJDK20U-jdk_x64_mac_hotspot_20.0.2_9.tar.gz.lock
-rw-r--r-- 1 ksaitou staff 192678886 3 25 16:36 OpenJDK22U-jdk_x64_mac_hotspot_22_36.tar.gz
-rw-r--r-- 1 ksaitou staff 17 3 25 16:37 OpenJDK22U-jdk_x64_mac_hotspot_22_36.tar.gz.lock
drwxr-xr-x 3 ksaitou staff 96 3 25 16:37 eclipse_adoptium-20-x86_64-os_x
drwxr-xr-x 3 ksaitou staff 96 3 25 16:37 eclipse_adoptium-22-x86_64-os_x
ちなみにこの機能、既にインストールされている Java についてはインストールされません。 試しに SDKMan! でJava 21をインストールしてから実行してみます。
$ sdk install java 21.0.2-tem
# ビルドは通る
$ sed -i '' 's|of(..)|of(21)|g' build.gradle && ./gradlew build
BUILD SUCCESSFUL in 873ms
# ダウンロードされた痕跡はない
$ ls -al ~/.gradle/jdks/ | grep 21
=> 結果なし
既にインストールされているか判定するための Gradle が認識しているJDKのインストール は gradle -q javaToolchains
で取得できるようです。ツールチェインの取得ソースとして、OSのJDK並びにSDKManやIntelliJなどメジャーどころは一通りスキャンするらしく便利にできています。
$ ./gradlew -q javaToolchains | grep -F ' +'
+ AdoptOpenJDK xx.xx
+ Azul Zulu JDK xx.xx
+ Eclipse Temurin JDK xx.xx
+ OpenJDK xx.xx
+ Oracle JDK xx.xx
もう少し深掘りしてみる
これだけでは消化不良なので、もう少し深掘りしていきます。
どのJDKがダウンロードされるのか?
JDKといってもOracle JDKのほか、色々なJDKが世の中にはあります。
-
OpenJDK
- Eclipse Temurin (旧 AdoptOpenJDK)
- Amazon Corretto
- Micrsoft Build of JDK
- Zulu JDK
- Open J9
これらベンダーについては vendor = JvmVendorSpec.XXXX
で明示的に指定可能です。
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
vendor = JvmVendorSpec.ADOPTIUM
}
}
vendor = ...
を省略した場合などの詳細な動作は settings.gradle
などに指定した Toolchain Plugins によって決まるようです。
現段階での99%の人は Foojay Toolchains Plugin (org.gradle.toolchains.foojay-resolver-convention
) を使うでしょうから2、その場合は Eclipse Temurin, AOJ(AdoptOpenJDK) が優先して使われるようです。 選択肢としては無難かつ妥当と思います。
val distributionOrderOfPreference = listOf("Temurin", "AOJ")
// ...
private fun allDistributionsPrecededByWellKnownOnes(distributions: List<Distribution>): List<Distribution> =
distributions.sortedBy {
// Put our preferences first, preserve Foojay order otherwise.
val indexOf = distributionOrderOfPreference.indexOf(it.name)
when {
indexOf < 0 -> distributionOrderOfPreference.size
else -> indexOf
}
}
どこからJDKがダウンロードされるのか? foojay Disco API とは何か?
ダウンロードすべきベンダーやバージョンが決まったら Foojay Toolchains Plugin は foojay Disco API を通じてJDKがダウンロードできるURLを取得するようです。
(foojay) Disco API (discovery api) というのは、いろいろな OpenJDK のビルドを検索するためのAPIのようです。試しに叩いてみます。
# メジャーバージョンおよびマイナーバージョンの一覧を取得してみる
$ curl https://api.foojay.io/disco/v3.0/major_versions \
| jq '.result[].major_version'
25
23
... (中略) ...
7
6
# Apple Siliconで動くJava9パッケージを取得してみる
$ curl 'https://api.foojay.io/disco/v3.0/packages?version=9&architecture=aarch64' \
| jq '.result[].links.pkg_download_redirect'
"https://api.foojay.io/disco/v3.0/ids/XXXXXXXXXXX/redirect"
"https://api.foojay.io/disco/v3.0/ids/YYYYYYYYYYY/redirect"
# 示されたURLからパッケージをダウンロード
$ curl -L -O -J "https://api.foojay.io/disco/v3.0/ids/XXXXXXXXXXX/redirect"
# 内容確認
$ tar -tzf OpenJDK9U-jre_aarch64_linux_hotspot_9_181.tar.gz
./jdk-9+181-jre/
./jdk-9+181-jre/lib/
./jdk-9+181-jre/lib/libj2gss.so
./jdk-9+181-jre/lib/jli/
...
JDKの検索とその実行ファイルのダウンロードまで出来ました。実際には、API上ではリダイレクト用のURLがレスポンスされ、その先ではGitHub上のバイナリアーカイブのURLが案内されました。JDKディストリビューションによって最終的に案内されるホストは違うと思われます。
メジャーバージョンより細かいバージョンは build.gradle
上で指定できないのか?
指定できません。 jenv
や nodenv
みたいなものと同一視しないほうがいいでしょう。
あくまでやんわりプロジェクトごとにJavaのバージョンを指定しつつ、自動ダウンロードもしてくれる便利機能ぐらいに考え、厳密にバージョンロックする仕組みと考えないほうが吉です。
Java以外のJVM言語で同じ仕組みは使えるのか?
マニュアル によるとScala, Kotlin, Groovy にも設定は継承されるようです。
まとめ
最近のGradleにてビルドやJava実行に使うJDKを自動選定&(希望すれば)自動インストールできる機能 (Toolchain) があることを学びました。
また、自動インストールの仕組みの中で foojay Disco API というAPIを使ってJDKのダウンロードURLを取得できることが分かりました。
できればGradleの実行に使うJDK自体も自動インストールできると最高ですが、この機能だけでも開発環境セットアップについて色々な面倒は減らせるかもしれません。
(参考) 今回のソースコードを格納したリポジトリ