😉

[Android] GradleでDependabotを使うときのTips

2020/12/09に公開

この記事はKyash Advent Calendar 2020の9日目の記事です。

Dependabotとは

Dependabotはライブラリのアップデート作業を自動化してくれるbotで、GitHubに統合されています。リポジトリにDependabotを設定すると、定期的にライブラリの更新を確認し、バージョンを上げるPull Requestを作ってくれます。

様々な言語に対応していますが、今回はGradleでDependabotを利用したときに発生する課題と、その解決策を紹介します。タイトルにAndroidと入れていますが、Gradle Javaなプロジェクトであれば同じことはできるはずです。

課題

特にAndroidでは多いのですが、ライブラリの肥大化を避けるため1つのライブラリを複数のアーティファクトに分け、必要な機能だけ提供する場合があります。たとえばAndroidX Lifecycleでは、導入例のコードが次のようになっています。

    dependencies {
        def lifecycle_version = "2.2.0"
        def arch_version = "2.1.0"

        // ViewModel
        implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
        // LiveData
        implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
        // Lifecycles only (without ViewModel or LiveData)
        implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"

        // Saved state module for ViewModel
        implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"

        // Annotation processor
        kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
        // alternately - if using Java8, use the following instead of lifecycle-compiler
        implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"

        // optional - helpers for implementing LifecycleOwner in a Service
        implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version"

        // optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
        implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"

        // optional - ReactiveStreams support for LiveData
        implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version"

        // optional - Test helpers for LiveData
        testImplementation "androidx.arch.core:core-testing:$arch_version"
    }

このように共通のバージョンを持つ複数の分割アーティファクトがある場合、バージョンを変数に切り出して利用するのはプログラマなら当然のアプローチです。

しかしながら、Dependabotはこのようにバージョンを変数に切り出した場合はうまく動作してくれません。Gradleのスクリプトを実行しているわけではなく、 "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" という文字列を検出するアプローチをしているのではないかと思われます。

(注) バージョン情報を別のgradleファイルに切り出すことでうまくいくという記事も見たことがありますが、私が手元で試したところ動作してくれませんでした。こうすればいいよ情報があればコメントいただけると幸いです。

では「どうせ自動で書き換えてPRにしてくれるなら変数に切り出さなくてもいいか」と次のように書くと、ちゃんと各々をアップデートするPRを4つ作ってくれます。

dependencies {
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
    implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0"
    implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
}

ですが、この場合1つのPRをマージした段階で他のPRがコンフリクトするという悲劇が発生します。DependabotにはGitHubのPR上で @dependabot rebase とコメントすると自動でrebaseしてくれる機能もありますが、1つ1つのPRに手作業でコメントしていくのでは自動化する意義が薄れてしまいます。

解決策

この課題を解決するには

  1. バージョンを変数に切り出しつつ
  2. Dependabotが認識できる文字列を用意する

必要があります。

実はGradleの implementation などのメソッドは Dependency 型のオブジェクトをreturnしていて、この中にはバージョン情報が含まれています。そのため、次のようにバージョンを抽出するヘルパーメソッドを作成し、それを変数に入れてあげれば実現できます。

static String captureVersion(Dependency dependency) {
    return dependency.version
}

dependencies {
    def lifecycle_version = captureVersion(implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"))
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
    implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
}

おわりに

本記事ではGradle x Dependabotをより効率よく運用するためのTipsを紹介しました。Androidは特にライブラリのアップデートが早いので、この方法を導入してからDeveloper Experienceが格段に向上したので、是非試してみてください。

Discussion