GradleでJavaのビルド時にJDKを動的に切り替える方法

# はじめに

OpenJDK11 が 2018 年の 9 月にリリースされました。エンタープライズな案件で Java を使うとすると JDK11 が多いのではないでしょうか。
また、すでに JDK8 を使っているけど、新しく導入される一部のプロジェクトでは JDK11 を採用したい、という声もちらほら聞きます。

今回は JDK8 を使っているが JDK11 での検証を始めたときに、Gradle でのビルドを JDK8 と JDK11 の両方に対応したい(ビルドしたい)という場合に、どのようなビルド設定がよいのか考えました。なお JDK の変更にバージョンアップに伴うアプリケーションの修正、チューニングなどは、必要事項と思いますが、本記事からは除きます。あくまでビルド時に JDK を動的に切り替える方法に焦点を当てたいと思います。

## 設定内容

### 1. `build.gradle` の `sourceCompatibility`, `targetCompatibility` でパラメータを指定

JDK のバージョンについては `build.gradle` に固定値で指定していることが多いと思います。(あるいは `gradle.properties` に記載)

以下のようなイメージです。

```groovy:build.gradle
...

sourceCompatibility = 1.8
targetCompatibility = 1.8

...
```

あるいは `gradle.properties` に値を記載しているとすると

```groovy:build.gradle
...

sourceCompatibility = javaVersion
targetCompatibility = javaVersion

...
```

```groovy:gradle.properties
...
javaVersion=1.8
...
```

というイメージです。

ビルド実行時に動的に切り替えたいので、 `build.gradle` の定義は

```groovy:build.gradle
...

sourceCompatibility = javaVersion
targetCompatibility = javaVersion

...
```

と変数で定義しておきます。

### 2. ビルド時にパラメータで JDK のバージョンと `JAVA_HOME` を設定

`gradle` の `-Pkey=value` でパラメータを指定できます。これを利用して、`gradle build -PjavaVersion=1.11` などとすればビルド実行時に JDK のバージョンを指定できます。合わせて Gradle が用いる JDK を指定する必要があります。これは `org.gradle.java.home` パラメータで指定することができます。

よって

```
gradle build -PjavaVersion=1.11 -Dorg.gradle.java.home="C:\java\11 --info"
```

のようにすれば良いのではないでしょうか。

## 確認

### プロジェクト構成

以下のようなプロジェクト構成にしておきます。

```
C:
└─src
    └─main
       ├─java
       │  └─sample
       │      └─gradle
       │          └─multiversion
       │              Sample.java
       └─resources
```

```groovy:build.gradle
plugins {
    id 'java-library'
}

repositories {
    jcenter()
}

dependencies {
}

sourceCompatibility = javaVersion
targetCompatibility = javaVersion
```

### JDK11 でビルド & 確認

JDK11 でビルドします。

```
>gradle build -PjavaVersion=1.11 -Dorg.gradle.java.home="C:\java\11"

BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed
```

実際にコンパイルされた class ファイルについて `javap -v` でビルドされたバージョンを確認します。

以下の実行結果からわかるように `major version: 55` と JDK11 でビルドされていることがわかります。

```
C:\workspace\sample-gradle-multiversion>javap -v build\classes\java\main\sample\gradle\multiversion\Sample.class
Classfile /C:/workspace/sample-gradle-multiversion/build/classes/java/main/sample/gradle/multiversion/Sample.class
  Last modified 2019/07/12; size 576 bytes
  MD5 checksum 580cd2384cb91a31c14e09339617ba75
  Compiled from "Sample.java"
public class sample.gradle.multiversion.Sample
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #5                          // sample/gradle/multiversion/Sample
  super_class: #6                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // Hello world.
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // sample/gradle/multiversion/Sample
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lsample/gradle/multiversion/Sample;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               Sample.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               Hello world.
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               sample/gradle/multiversion/Sample
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public sample.gradle.multiversion.Sample();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lsample/gradle/multiversion/Sample;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello world.
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 10: 0
        line 12: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "Sample.java"
```

JDK 11 でビルドした class ファイルを削除するために `gradle clean` しておきます。

```
C:\workspace\sample-gradle-multiversion>gradle clean -PjavaVersion=1.11

BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
```

### JDK8 でビルド & 確認

次に JDK8 でビルドします。

```
C:\workspace\sample-gradle-multiversion>gradle build -PjavaVersion=1.8 -Dorg.gradle.java.home="C:\java\8"

BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed
```

class ファイルを確認します。 `major version: 52` と JDK8 でビルドされていることがわかります。

```
C:\workspace\sample-gradle-multiversion>javap -v build\classes\java\main\sample\gradle\multiversion\Sample.class
Classfile /C:/workspace/sample-gradle-multiversion/build/classes/java/main/sample/gradle/multiversion/Sample.class
  Last modified 2019/07/12; size 576 bytes
  MD5 checksum ea23dd24700da60b773d93f8ff2f9d77
  Compiled from "Sample.java"
public class sample.gradle.multiversion.Sample
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #5                          // sample/gradle/multiversion/Sample
  super_class: #6                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // Hello world.
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // sample/gradle/multiversion/Sample
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lsample/gradle/multiversion/Sample;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               Sample.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               Hello world.
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               sample/gradle/multiversion/Sample
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public sample.gradle.multiversion.Sample();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lsample/gradle/multiversion/Sample;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello world.
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 10: 0
        line 12: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "Sample.java"
```

## 結論

1. `build.gradle` の `sourceCompatibility`, `targetCompatibility` に変数で定義
2. Gradleのビルド時にパラメータ `-P` で JDK のバージョンと `-Dorg.gradle.java.home` で `JAVA_HOME` を設定