この広告は、90日以上更新していないブログに表示しています。
とある Gradle Plugin を 2.0.0 に移行する際、v1 から KotlinDSL を使っていた人の環境でちょっと問題が発生したというツイートを見たので、KotlinDSL がどうやってDSL Marker なしに lambda で書けるようにしてるのかちょっと調べてみた。ここで記述している問題は 2.0.1 では修正されていて、また KotlinDSL での移行ステップも README に追記しておきました。
kotlin-dsl を apply して開発しないと Groovy と KotlinDSL で同じような Closure (Lambda) の記法に出来ない(多分) (2/25 追記: DomainObjectでない場合)v1 では動いていた以下の記述が 2.0.0 だと動かず、 2.0.0 から導入されたdeployments であれば動くという問題が発生していた。
deploygate { apks { ... }}これは下記の通りに書き換えると apks 記述でも動く。
deploygate { apks(closureOf<NamedDomainObjectContainer<NamedDeployment>> { ... }}def apks(Closure) を呼び出そうとするが、渡している lambda と Closure の間で型変換が不可能であるので発生していた。これはclosureOf を使い、型情報も明示的に提供することで解決できる。が、普通に lambda で書きたいですよね。
じゃあそもそも v1 ではdef apks(Closure) と書いてなかったのか?というと、ちゃんと(?)def apks(Closure) としていた。実は KotlinDSL で closureOf などを使わずに lambda で書けている場合、この Closure 関数が直接呼ばれているわけではない。
Groovy ではapks { ... } はdef apks(Closure) で動くし、この Closure を引数に取る関数が実際に呼ばれているんだけど、KotlinDSL においてapks { ... } はgetApks().invoke(...) の糖衣構文となっており、これは KotlinDSL が定義する拡張関数によって実現されていた。
ということで差異はgetApks().invoke(...) の糖衣構文であるところに由来していた。v1 ではgetApks() に NamedDomainObjectContainer がちゃんと指定されていたんだけど、2.0.0 ではリファクタリングの過程でdef を使って動的解決に変更してしまったことが原因だった。 そうすると Object 型として認識されてしまう。そして最終的に Kotlin は「def apks(Closure) を呼ぼうとしてるけど型が一致してないよ」とエラーを吐くという感じ*1。 ref:v1 /v2
ということで、
apks(Closure) は Groovy でapks { ... } と書く用途getApks(): <concrete type> は KotlinDSL でapks { ... } と書く用途Kotlin でも Groovy でも Property アクセスがサポートされているので、configuration ではメソッド呼び出しではなく代入文の書き方で表現できる
propName = "..."
という感じなんだけど、 Kotlin と Groovy の言語仕様の関係で、以下の記述が KotlinDSL だと v2 で動いてなかった。
deploygate { userName = "userName"}Kotlin では Setter の可視性は Getter よりも広くなければならない。つまり private getter + public setter のような property は存在せず、この場合はsetFoo(...) という呼び出しを強いられる。
Groovy はそんな制約はなく、private getter + public setter のような property でもfoo = ... と書ける。
今回 2.0.0 では deprecated とした設定値を全て public setter で定義し、新しい設定値の property にdelegate している。その結果、v1 ではただの public property だったものが Kotlin 上では property 扱いとならなくなってしまった。なお Groovy では動くので、Groovy 用の後方互換テストケースでは気づけなかった。
これは getter を定義するだけで修正できる。
2022/10/25 追記。実行側のGradle バージョン依存の動作差異(Kotlin 1.3 compatibility)でした
2.0.0 では配布ページ(英名: Distribution) の設定について、distribution ブロックを用意して切り出した。
deployments { create("debug") { distribution { ... } }}これは KotlinDSL だと最初の項lambda と Closure の型変換 と同じ理由で、そのままでは動かない。このdistribution は NamedDomainObjectContainer ではないので、invoke による補助が使えない。下記のように closureOf + 型指定でも動くけど、型を明示的に書いてもらうと今後のアップデートに支障が出るので正直避けたい。
deployments { create("debug") { distribution(closureOf<Distribution> { ... }) }}さて、KotlinDSL で推奨されているorg.gradle.api.Action にすれば動くんじゃない?って思ったけど今度は Groovy 側で落ちるようになった。caller (owner) が Distribution lambda の this になってしまって見つからないらしい。
- Marks a SAM interface as a target for lambda expressions / closures
- where the single parameter is passed as the implicit receiver of the
- invocation ({@code this} in Kotlin, {@codedelegate} in Groovy) as if
- the lambda expression was an extension method of the parameter type.
ドキュメントを見た感じ、delgate に差し込んでくれるんじゃないの?と思って悩んでたんだけど、そもそも Gradle Plugin 開発時にkotlin-dsl plugin (というか Kotlin +SAML w/ receiver compiler plugin) を当てないとここらへんの機能提供ができないっぽい。なんとなく Kotlin で Gradle 設定を書く方をKotlinDSLと呼ぶ印象があって、Plugin 開発側では何も考えていなかった。
試そうと思って KotlinDSL を入れたら Kotlin コードから Groovy のコードが見れず、凄い長い戦いになりそうだったのでやめました。詳しい人教えてください。
ここに KotlinDSL がDSL として動くように拡張関数が定義されており、かなり楽しい。 うーん、Configurable#invoke 生やしてくれないかなー
https://github.com/gradle/kotlin-dsl/tree/master/subprojects/provider/src/main/kotlin/org/gradle/kotlin/dslgithub.com
Action がなんで this を解決できるかについてはSAM-with-receiver Kotlin compiler plugin を調べればOK。kotlin/build.gradle.kts at master · JetBrains/kotlin · GitHub
そういえばGroovy -> KotlinDSL への書き換えについては DroidKaigi 2019 で tnj という人が書き換えについて発表をしています。
スライド中で触れられている「Gradle のファイルっていつの間にか増えるよね」で増やした張本人は僕です。
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。