Gradle 5.0 に備えて annotationProcessor について調べる

# 非推奨の警告
最近の Gradle （自分の環境は、 4.10.2）で注釈処理を利用していると、次のようなメッセージが出る。

```
> gradle compileJava
...

Deprecated Gradle features were used in this build, making it incompatible with Gradle 5.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/4.10.2/userguide/command_line_interface.html#sec:command_line_warnings
```

Gradle 5.0 で非互換になる機能を利用しているので、詳細を確認したければ `--warning-mode all` オプションを指定しろとのこと。

オプションを指定して実行すると、次のように出力される。

```
> gradle compileJava --warning-mode all
...

Detecting annotation processors on the compile classpath has been deprecated.
Gradle 5.0 will ignore annotation processors on the compile classpath.
The following annotation processors were detected on the compile classpath: 
'combined.apt.CombinedProcessor' and 
'separated.apt.SeparatedProcessor'. 
Please add them to the annotation processor path instead.
If you did not intend to use annotation processors, you can use the '-proc:none' compiler argument to ignore them.
```

なんでも、 Gradle 5.0 からはアノテーションプロセッサをコンパイル時のクラスパスから取得する方法がサポートされなくなるらしい。
試しに [5.0 の RC 版](https://gradle.org/release-candidate/) で実行したら、確かに無視されるようになっていた。

代わりに、 `annotation processor path` に指定しろとのこと。
もしくは、別に注釈処理を使っていないプロジェクトであれば、 `-proc:none` をコンパイルオプションに追加しろとのこと。

[Gradle 5.0: Planned behaviour of ignoring jars with annotation processors not okay. · Issue #5056 · gradle/gradle](https://github.com/gradle/gradle/issues/5056)

こちらの Issue に書き込まれているコメントによると、インクリメンタルな注釈処理（incremental annotation processors）をサポートするのが理由らしい（よくわかってない）。


# 対応方法
[Convenient declaration of annotation processor dependencies | Gradle 4.6 Release Notes](https://docs.gradle.org/4.6/release-notes.html#convenient-declaration-of-annotation-processor-dependencies)

Gradle 4.6 の時点で Java プラグインを入れると `annotationProcessor` という configuration が設定できるようになっている。

[Dependency configurations | The Java Plugin](https://docs.gradle.org/4.10.2/userguide/java_plugin.html#sec:java_plugin_and_dependency_management)

注釈処理を提供する依存ライブラリは、 `implementation` ではなく `annotationProcessor` に指定すれば良い。

ちなみに、 `annotationProcessor` は main のコード用の configuration なので、 test のコードに対して注釈処理を適用したい場合は `testAnnotationProcessor` に指定する。

**Android の annotationProcessor**

ググってると、[Android Plugin for Gradle 3.0.0 へのマイグレーションの話](https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration?hl=ja#annotationProcessor_config) でも同じように `annotationProcessor` が出てくるけど、これはまた別物？（Android 事情に疎い）

少なくとも、 Gradle の JavaPlugin に `annotationProcessor` が入ったのは [2017年12月15日のコミットっぽい](https://github.com/gradle/gradle/commit/f71d8d69a1f1e031d6ffd105664da0ce9440f04b#diff-83f0eaa4ff604af6ab3b1bcc44250720)。Android の方は 3.0.0 のリリースが [2017年10月っぽい](https://developer.android.com/studio/releases/gradle-plugin#3-0-0)ので、 Android の方が先？


## 注釈処理の提供方法によって対応方法が変わる（と思う）
実際にいろいろ試してみて気づいたが、単に注釈処理といっても、その提供方法などによって、ちょっと指定方法が変わってくると思う（個人的な試行錯誤の結果たどり着いた結論なので、正しさの保証はなし）。

分類すると、次のような感じで分かれる気がする。

- ライブラリ本体と注釈処理が別々の jar で提供されている
- ライブラリ本体と注釈処理が同じ jar で提供されている

## 検証用の実装
```text:フォルダ構成
|-build.gradle
|-settings.gradle
|-combined/
| `-src/main/
|   |-java/
|   | `-combined/
|   |   |-apt/
|   |   | `-CombinedProcessor.java
|   |   `-core/
|   |     `-Combined.java
|   `-resources/
|     `-META-INF/services/
|       `-javax.annotation.processing.Processor
|
|-separated/
| |-apt/
| | `src/main/
| |  |-java/
| |  | `-separated/apt/
| |  |   `-SeparatedProcessor.java
| |  `-resources/
| |    `-META-INF/services/
| |      `-javax.annotation.processing.Processor
| `-core/
|   `-src/main/java/
|     `-separated/core/
|       `-Separated.java
|
`-main/
  `-src/
    |-main/java/
    | `-main/
    |   `-Foo.java
    `-test/java/
      `-main/
        `-FooTest.java
```

- ３つのマルチプロジェクトから成る
    - `combined`
        - 本体と注釈処理を１つの jar で提供するプロジェクト
    - `separated`
        - 本体と注釈処理を異なる jar で提供するプロジェクト
    - `main`
        - それぞれの注釈処理を利用しているプロジェクト
- `CombinedProcessor.java` と `SeparatedProcessor.java` が、それぞれ注釈処理を実装したクラス
- `Combined.java` と `Separated.java` が、それぞれ処理対象となるアノテーション
- `Foo.java` は、両方のアノテーションを設定して処理対象になるようにしている

```java:Foo.java
package main;

import combined.core.Combined;
import separated.core.Separated;

@Combined
@Separated
public class Foo {}
```

- `build.gradle` は次のような感じ

```gradle:build.gradle
subprojects {
    apply plugin: "java"
    sourceCompatibility = 11
    targetCompatibility = 11
    compileJava.options.encoding = "UTF-8"
}

project(":separated:apt") {
    dependencies {
        implementation project(":separated:core")
    }
}

project(":main") {
    dependencies {
        ... // ここの設定は後述
    }
}
```


## ライブラリ本体と注釈処理が別々の jar で提供されている
この場合は、 `implementation` [^1] に本体の jar を、 `annotationProcessor` に注釈処理用の jar を指定すればいい。

[^1]: コンパイル時のみ必要なライブラリの場合は `compileOnly` に設定する

```gradle:build.gradle
project(":main") {
    dependencies {
        implementation project(":separated:core")
        annotationProcessor project(":separated:apt")
    }
}
```

```text:検証
> gradle :main:build --warning-mode all
...

> Task :main:compileJava
注意:SeparatedProcessor!!
注意:SeparatedProcessor!!

BUILD SUCCESSFUL in 4s
```


警告なく完了する。
※`CombinedProcessor` の方は一時的に依存や実装を外している

このパターンに該当するライブラリとしては、公式ドキュメントで紹介されている [Dagger](https://github.com/google/dagger) などが該当する。

## ライブラリ本体と注釈処理が同じ jar で提供されている
この場合は、 `implementation` と `annotationProcessor` の両方に同じ依存対象を指定する。

```gradle:build.gradle
project(":main") {
    dependencies {
        implementation project(":combined")
        annotationProcessor project(":combined")

        ...
    }
}
```

```text:実行結果
> gradle :main:build --warning-mode all
...

> Task :main:compileJava
注意:CombinedProcessor!!
注意:SeparatedProcessor!!
注意:CombinedProcessor!!
注意:SeparatedProcessor!!

> Task :main:compileTestJava
Detecting annotation processors on the compile classpath has been deprecated. Gradle 5.0 will ignore annotation processors on the compile cl
asspath. The following annotation processors were detected on the compile classpath: 'combined.apt.CombinedProcessor'.  Please add them to t
he annotation processor path instead. If you did not intend to use annotation processors, you can use the '-proc:none' compiler argument to
ignore them.

BUILD SUCCESSFUL in 4s
```

警告が出た。
よく見ると、 `compileTestJava` のタスク内で警告が出力されている（`compileJava` の方は警告なしで完了している）。

[Java プラグインのドキュメント](https://docs.gradle.org/4.10.2/userguide/java_plugin.html#tab:configurations) に、各 configuration の依存関係が図解されている。
今回関係する部分だけを抽出すると、次のようになっている（赤いのは非推奨の configuration）。

![annotationProcessor.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/55076814-3386-f70a-56f4-648f63e59847.jpeg)

`implementation` に指定した依存関係は、そのまま `testCompileClasspath` でも利用される。
従って、テストのときにクラスパスから注釈処理が検出されて警告が出てしまっているっぽい。

どうすべきか？

### コンパイル時だけ必要な場合
jar が必要なのがコンパイル時のみであれば、 `implementation` の代わりに `compileOnly` を使うことで警告表示を回避できる（というか、その方が設定として正しい気がする）。
例えば、 [Lombok](https://projectlombok.org/) がこれに該当する。

```gradle:build.gradle
dependencies {
    compileOnly project(":combined")
    annotationProcessor project(":combined")

    ...
}
```

```text:実行結果
> gradle :main:build --warning-mode all
...

> Task :main:compileJava
注意:CombinedProcessor!!
注意:SeparatedProcessor!!
注意:CombinedProcessor!!
注意:SeparatedProcessor!!

BUILD SUCCESSFUL in 5s
```

警告は出なくなった。

### 無視する
テストコードで注釈処理を利用していないのであれば、警告は無視するのも選択肢としてありかなとも思う。
結局、 5.0 からは無視されるのであれば、 5.0 に上げた時点で警告も出なくなり、動きとしても問題ない状態に落ち着きそうな気がする。

一応 5.0 の RC 版で試したら、警告はなくなり main の方の注釈処理だけが動いてくれた。

```text:5.0RCで動かした結果
> gradle --version
------------------------------------------------------------
Gradle 5.0-rc-1
------------------------------------------------------------

...

> gradle :main:build
...

> Task :main:compileJava
注意:CombinedProcessor!!
注意:SeparatedProcessor!!
注意:CombinedProcessor!!
注意:SeparatedProcessor!!

BUILD SUCCESSFUL in 4s
```


### 実行時も必要な場合
実行時も依存対象が必要な場合は、 `compileOnly` の指定は利用できない。
例えば、 [Doma2](https://github.com/domaframework/doma) がこれに該当する。

こうなると、テストコードで出る警告は無視するのが一番良い気もする。
しかし、どうしても無視できないのであれば、最初の警告メッセージで出ていたコンパイルオプションにアノテーションプロセッサを無効にする指定を追加する方法を取ることになると思う。

```gradle:build.gradle
dependencies {
    implementation project(":combined")
    annotationProcessor project(":combined")

    ...
}

compileTestJava {
    // テスト時のコンパイルオプションに `-proc:none` を追加
    options.compilerArgs += ["-proc:none"]
}
```

```text:実行結果
> gradle :main:build --warning-mode all
...

> Task :main:compileJava
注意:CombinedProcessor!!
注意:SeparatedProcessor!!
注意:CombinedProcessor!!
注意:SeparatedProcessor!!

BUILD SUCCESSFUL in 5s
```

警告は出ない。