Androidアプリエンジニアの石原(iaia)です。 DroidKaigi 2021 がもうすぐですね!
弊社ロコガイドも、スポンサーとして協賛しております。
DroidKaigi 2021 では、SUPPORTERSスポンサーとして株式会社ロコガイド様 @tokubaishopping にご協力いただいています!
— DroidKaigi (@DroidKaigi) September 29, 2021
At this time, Locoguide Inc. @tokubaishopping supports us as SUPPORTERS Sponsor of DroidKaigi 2021.#DroidKaigi
さて、私が入社したのが2020年1月で、その頃からずっとCIがたまにコケる、という問題が発生していて、 原因、解決策が分からず、ずっと放置していたのを一年越しに解決した、という話をします。
※ 解決したのが今年の1月で、思い出しながら書いています...
どんなエラーだったか
※ 注意 本記事には Orma
の名前が何度も出てきますが、 Orma
の問題では一切ありません
※ Ormaとは
> Task :data:kaptStagingDebugKotlin Note: [OrmaProcessor] built 11 of schema models in 45msNote: [OrmaProcessor] process classes in 103ms/tmp/android-bargain/data/src/main/java/jp/co/tokubai/android/bargain/data/di/provider/OrmaDatabaseProvider.kt:6: error: cannot find symbol val database: OrmaDatabase ^ symbol: class OrmaDatabase location: interface OrmaDatabaseProvider/tmp/android-bargain/data/src/main/java/jp/co/tokubai/android/bargain/data/source/example/ExampleLocalDataSource.kt:16: error: cannot find symbol private val database: OrmaDatabase = databaseProvider.database ^ symbol: class OrmaDatabase location: class ExampleLocalDataSource/tmp/android-bargain/data/src/main/java/jp/co/tokubai/android/bargain/data/di/provider/OrmaDatabaseProviderImpl.kt:12: error: cannot find symbol override val database: OrmaDatabase = OrmaDatabase ^ symbol: class OrmaDatabase location: class OrmaDatabaseProviderImpl/tmp/android-bargain/data/build/tmp/kapt3/stubs/stagingDebug/jp/co/tokubai/android/bargain/data/source/example/ExampleLocalDataSource.java:11: error: [ComponentProcessor:MiscError] dagger.internal.codegen.ComponentProcessor was unable to process this class because not all of its dependencies could be resolved. Check for compilation errors or a circular dependency with generated code. public final class ExampleLocalDataSource implements jp.co.tokubai.android.bargain.data.source.example.ExampleDataSource { ^/tmp/android-bargain/data/build/tmp/kapt3/stubs/stagingDebug/jp/co/tokubai/android/bargain/data/di/provider/OrmaDatabaseProviderImpl.java:11: error: [ComponentProcessor:MiscError] dagger.internal.codegen.ComponentProcessor was unable to process this class because not all of its dependencies could be resolved. Check for compilation errors or a circular dependency with generated code. public final class OrmaDatabaseProviderImpl implements jp.co.tokubai.android.bargain.data.di.provider.OrmaDatabaseProvider { ^[WARN] Incremental annotation processing requested, but support is disabled because the following processors are not incremental: com.github.gfx.android.orma.processor.OrmaProcessor (NON_INCREMENTAL). > Task :data:kaptStagingDebugKotlin FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':data:kaptStagingDebugKotlin'. > A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptExecution > java.lang.reflect.InvocationTargetException (no error message)
どういう問題があるか
- すべてのビルド(staging/product, debug/release)時に発生する可能性がある
- Pull Request時のビルド(stagingDebugビルド)でもコケる
- CIが通るまでレビューお願いできないので時間がかかる
- Google Play Storeへのsubmitのビルド(productReleaseビルド)でもコケることがある
- HotFixを早く出したいときなどにも時間がかかってしまう可能性
- Pull Request時のビルド(stagingDebugビルド)でもコケる
- CircleCIに行って、原因確認して、re-runボタン押す、としないといけなくて面倒
- 発生頻度は1週間に1回くらいで、CIをre-runすればとりあえず通るから
一年前の原因調査
原因?
OrmaDatabase.java
はあるはず...- このとき実は出来ていないのだがちゃんと調査していなかった...
- 成功するときと失敗するときがあるということはコード的には問題ない
- 実際ローカルでも成功しているし
- 多分ビルドのタスク順?
- 成功時と失敗時のdataモジュールのタスク順を眺めてみる
- 確かに差分はある
- 何のタスクが先にあると良くて、どのタスクが後にあると良いのか? を調べようとしていた
諦め
ってところまで進めてはいたものの以下の理由で諦めました。
- 原因が分かったところで、ではどうすれば解消できるのか? が思いつかない
- タスクの順番を固定化させるのか?
- この問題を調べ始めたときはコケることがそんなに頻繁じゃなくなった
- dataモジュールを変更しない場合、成功時のキャッシュが利用されて問題が発生しない
- 隙間時間で調べていたが、他のタスクが積まれていた
それから一年後...
- 突然当該エラーが頻発し始める
- 3回に2回くらいの頻度でコケはじめる
このままだとまともに開発が出来ないので、本腰入れて原因調査することになりました。
一年越しの原因調査
OrmaDatabasae.java
をkaptで生成し、それを参照する OrmaDatabaseProvider.kt
をDIして XxxxLocalDataSource.kt
で使う、という構造だった。
以下コード例
import com.example.data.OrmaDatabase interface OrmaDatabaseProvider { val database: OrmaDatabase }
class ExampleLocalDataSource @Inject constructor( databaseProvider: OrmaDatabaseProvider ) { private val database: OrmaDatabase = databaseProvider.database }
もう一度エラーコードを振り返ると
OrmaDatabaseProvider.kt:6: error: cannot find symbol
val database: OrmaDatabase
OrmaDatabase
が見つからないと言っている。
本当にOrmaDatabaseは存在しないのか?
- Circle CIにsshして
OrmaDatabase.java
が存在するか確認すると良さそう - まずローカルの
OrmaDatabase.java
はどこにあるのか? を調べるbuild/generated/source/kapt/debug/com/example/data/OrmaDatabase.java
に作られている
- sshして該当の場所を探す
- 確かにない!
OrmaDatabase.javaが存在しない原因
- 生成されたが削除されてしまった
- わざわざそんなことはしないはず...
- 成功しているときは存在していることからもこの説はなさそう
- 設定に不備がある
- 同じような問題に当たっている人が多分いるのでは?
- 「こういう条件のとき生成されない可能性があるのでxxxを設定してください」みたいなワークアラウンドがあるのでは?
- ビルドエラーになった時点では生成されない (タイミングの問題)
- これは原因調査時に気づいてなかった仮説
設定に不備があるのか?
OrmaDatabase.java
を生成する条件は何なのか?- 必要なライブラリは全部入っている
- README と見比べても問題なさそう
@Table
が指定されているclassがある- 設定されている
- 必要なライブラリは全部入っている
- 他に
OrmaDatabase.java
を生成させるオプションはないのか?- 明示的に
@Database
と指定するオプションがある - とりあえず試してみるも同様の問題が起きるのでこれが原因ではなさそう
- 明示的に
タイミングの確認
- そもそも
OrmaDatabase.java
は最初存在しておらずcannot find symbol
であるのは正しい - 「
OrmaDatabase.java
が必要となるタイミング」までに作られていれば良い - 「
OrmaDatabase.java
が必要となるタイミング」とは? - もう一度エラーメッセージを見てみる
^/tmp/android-bargain/data/build/tmp/kapt3/stubs/stagingDebug/jp/co/tokubai/android/bargain/data/di/provider/OrmaDatabaseProviderImpl.java:11: error: [ComponentProcessor:MiscError] dagger.internal.codegen.ComponentProcessor was unable to process this class because not all of its dependencies could be resolved. Check for compilation errors or a circular dependency with generated code.
- Dagger2 がjavaファイル生成しているところで必要になっているっぽい
- そういえばdaggerもkaptだ
- orma -> dagger の順でアノテーションを処理していれば成功するし、 dagger -> ormaの順であれば失敗する のでは?
kaptの処理順が問題ならどうするか?
- kaptの処理順は明示的に指定できるのか?
- モジュール分割してあげればいいじゃん!ということに気づく
- 1個のモジュールの中での処理順が決められないなら、そもそも別のモジュールにして、参照するようにする
- すると参照されているモジュールは先にビルドされる必要があるので、そのモジュール内のkaptが必ず先に処理されるはず
- Ormaのアノテーションの
@Database
とか@Table
とかすべてdataモジュールの中に存在している- daggerのアノテーションもdataモジュールにある
- これをOrmaのアノテーション使っているようなものをすべて別のモジュール(database)に切り出してしまう
結果
おれはやったぜ
当該エラーでCIがコケることはなくなってみんなはっぴー
振り返り
- まず最初の原因調査の仕方がまずかった
- 原因調査になっていない、推測しかしてない
- まずCIにsshして「本当にjavaファイルがないのか?」は絶対にやるべきだった
- 多分、
Rerun with SSH
しても同じエラーが起きるとは限らなくて断念してた気がする
- 多分、
- 文脈がちょっと違うが「推測するな、計測せよ」を肝に銘じた
- 調査したら必ずメモを取る
- 本記事は2020年4月頃と、2021年1月頃に調査したときのメモがGitHubのissue上に残っていたものを参考に書いている
- メモってなかったらここまで書けなかったと思う
- それでもメモが足りていない部分がある
- sshしてファイルが無かったってことをメモっていない
- kaptの処理順がランダムなことのエビデンス
- 将来、この直し方はやっぱり間違えていて問題が起きたときに、どうしてこの直し方にしたのか? が分かるはず
- 自分の調査の仕方が悪い、ということも一年越しにメモを見ることで気づけた
- kaptの処理順とかあまり意識しなかったが調べるいいきっかけになった
- マルチモジュール化進んでないなという実感
- 進んでいればこういう問題にぶち当たることもなかったかも
- そもそもまだOrma使っているのか?
- Room に移行したいが...
おわり
今回みたいなCIの安定化や、マルチモジュール化を進めてくれるようなAndroidアプリエンジニアを積極採用中です! 最近はJetpack Composeも取り入れはじめているので、腕力で既存のコードを書き換えてくれるような方もお待ちしております!