Gradleの依存関係解決がSpringBootプラグインのせいでなんだかいうことを聞いてくれない時の一例

# 概要

このエントリでは、Gradleを使っているときに、依存性解決がなんだかうまくいかない時の一例、について紹介します。


[Enterprise電卓を作る Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ehw_calculator)の12/3のエントリです。

# 想定読者

- Gradleを使っていて、SpringBootを使っているときに、依存性解決において、サードパーティーのライブラリがなんだか古いバージョンが使用されるんだが、という経験がある方。

- JLine3をSpringBootで使うとき、WindowsプラットフォームでJNAで起動しようとしているときに困っている方。

# 事象

## ログ

[Javaでコンソールでのキー入力を１文字ずつハンドルしたいときにJLineを使う](https://qiita.com/hrkt/items/885f1c3526af03939c54)のエントリを書いていた際、下記のようなエラーが出ました。こういった事象に対応するための手段の一つを紹介します。

![JNA_error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65002/a19229ef-f321-360d-d7a4-97a049ce1845.png)

このログを見るに、

```
        Suppressed: java.lang.NoSuchMethodError: com.sun.jna.Native.load(Ljava/lang/String;Ljava/lang/Class;Ljava/util/Map;)Lcom/sun/jna/Library;
```

ということで、利用しているライブラリの中で、特定のクラスにメソッドがないんだけど、という風に読めます。

Gradleを使って依存性解決をお任せしているのになんじゃこれ、ということになりますが、そういうことになっているので仕方がありません。

## 確認

Gradleで、プロジェクトが利用しているライブラリの確認コマンドは、下記です。

```bash
gradle dependencies 
```

例えば、下図のようなシンプルなbuild.gradleを用意して、

```gradle

plugins {
	id 'java'
}

repositories {
	mavenCentral()
}

dependencies {
	implementation group: 'org.jline', name: 'jline', version: '3.12.1'
	implementation group: 'org.jline', name: 'jline-terminal-jna', version: '3.12.1'
}
```

実行すると、下図のようなツリーが表示されます。それぞれのタスクにおいて使用されるライブラリがわかります。

```

> Task :dependencies

------------------------------------------------------------
Root project
------------------------------------------------------------

annotationProcessor - Annotation processors and their dependencies for source set 'main'.
No dependencies

apiElements - API elements for main. (n)
No dependencies

archives - Configuration for archive artifacts.
No dependencies

compile - Dependencies for source set 'main' (deprecated, use 'implementation' instead).
No dependencies

compileClasspath - Compile classpath for source set 'main'.
+--- org.jline:jline:3.12.1
\--- org.jline:jline-terminal-jna:3.12.1
     +--- net.java.dev.jna:jna:5.3.1
     \--- org.jline:jline-terminal:3.12.1

compileOnly - Compile only dependencies for source set 'main'.
No dependencies

default - Configuration for default artifacts.
+--- org.jline:jline:3.12.1
\--- org.jline:jline-terminal-jna:3.12.1
     +--- net.java.dev.jna:jna:5.3.1
     \--- org.jline:jline-terminal:3.12.1

implementation - Implementation only dependencies for source set 'main'. (n)
+--- org.jline:jline:3.12.1 (n)
\--- org.jline:jline-terminal-jna:3.12.1 (n)

runtime - Runtime dependencies for source set 'main' (deprecated, use 'runtimeOnly' instead).
No dependencies

runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.jline:jline:3.12.1
\--- org.jline:jline-terminal-jna:3.12.1
     +--- net.java.dev.jna:jna:5.3.1
     \--- org.jline:jline-terminal:3.12.1

runtimeElements - Elements of runtime for main. (n)
No dependencies

runtimeOnly - Runtime only dependencies for source set 'main'. (n)
No dependencies

testAnnotationProcessor - Annotation processors and their dependencies for source set 'test'.
No dependencies

testCompile - Dependencies for source set 'test' (deprecated, use 'testImplementation' instead).
No dependencies

testCompileClasspath - Compile classpath for source set 'test'.
+--- org.jline:jline:3.12.1
\--- org.jline:jline-terminal-jna:3.12.1
     +--- net.java.dev.jna:jna:5.3.1
     \--- org.jline:jline-terminal:3.12.1

testCompileOnly - Compile only dependencies for source set 'test'.
No dependencies

testImplementation - Implementation only dependencies for source set 'test'. (n)
No dependencies

testRuntime - Runtime dependencies for source set 'test' (deprecated, use 'testRuntimeOnly' instead).
No dependencies

testRuntimeClasspath - Runtime classpath of source set 'test'.
+--- org.jline:jline:3.12.1
\--- org.jline:jline-terminal-jna:3.12.1
     +--- net.java.dev.jna:jna:5.3.1
     \--- org.jline:jline-terminal:3.12.1

testRuntimeOnly - Runtime only dependencies for source set 'test'. (n)
No dependencies

(n) - Not resolved (configuration is not meant to be resolved)

A web-based, searchable dependency report is available by adding the --scan option.

BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed

```

これを見ると、org.jline:jline-terminal-jna:3.12.1がnet.java.dev.jna:jna:5.3.1に依存しているので、それをGradleが使えるようにしてくれることを示します。

# 目の前の事象

さて、ここでプラグインを2つ追加してみます。[SpringInitializr](https://start.spring.io/)でSpringBootのプロジェクトを作った時に追加されているものです。
build.gradleの全体は下図のようになります。

```gradle

plugins {
	id 'org.springframework.boot' version '2.2.1.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

repositories {
	mavenCentral()
}

dependencies {
	implementation group: 'org.jline', name: 'jline', version: '3.12.1'
	implementation group: 'org.jline', name: 'jline-terminal-jna', version: '3.12.1'
}
```

依存関係を確認してみましょう。

```
default - Configuration for default artifacts.
+--- org.jline:jline:3.12.1
\--- org.jline:jline-terminal-jna:3.12.1
     +--- net.java.dev.jna:jna:5.3.1 -> 4.5.2
     \--- org.jline:jline-terminal:3.12.1


runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.jline:jline:3.12.1
\--- org.jline:jline-terminal-jna:3.12.1
     +--- net.java.dev.jna:jna:5.3.1 -> 4.5.2
     \--- org.jline:jline-terminal:3.12.1
```

あれ、、、net.java.dev.jna:jna:5.3.1 -> 4.5.2となり、古いバージョンのものを参照してしまっています。

この状況はプラグインの動作のためらしく、Gradleのコミュニティサイトでも[やり取り](https://discuss.gradle.org/t/project-dependency-picks-up-a-lower-version/19747/4)がありました。以下引用。

> However, as you already suspected, Spring Gradle plugins do configure the dependency resolution by providing their own dependency management plugin:
https://github.com/spring-gradle-plugins/dependency-management-plugin 84.
Using the spring boot plugin, you need to use that, but you can reconfigure dependencies there 

# 暫定対策

将来はこのあたりもうまくいってくれることを願いつつ、暫定対策で自分でdependencyを一つ追加します。build.gradleは下図のようになります。

```gradle
plugins {
	id 'org.springframework.boot' version '2.2.1.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

repositories {
	mavenCentral()
}

dependencies {
	implementation group: 'org.jline', name: 'jline', version: '3.12.1'
	implementation group: 'org.jline', name: 'jline-terminal-jna', version: '3.12.1'
    implementation group: 'net.java.dev.jna', name: 'jna', version: '5.3.1'

}

```

これにより、意図したバージョンのライブラリが使われるようになりました。

```
default - Configuration for default artifacts.
+--- org.jline:jline:3.12.1
+--- org.jline:jline-terminal-jna:3.12.1
|    +--- net.java.dev.jna:jna:5.3.1
|    \--- org.jline:jline-terminal:3.12.1
\--- net.java.dev.jna:jna:5.3.1

```

これで冒頭のエラーがなくなりました。

# おわりに

このエントリでは、Gradleの依存解決で、うまくいかないときの対処の一例として、依存関係の確認の仕方と、利用ライブラリを上書きでしていするやりかたをご紹介しました。