Kotlin DSL を考慮した Gradle Plugin を記述するために必要だったこと
とある Gradle Plugin を 2.0.0 に移行する際、v1 から Kotlin DSL を使っていた人の環境でちょっと問題が発生したというツイートを見たので、Kotlin DSL がどうやって DSL Marker なしに lambda で書けるようにしてるのかちょっと調べてみた。ここで記述している問題は 2.0.1 では修正されていて、また Kotlin DSL での移行ステップも README に追記しておきました。
TL;DR
- 外に見せる境界で def を使うのは避けておいた方が無難
- Kotlin DSL は拡張関数で delegate してて、見るべきメソッドが違うかもしれないから気をつけよう
- Kotlin と Gradle の言語仕様の違いに気をつけよう
kotlin-dsl
を apply して開発しないと Groovy と Kotlin DSL で同じような Closure (Lambda) の記法に出来ない(多分) (2/25 追記: DomainObjectでない場合)
lambda と Closure の型変換
v1 では動いていた以下の記述が 2.0.0 だと動かず、 2.0.0 から導入された deployments
であれば動くという問題が発生していた。
deploygate { apks { ... } }
これは下記の通りに書き換えると apks 記述でも動く。
deploygate { apks(closureOf<NamedDomainObjectContainer<NamedDeployment>> { ... } }
def apks(Closure) を呼び出そうとするが、渡している lambda と Closure の間で型変換が不可能であるので発生していた。これはclosureOf
を使い、型情報も明示的に提供することで解決できる。が、普通に lambda で書きたいですよね。
lambda での Closure と見かけ上同等の構文の実現
じゃあそもそも v1 では def apks(Closure)
と書いてなかったのか?というと、ちゃんと(?) def apks(Closure) としていた。実は Kotlin DSL で closureOf などを使わずに lambda で書けている場合、この Closure 関数が直接呼ばれているわけではない。
Groovy では apks { ... }
は def apks(Closure)
で動くし、この Closure を引数に取る関数が実際に呼ばれているんだけど、Kotlin DSL において apks { ... }
は getApks().invoke(...)
の糖衣構文となっており、これは Kotlin DSL が定義する拡張関数によって実現されていた。
ということで差異は 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>
は Kotlin DSL でapks { ... }
と書く用途
setter property アクセス
Kotlin でも Groovy でも Property アクセスがサポートされているので、configuration ではメソッド呼び出しではなく代入文の書き方で表現できる
propName = "..."
という感じなんだけど、 Kotlin と Groovy の言語仕様の関係で、以下の記述が Kotlin DSL だと 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 を定義するだけで修正できる。
lambda や Closure を受け取りたい block (not DomainObject)
2022/10/25 追記。実行側のGradle バージョン依存の動作差異(Kotlin 1.3 compatibility)でした
2.0.0 では配布ページ(英名: Distribution) の設定について、distribution
ブロックを用意して切り出した。
deployments { create("debug") { distribution { ... } } }
これは Kotlin DSL だと最初の項 lambda と Closure の型変換
と同じ理由で、そのままでは動かない。この distribution は NamedDomainObjectContainer ではないので、invoke による補助が使えない。下記のように closureOf + 型指定でも動くけど、型を明示的に書いてもらうと今後のアップデートに支障が出るので正直避けたい。
deployments { create("debug") { distribution(closureOf<Distribution> { ... }) } }
さて、Kotlin DSL で推奨されている 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, {@code delegate} 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 設定を書く方をKotlin DSLと呼ぶ印象があって、Plugin 開発側では何も考えていなかった。
試そうと思って Kotlin DSL を入れたら Kotlin コードから Groovy のコードが見れず、凄い長い戦いになりそうだったのでやめました。詳しい人教えてください。
蛇足
ここに Kotlin DSL が 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 -> Kotlin DSL への書き換えについては DroidKaigi 2019 で tnj という人が書き換えについて発表をしています。
スライド中で触れられている「Gradle のファイルっていつの間にか増えるよね」で増やした張本人は僕です。
DroidKaigi 2019 スタッフ業
今年もスタッフでした。昨年の致命傷はこちら。
今回は
- 採択後のスピーカーとの各種やり取り
- タイムテーブル周り
- アプリなどで使うAPIの作成
- 公式アプリのメンテナー(特にCI)
- 前日設営配送周り
- Day1 のホール周りの下拵え(圧倒的 keima さんと hidey さんによりホール設営自体は全てやってもらいました。感謝。)
- パーティー前移動作戦本部
- セッション部屋リーダーの補佐
- ほんのちょっとだけセッション部屋の司会
- 遊撃手
- ネットワーク(全く稼働できず)
今年は例年の致命傷を避けようとして結果即死した感じです。めっちゃミスしました。本当にすみません。
本業は同僚全員凄く理解のある人たちで助かりました(去年もですよ)。本当にありがとうございます。ただレビューや問い合わせなど自分以外の人がロックされてしまうタスクを優先しつつも、甘えすぎてもいけないので普通に開発をしたりとかなり忙しい日々でした*1。
副業も最高に理解があって、いきなり無理を言って 1月中の稼働は結局0時間とさせてもらいました。本当にありがとうございます。
でも飲み会の回数は減らしませんでした。許してください。
採択後のスピーカーとの各種やり取り
今年は問い合わせのうちのいくつかを他の担当の人にディスパッチしつつ、基本的にプログラム用問い合わせMLに流れてきたものを拾って返していました。
それでも単一障害点と化したのは間違いなくて、返信が遅くなったり二度手間を取らせてしまったこともありました。不快な気持ちになってしまった方がいらっしゃいましたら、本当に申し訳ありませんでした。プログラム用のMLでもいいのでフィードバックを頂けると嬉しいです。
タイムテーブル周り
今年はタイムテーブルを @Reyurnible と @satsukies に手伝ってもらいました。とても雑なお願いでシュッとやってくれてありがとう。戻ってきたときにGASでverificationが書いてあってとても感動しました。
さて、今年も「XXXとYYYを聞きたいのに被ってる〜〜〜!」という声をいただきました
わかる。頑張ってこの世に存在できるインスタンス増やしてください。
これは本質情報なのですが、8インスタンスあれば全てのセッションを聞きつつ、codelabsやスポンサー巡りができます。
Day1 ホール直後の移動問題 + Day1 パーティー前の移動問題
さて、昨年来た方は Day1 ホールセッション後や Day1 パーティー前の移動の惨事を覚えていらっしゃるかと思います。あれはひどい事件でしたね。
今回はそれぞれに対応をしています。一応うまくいったと思います!
Day1 ホール直後の移動問題
最初のウェルカムトーク、ホールAのセッションを終えたときの開放部屋調整を行っています。具体的には去年と異なりホールBを使えるようにし、5F で開放する部屋も減らしました。
さらに急遽、ホールAの最初のセッションを同時通訳にしてもらいました*2。元々同時通訳は完全に Room 3 だけの予定だったのですが、やはり最初のセッションは日英どちらの方も楽しんでいただきたく、僕の独断と偏見で同時通訳対象セッションをバチコーン!しました*3。発表されたTakuma さん、本当にありがとうございました。
この午前の構成ですが、僕の独断と偏見をそれっぽく解説すると
という背景を感じてまして、
を主題とするセッションで構成することで、テーマ感をある程度持たせることができたのかなと思います。多分。
Day1 パーティー前の移動問題
- Day1 最後の方のセッションを変則にし、同時に終わる部屋数を減らす
- ホールで Fireside Chat を開き、移動を促す
- 先んじてホールでウェルカムドリンクを配れるように業者(ビヨンドさん)の方にお願いする
- Fireside Chat とウェルカムドリンクを踏まえたホールレイアウトを考える
- keima 神による誘導
ということを今回は計画していました。
とはいえ僕がやったのは「関係各所の人をざっと集めまして、こんな感じの戦略で1Fに人を流していきたい、宜しくお願いします!」というざっくりとした方向性の提案だけで、実際に Fireside Chat を企画したのはcodelabs班だし、ウェルカムドリンクやバリスタさんのコントロールは全て飲食班、keimaさんはkeimaさんだし、ズラした枠にうまくセッションを当ててくれたのは僕以外のタイムテーブル班、展示ルームのコントロールはスポンサー班って感じでした*4。本当にありがとうございます。もちろん来場者の皆さんのご協力があってこそでしたので、その点も深く感謝しています!
APIや公式アプリに関して
皆さんコントリビューションありがとうございます! @takahirom さんを始めとしたコアメンバーがコントリビューションしやすいような形で整えてくれて、本当にかっこよかったです。
CIについてはどっかで発表しますが、急ぎでないときは CI やプロジェクトに依存しないように書いていたので、大部分が皆さんのプロジェクトでも動くと思います。ぜひ感想を聞かせてください。CircleCI v2.1 にするPRは後で投げます。CircleCI Orb化や Bitrise Step化も視野に入れてます。
ちなみに bot名に DroidKaigi と入れなかったことにはちゃんと理由があって、もし token を使われて罵詈雑言などを書かれた場合などにヒッジョーに面倒臭いからです。その点、僕のbotであれば(全く良くはないんですけど)まだ被害が小さいので。でもアイコンはいらすとやの最高のアイコンですよ。 (cc しほちゃん)
遊撃手
ショートではない。僕はファーストが好きです。
英語
今年も Global 班(メンションは english) という班がありまして、翻訳や proofread をお願いしてました。駄文を投げるとキレイになって返ってきて最高でした。
各セッションの英語タイトルは僕が一通り訳して、Global班に推敲してもらいました。皆さんの思惑と違うものがあったら本当に申し訳ありません。
やはり難しかったのが「投稿者がタイトルに含めたモチベーションと意味」をどれだけ維持できるかで、例えば @yanzm さんの DDD の戦術的設計
という言葉は明らかに DDD 由来なので、エヴァンス本を引っ張り出して来て訳の参考にしたりしました。他にも @shiraj_i さんの How about reviewing your Android Studio settings? は「何回かしらじさんの英語発表聞いてるけど、How about ~ って結構言うし、この言い方しそうだなー」と思いながら訳しました。
嬉しかったこと
- 問い合わせしてくれた人たちが現地で「回答(調整)ありがとう」って言ってくれた
- 前職(Quipper)のフィリピンの元同僚や入れ違いで入ったエンジニアが来てくれて、話す機会が取れた
- アプリのコントリビューターから声をかけてもらえた
- コーヒーが美味しかった
- こそこそしてたらウェルカムトークの司会から今年は逃げられた
P.S.
いま家にニトリのポールハンガーが25個あって威圧感が凄いです。(画像のダンボールの陰に同じ量のダンボールがいます)。少しの間、食費のかからない25個と1人暮らしです。
buildSrc や Gradle Plugin の開発のために Debugger を当てる
buildSrc 内のソースや Gradle Plugin の開発中でも Debugger を当てたいときはあると思います。
./gradlew --stop ./gradlew <any task> -Dorg.gradle.debug=true --no-daemon
非デーモンのデバッグモードで Gradle を立ち上げると、Debugger Attach 待機状態になる。デフォルトのポートは 5005。
IntelliJ の Run Configuration から Remote を新規作成し、デフォルトの設定で繋がる最高。
Gradle Plugin 開発の場合は大体 Project Evaluated のタイミングで終わってしまうので、Breakpoint は Debugger を Attach する前につけておきましょう。
pre-Lolipop でも TextView に icon tint を書けたい
MaterialComponent 対応をしていて、menu iconなども全部 theme baseのカラーリングにリファクタリングしている。
その中に pre-Lolipop でも TextView のicondrawable にtintが効かないというものがあったので、以下の方法で対応した。
import android.content.Context import android.os.Build import android.util.AttributeSet import androidx.appcompat.widget.AppCompatTextView import androidx.core.graphics.drawable.DrawableCompat import androidx.core.widget.TextViewCompat import com.google.android.material.theme.MaterialComponentsViewInflater class LayoutInflater : MaterialComponentsViewInflater() { // pre-L cannot apply tint color for text view drawable override fun createTextView(context: Context, attrs: AttributeSet?): AppCompatTextView { val textView = super.createTextView(context, attrs) // throwable をsuppress するだけのtry-catchを別で書いている trySafely { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { val drawables = textView.compoundDrawables if (drawables.all { it == null }) { return textView } val ta = context.theme.obtainStyledAttributes(R.styleable.AppTheme) val color = ta.getColor(R.styleable.AppTheme_drawableIconColor, -1) ta.recycle() if (color == -1) { return textView } val newDrawables = drawables.map { if (it != null) { val wrapped = DrawableCompat.wrap(it) DrawableCompat.setTint(wrapped, color) wrapped.setBounds(0, 0, wrapped.intrinsicWidth, wrapped.intrinsicHeight) wrapped } else { null } } TextViewCompat.setCompoundDrawablesRelative( textView, newDrawables[0], newDrawables[1], newDrawables[2], newDrawables[3] ) } } return textView } }
公式がやってない辺りにちゃんと理由がありそうで、とても気になる。なんでだろう。
2018 年の振り返り
自分用のメモも兼ねて
人生
月 | 内容 |
---|---|
1月末 | Quipper を退職 |
2月頭 | DroidKaigi |
2月~ | スノボいきまくる |
2月~ | 業務委託を2件持ってみて、忙殺される |
4月頭 | DeployGate に入社 |
5月 | 会社のお金で I/O |
8月 | KotlinFest のお手伝い |
8月 | 我が家納骨所になる*1 |
10月頭 | 会社のお金で Kotlin Conf (デンマーク経由・滞在) |
11月末 | JAL 修行終了 (会社のお金ではない) |
正直大体の月で忙殺されていた気がするけど、原因を具体的に覚えてない事項が多い。
2月から週2~3 の業務委託を2件契約して週5~6 で働いた時期がちょっとだけあったけれど、3日ごとくらいにコンテキストスイッチが必要になるし、日によっては1日でコンテキストスイッチを頻繁に考える必要があった。
当然効率は一定以上上がらないし、この状態で「自分から球を拾いに行くスタイル」で働くことは(少なくとも自分には)かなり厳しいことがわかったのでもう二度とやらない・・・という学びを得た
Backend (本業)
- GCM -> FCM 移行
- クレジットカード支払い周りの再設計
- Group のクレジットカード支払い対応
- サーバーサイドのローカル環境構築の自動化
- Enterprise における SAML 認証 (テスト運用中)
1個1個のタスクが大きくて、数にしたらあんまりなかった。
Android (本業)
- GCM -> FCM 移行
- CI チューニングとジョブの整備
- Crashlytics などの解析系導入と解析結果の GitHub Issues への連携
- WebTranslateIt による翻訳文書の管理
- svg -> vector drawable, optimized png の自動インポート
- Ribbonizer や git-pr-release などによる一人でもリリース間違えないもんリリースフローの整備
- Detekt, ktlint, Android Lint × Danger による一人でも寂しくないもん開発環境の構築
- Re-architect
- AndroidX と Material Components 対応 (未リリース)
弊社ァの Android リポジトリは独立時点で新しくしたことやそもそもそんなアップデートをしていなかったこともあって、入社時点で PR/Issue が合計で9個しかなかった。 今確認したら 330 までいったので、なるほど。という気持ち
趣味
- DeployGate TGIF で料理を作っている
- ビールの醸造所やバーを巡った http://jmatsu.hatenablog.com/entry/2018/12/03/154552
- 出社回数 < BrewDog Roppongi 来店数 の週が増えた
- 酒蔵めぐりと日本酒を浴びるように飲んだ
- Danger Plugin https://github.com/jmatsu/danger-apkstats , https://github.com/jmatsu/danger-checkstyle_reports
- 使ってる OSS の bug fixes
- DroidKaigi API 作成 (進行中)
- 松亭月例会
- https://www.swarmapp.com/user/year-in-review/2018
苺のような香りがほのかにして、美味い。気に入った。高いけど。 pic.twitter.com/PbwncM8qvn
— おなかすいた (@red_fat_daruma) December 26, 2018
今年一番気に入った日本酒はこれ、獅子の里。愛山特有のすっきりした酸味と甘味があって、口の中で転がすと本当にいちごのような香りがして半端なく美味しい。
でもちょっと体が心配になってきたので、量を減らそうと思います・・・
英語力
時々フリートークでDMM英会話をしているけど、前職 *2 からあんまり伸びてない気がする。ただ周りの英語話者(non-native含む)には今も恵まれていて、推敲含めて教えて貰えることが多い環境に身を置けていて大変ありがたい。
学部生の頃に TOEIC 315 点しかなくて留年の危機 *3 に陥ったし、入試や院試 *4で苦しんだけど、今はさすがに上がってるやろ〜〜ってことでTOEICを受けた。
英語を仕事で使う身としては圧倒的に低いけど、元が低いこともあって2.5倍になった。やはり「TOEICの点数をあげるには最初に低い点数をとればいいじゃない」説は正しい。
みんなもまず300点くらいを取ることをオススメします。その後900点を超えたらなんと3倍です。
2019 年での目標
- 中華と和食が多いので、イタリアンのレパートリーを増やそうと思います。何か食いたいものがあったら言ってください。作れるようになります。
- 今年はインフラやBackend周りを重点的にやろうと思います。
- いっそ日本酒になりたい
2019年を振り返って
セブンからの帰り道、しほちゃんちの鍵を自分の家のオートロックに差し込もうとして刺さらなくて困惑しながら2019年を迎えた。
上の階の人がハッピーニューイヤー!おやすみ!って叫んでた。
完
むしろ食われろ、餃子に
http://www.anime-line.com/animes/156/stories/2982
今年もやってまいりました、SHIROBAKO Advent Calendar 2018 4日目です。
さて、SHIROBAKO 第12話、「えくそだす・クリスマス」の回を覚えているでしょうか?
作中で制作されているアニメ「えくそだす」の最終回を仕上げる重要な回であり、杉江さんによるプロフェッショナルな仕事を見ることができます。
杉江さんへの愛については 2016年のAdvent Calendar より、konifar さんによる SHIROBAKO12話の杉江さんが好きなのでただただまとめておきたい を御覧ください。「わかる」しか感想が出てこないので、この点について僕から言うことは何もありません。
しかしこの回には非常に特徴的なキーワードが出てくることをご存知でしょうか?開始5分頃の会話に出てきます。
興津さん「高梨さんは矢野さんを送っていったあと連絡が取れませんでしたが、先程、餃子を食べてから午後には戻る。とのメールが来ました」
本田さん「餃子?なんで?」
興津さん「せっかくだから、だそうです」
本田さん「むしろ食われろ、餃子に」
そうです、餃子です。餃子は美味しいですよね。14話にも出てくることを考えれば、そこらへんのモブよりも餃子の存在感は強いと言えるでしょう。
https://gifmagazine.net/post_images/70409
ということで、宇都宮餃子の町「宇都宮」で育った僕がオススメの餃子を紹介します。ここから SHIROBAKO の話は一切出てきません。
吉祥寺 - みんみん
みんみんはあの松亭がある吉祥寺のハモニカ横丁内に存在する中華料理屋です。
なかなかに大ぶりな餃子です。皮は少し厚めでモチモチしつつも くどい弾力はなく、加えて肉と野菜のバランスの良い餡を味わうことができます。
また名物のあさりチャーハンも最高に美味しいです。焼き餃子と一緒に食べましょう。結構量があるので二人でいって餃子をシェアするといいかもしれません。
新橋 - 一味玲玲
2つ目は新橋の中華料理屋、一味玲玲です。
15種類を超える餃子を焼き・水・蒸しの3種類の調理法から選べます。
オススメはなんといってもレモン餃子の焼き。レモン果肉の酸味やレモン皮にあるほのかな苦味が良いアクセントになり、ビールが無限に飲めます。
自分の好きな餃子のタネと調理法を見つけてみるのも楽しいかもしれませんね。
宇都宮 - 正嗣
「地元の人でも食べるお店へいきたい」と聞かれることがあります。ただよく驚かれるんですが、地元の友人に聞いても宇都宮民でもあまり餃子専門店に行きません。 *1
そんな中でも自分や地元の友人が行ったことのある餃子専門店が「正嗣」です。*2
メニューは焼き餃子と水餃子のみ、また持ち帰りだと冷凍餃子が購入できます。ライスやビールはありません。なぜならここは餃子専門店だからです。しらんけど。
1枚210円という安さのため、たこ焼きを食べる感覚・セブンでコーヒーを買う感覚で立ち寄り、焼き1水1を頼んでシュッと食べましょう。
野菜多めの餡で皮は薄く、何個でも食べられるタイプの餃子です。リピーターが多いようで、友人の中には実家を出てからも通販で買ってる人もいるようです。
宇都宮 - 来らっせ
餃子を色々食べたい・・・というあなたには「来らっせ」というお店をオススメします。
宇都宮には宇都宮餃子会と呼ばれる協会があり、来らっせはその協会の中でも有名な店舗の餃子を一箇所で楽しめる融合店舗になっています。また宇都宮餃子というワードとともにテレビ(首都圏しか知らない)で取り上げられるお店は大体この協会に所属しています。
これらの餃子屋は行列のできるお店もあり、全てを回ることはもちろんのこと、一部の店舗をはしごすることすら難しかったりします。それを1店舗で楽しめるというのはかなりの良さじゃないでしょうか?まあいったことないんですけど。
ちなみに上で紹介した正嗣はこの協会に所属していません。