Gradle の compile, api, implementation とかについて

Gradle の `dependencies` で指定する `compile`, `api`, `implementation` についての勉強メモ。

# compile は非推奨
Gradle 3.4 で [Java Library Plugin](https://docs.gradle.org/current/userguide/java_library_plugin.html) が[追加された](https://docs.gradle.org/3.4/release-notes.html#the-java-library-plugin)ことで、 `dependencies` で `compile` を使用することは非推奨となってたらしい(`runtime`, `testCompile`, `testRuntime` も)。

4.7 の [Java Plugin の説明](https://docs.gradle.org/4.7/userguide/java_plugin.html#sec:java_plugin_and_dependency_management)では、ガッツリ `Deprecated` と書かれている。
（[4.6 のドキュメント](https://docs.gradle.org/4.6/userguide/java_plugin.html#sec:java_plugin_and_dependency_management)だと `Deprecated` って書いてないけど、明確に非推奨となったのは最近？）

代わりに `implementation` と `api` を使用することが推奨されている。

# compile と implementation の違い
## compile の場合
**実装**

```text:プロジェクト構成
|-settings.gradle
|-build.gradle
|
|-foo/
|  |-build.gradle
|  `-src/main/java/foo/
|    `-Foo.java
|
`-bar/
   |-build.gradle
   `-src/main/java/bar/
     `-Bar.java
```

---

```groovy:/settings.gradle
include 'foo', 'bar'
```

```groovy:/build.gradle
subprojects {
    apply plugin: 'java'

    sourceCompatibility = 10
    targetCompatibility = 10

    compileJava.options.encoding = 'UTF-8'

    repositories {
        mavenCentral()
    }
}
```

---

```groovy:/bar/build.gradle
dependencies {
    compile 'org.apache.commons:commons-lang3:3.7'
}
```

```java:Bar.java
package bar;

import org.apache.commons.lang3.RandomStringUtils;

public class Bar {

    public void hello() {
        System.out.println("Bar: " + RandomStringUtils.random(10, "0123456789"));
    }
}
```

---

```groovy:/foo/build.gradle
apply plugin: 'application'

mainClassName = 'foo.Foo'

dependencies {
    compile project(':bar')
}
```

```java:Foo.java
package foo;

import bar.Bar;
import org.apache.commons.lang3.RandomStringUtils;

public class Foo {

    public static void main(String... args) {
        new Bar().hello();
        System.out.println("Foo: " + RandomStringUtils.random(10, "0123456789"));
    }
}
```

- `foo`, `bar` という２つのサブプロジェクトから成る
- `bar` は `commons-lang3` に依存している
- `foo` は `bar` プロジェクトに依存している
- それぞれのプロジェクトで `commons-lang3` を利用している

**実行結果**

```
> gradle :foo:run
Bar: 3803159716
Foo: 6423224304
```

## implementation を使った場合
**実装**

```groovy:/bar/build.gradle
dependencies {
    implementation 'org.apache.commons:commons-lang3:3.7'
}
```

```groovy:/foo/build.gradle
apply plugin: 'application'

mainClassName = 'foo.Foo'

dependencies {
    implementation project(':bar')
}
```

**実行結果**

```
> gradle :foo:run

...\foo\src\main\java\foo\Foo.java:4: エラー: パッケージorg.apache.commons.lang3は存在しません
import org.apache.commons.lang3.RandomStringUtils;
                               ^
...\foo\src\main\java\foo\Foo.java:10: エラー: シンボルを見つけられません
        System.out.println("Foo: " + RandomStringUtils.random(10, "0123456789"));
                                     ^
  シンボル:   変数 RandomStringUtils
  場所: クラス Foo
エラー2個

...
```

## 説明
```text:依存の関係
# compile
[foo] --compile--> [bar] --compile--> [commons-lang3]

  [foo] - ok -> [bar] - ok -> [commons-lang3]
    |                               ^
    |                               |
    +------------ ok ---------------+

# implementation
[foo] --implementation--> [bar] --implementation--> [commons-lang3]

  [foo] - ok -> [bar] - ok -> [commons-lang3]
    |                               x
    |                               |
    +------------ ng ---------------+
```

- `compile` で指定した依存関係は伝播する
    - `bar` プロジェクトで `commons-lang3` を `compile` で指定した場合、 `foo` プロジェクトで `bar` プロジェクトを依存関係に指定すると、 `foo` プロジェクトは `commons-lang3` にも依存するようになる
- `implementation` で指定した依存関係は伝播しない
    - `bar` プロジェクトで `commons-lang3` を `implementation` で指定した場合、 `foo` プロジェクトで `bar` プロジェクトを依存関係に指定しても、 `foo` プロジェクトは `commons-lang3` に依存しない（`foo` プロジェクトは `commons-lang3` を使用できない）

# 依存関係を伝播させる
**実装**

```groovy:/bar/build.gradle
apply plugin: 'java-library'

dependencies {
    api 'org.apache.commons:commons-lang3:3.7'
}
```

**実行結果**

```text
$ gradle :foo:run

Bar: 7783742303
Foo: 6741510207
```

## 説明
```groovy:/bar/build.gradle
apply plugin: 'java-library'

dependencies {
    api 'org.apache.commons:commons-lang3:3.7'
}
```

- `compile` のように依存関係を伝播させるには、 `api` で依存関係を指定する
    - `api` は Java Library Plugin を追加することで利用できるようになる
    - Java Library Plugin を使うには、 `java-library` でプラグインを追加する

# メリット
公式ドキュメントでは、 `compile` ではなく `implementation` を使うことについて、次のようなメリットがあると説明している。

1. コンパイル時に依存関係が利用側のどこにも漏れることがない。  
  従って、意図しない推移的な依存関係が発生することがない。
2. 取り除かれたクラスパスによってコンパイルがより早くなる。
3. `implementation` で指定した依存対象が変更されても、利用する側はリコンパイルの必要がない。
4. 新しい Maven プラグインと合わせて使うと、コンパイル時に必要になるライブラリと実行時に必要になるライブラリを明確に分けた POM ファイルを生成するようになり、綺麗な公開ができる（正直、 Maven プラグイン使ってないのでよくわかってない）

要するに、 `compile` を使うと次のような問題があった。

- 依存関係が全て推移的に伝播していくため、依存関係が不必要に拡大してしまっていた
    - 内部 API のような外部に漏らしたくない依存関係まで広がっていた

これを `implementation` で定義することで、依存関係の不必要な拡大を防ぎ、本当に必要な依存関係だけを `api` で推移させられるようになる。

また、 `implementation` が依存関係を伝播させなくなることで、リコンパイルの頻度も減らせられるようになるメリットがある。

# `implementation` と `api` の使い分け
以下は個人的な見解。

- 基本は `implementation` で宣言する
- どうしても利用側に伝播させないといけない場合だけ `api` で利用側に公開する
    - 可能なら自作 API でラップすることで、使用しているライブラリへの直接の依存を無くせれたらベストだと思う
        - ライブラリの差し替えが容易になる
        - 自作 API で利用方法を制限することで、意図しない使われ方・間違った利用方法を減らせられる
    - ただ、自作 API でのラップが大変で費用対効果が薄いなら `api` で公開するのもありかと思う

# まとめ
|`configuration` |依存関係の伝播|定義されているプラグイン|
|----------------|-------------|----------------------|
|`compile`       |する         |Java Plugin           |
|`implementation`|しない       |Java Plugin           |
|`api`           |する         |Java Library Plugin   |


# 参考
- [The Java Library Plugin - Gradle User Manual](https://docs.gradle.org/current/userguide/java_library_plugin.html?_ga=2.20365944.1984342920.1527163336-492720480.1523802875)