公式ドキュメントを試してGradleと仲良くなろうシリーズ、今日はこちら。
Gradleのバージョンは7.1.0を利用している。
[シンプルなGradleプラグイン]
シンプルなGradleプラグインは、build.gradle.kts
ファイルに直接実装することができる。
まずはいつものようにgradle init
コマンドでGradleプロジェクトを作成する。
$ gradle init Starting a Gradle Daemon (subsequent builds will be faster) Select type of project to generate: 1: basic 2: application 3: library 4: Gradle plugin Enter selection (default: basic) [1..4] 2 Select implementation language: 1: C++ 2: Groovy 3: Java 4: Kotlin 5: Scala 6: Swift Enter selection (default: Java) [1..6] 4 Split functionality across multiple subprojects?: 1: no - only one application project 2: yes - application and library projects Enter selection (default: no - only one application project) [1..2] 1 Select build script DSL: 1: Groovy 2: Kotlin Enter selection (default: Kotlin) [1..2] 2 Project name (default: gradle-simple-plugin): Source package (default: gradle.simple.plugin): > Task :init Get more help with your project: https://docs.gradle.org/7.1/samples/sample_building_kotlin_applications.html BUILD SUCCESSFUL in 30s 2 actionable tasks: 2 executed
./app/build.gradle.kts
ファイルが作成されたので、ここにGradleプラグインを実装してみる。Gradleプラグインはorg.gradle.api.Plugin<T>
インターフェースを実装したクラスとなる。
プラグインの処理はapply()関数に実装する。
Pluginインターフェースを実装したクラスのボディでaとだけタイプすると、apply()関数がサジェストされた。便利。
サジェストされたapply()関数を選択すると、下の画像のような仮実装がされた。
TODO()関数は、常にエラーを発生させるらしい。恐ろしい。
公式ドキュメントに従ってTODOを消化する。
この時点で、helloタスクを実行しようとすると次のようにエラーが発生する。
$ ./gradlew -q hello FAILURE: Build failed with an exception. * What went wrong: Task 'hello' not found in root project 'gradle-simple-plugin'. Some candidates are: 'help'. * Try: Run gradlew tasks to get a list of available tasks. Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 2s
プラグインを実装しただけではダメで、プラグインを適用する設定が必要らしい。
適当にサジェストに従って、実装を進めていく。自作プラグインもちゃんとサジェストされる。賢いぞGradle(むしろIntelliJ IDEAのほうか?)。
applyは関数なので関数呼び出しが必要。
完成。
再度実行してみると、今度はうまくいった!
$ ./gradlew -q hello Hello from GreetingPlugin!!
ところで、ドキュメントで指示された「-q」オプションについて知らなかったので、gradle --help
コマンドを実行して、オプションをリストアップすると、以下のように書かれていた。
-q, --quiet Log errors only.
つぎは、コードを若干修正して実行してみる。出力されるメッセージに!!
を追加した。
タスクを実行してみると、すぐに修正が反映された!🙌
$ ./gradlew hello > Task :app:hello Hello from GreetingPlugin!! BUILD SUCCESSFUL in 2s 1 actionable task: 1 executed
今回はPlugin<Project>とProjectを型引数に指定したが、他にもSettings型やGradle型を指定できるらしい。
Settings型の場合settings script(多分settings.gradleまたはsettings.gradle.ktsのこと?)で利用でき、Gradle型の場合初期化スクリプトで利用できるらしい。初期化スクリプトってなんだろう……。
Hello Worldレベルの出力ではあまり意味はないが、これを発展させると、パッケージング、静的解析、コード署名など様々なプラグインを実装できる(はずだ)。
実装は相当難しそうだけど……。
[Gradleプラグインの設定]
プラグインを利用する際の設定を保持することもできる。
同じbuild.gradle.ktsファイルにGreetingPlugin2を実装していく。
まずは、プラグインの拡張設定クラスを公式ドキュメントに従って実装する。
Property.convention()
ってなんだろう、とおもったら設定されない場合のデフォルト値を提供するものらしい。なるほど。
プラグイン本体の実装と、プラグインの適用、拡張設定を行うとコードの全体は次のようになる。
the
ってなんだろう、typoかな?と思ったらちゃんとそういう関数があるらしい。プラグインの設定を取得するための関数だそうな。
ただしドキュメントによると、Gradle 8でconventionの方は廃止になるらしい。Extensionの方は使い続けても大丈夫ってことなのかな?Gradle 7の公式ドキュメントでもExtensionの説明をしてくれてるわけだから、多分大丈夫なんだろう。
拡張設定を追加したhello2タスクを実行してみる……と、以下のようにエラーとなった。
$ ./gradlew hello2 FAILURE: Build failed with an exception. * Where: Build file '/(中略)/kotlin-sandbox/gradle-simple-plugin/app/build.gradle.kts' line: 82 * What went wrong: Failed to apply plugin class 'Build_gradle$GreetingPlugin2'. > Could not create plugin of type 'GreetingPlugin2'. > The constructor for type Build_gradle.GreetingPlugin2 should be annotated with @Inject. * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 1s
GreetingPlugin2
クラスのコンストラクタに@Injectアノテーションが必要らしい。よくわからない。
適当にググって見つけた記事に従って、コンストラクタに@Injectアノテーションを追加してみた。
しかし、やはりエラーとなる。
$ ./gradlew -q hello2 FAILURE: Build failed with an exception. * Where: Build file '/(中略)/kotlin-sandbox/gradle-simple-plugin/app/build.gradle.kts' line: 141 * What went wrong: Failed to apply plugin class 'Build_gradle$GreetingPlugin2'. > Could not create plugin of type 'GreetingPlugin2'. > 0 (略)
0ってなんだろうと思い、スタックトレースを出力してみると、ArrayIndexOutOfBoundsException
が発生しているようだった。
$ ./gradlew -q hello2 --stacktrace FAILURE: Build failed with an exception. (略) * Exception is: org.gradle.api.internal.plugins.PluginApplicationException: Failed to apply plugin class 'Build_gradle$GreetingPlugin2'. at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:173) (略) at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) Caused by: org.gradle.api.plugins.PluginInstantiationException: Could not create plugin of type 'GreetingPlugin2'. at org.gradle.api.internal.plugins.DefaultPluginManager.instantiatePlugin(DefaultPluginManager.java:83) (略) at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:166) ... 156 more Caused by: java.lang.ArrayIndexOutOfBoundsException: 0 at org.gradle.internal.instantiation.generator.DependencyInjectingInstantiator.addServicesToParameters(DependencyInjectingInstantiator.java:167) at org.gradle.internal.instantiation.generator.DependencyInjectingInstantiator.convertParameters(DependencyInjectingInstantiator.java:121) at org.gradle.internal.instantiation.generator.DependencyInjectingInstantiator.doCreate(DependencyInjectingInstantiator.java:62) at org.gradle.internal.instantiation.generator.DependencyInjectingInstantiator.newInstance(DependencyInjectingInstantiator.java:55) at org.gradle.api.internal.plugins.DefaultPluginManager.instantiatePlugin(DefaultPluginManager.java:81) ... 173 more * Get more help at https://help.gradle.org BUILD FAILED in 710ms
ググると、以下のIssueが見つかった。1年以上前のIssueが放置されているようだ。公式ドキュメントの例が動かないんだから早いところ直して欲しいものだ……。
GradleもOSSなので、これくらい直せるかな?と思ったらDependencyInjectingInstantiator#addServicesToParameters()
がプライベートメソッドでテストが書きづらそうな感じだったので泣く泣く断念……。
そもそもこのあたりのテストがGroovyで実装されててGroovyわからん……となったのもある。
Twitterにも流してみたが、特に反応がなかったのでみんな困ってないのかしら……。
https://t.co/gRHicyxJya
— KIDANI Akito (@kdnakt) 2021年7月9日
Trying to build a gradle plugin but got stuck with Example2... 😭
> The constructor for type Build_gradle.GreetingPlugin2 should be annotated with @Inject.
Looks like related to this open issue ... over 1 year ago...https://t.co/EbPI2jMecT