🔨

Android向けCI回す時、Gradle意識していますか?

2023/12/18に公開

https://qiita.com/advent-calendar/2023/android

TL;DR

  • CIでGradleを忘れがちで各JobでGradle DLして時間もったいない
  • Dockerfile内で直接Gradle versionやPATHを設定しているのはちょくちょく見る
  • ./gradlew --gradle-user-home ./.gradleHome でGradle展開先を指定するのが複数アプリ開発ではよさげ

よく見るAndroid向けDockerfile

GitLab CIのテンプレートを見ると、Android SDKはインストールしているがGradleについては全く触れていない。

# Packages installation before running script
before_script:
  ...
  - wget --no-verbose --output-document=$ANDROID_HOME/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}_latest.zip
  - unzip -q -d "$ANDROID_HOME/cmdline-tools" "$ANDROID_HOME/cmdline-tools.zip"
  - mv -T "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/tools"
  - export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/cmdline-tools/tools/bin

  # use yes to accept all licenses
  - yes | sdkmanager --licenses > /dev/null || true
  - sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}"
  - sdkmanager "platform-tools"
  - sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}"
...

✅Good Point

  • スクリプトがシンプルで読みやすくメンテナンスしやすい

❌Bad Point

  • 各JobでGradleがDLされてCI時間がぞうかしてしまう
    • どうなるかというと、BuildLintTestなどの各JobでGradleをDLしてしまいます

Graldeも同梱されているDockerfile

BitriseのAndroid向けDockerfileを見ると、Android SDKに加えてGradleもインストールしている。
インストールするGradleのバージョンについては、環境変数GRADLE_VERSIONで指定している模様。

...
# --- Install Gradle from PPA

# Gradle PPA
ENV GRADLE_VERSION=6.3
ENV PATH=$PATH:"/opt/gradle/gradle-6.3/bin/"
RUN wget https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip -P /tmp \
    && unzip -d /opt/gradle /tmp/gradle-*.zip \
    && chmod +775 /opt/gradle \
    && gradle --version \
    && rm -rf /tmp/gradle*
...

✅Good Point

  • GraldeもDocker Imageとして配布されるため環境差分が発生しない

❌Bad Point

  • GRADLE_VERSIONのメンテを忘れると、古いGradle使い続けることになる
    • ちなみに通常Gradleバージョンは、gradle-wrapper.propertiesで定義されている

結局どうしたのか

よく見るAndroid向けDockerfileGraldeも同梱されているDockerfileを見て、
これCIのキャッシュに任せたほうがいいんじゃない」っと思ったので下記のようにして見ました。

debugBuild:
  stage: debug
  image: $CI_REGISTRY_IMAGE:latest
  script:
    - ./gradlew --gradle-user-home ./.gradleHome assembleDebug
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    policy: pull-push
    paths:
      - ./.gradleHome

Workingディレクトリ配下の.gradleHomeに、GradleをDLするように指定して、CIのキャッシュに.gradleHomeを設定しています。
これで後続のJobは.gradleHomeをキャッシュとして再利用します。
Gradleは、ユーザーホームディレクトリにGradleが既に存在する場合はDLを行わず、本来のJobを実行します。

ポイント

ポイントは下記2つ。

  1. --gradle-user-homeオプションでGradleユーザーホームディレクトリを指定すること
  2. CIキャッシュに、--gradle-user-homeオプションのディレクトリを指定すること

--gradle-user-home

--gradle-user-homeオプションとは、Gradleのユーザーホームディレクトリを指定するオプションです。
このオプションを指定しなかった場合、Gradleはユーザディレクトリ配下の.gradleすなわち、~/.gradleにDLされます。
Dockerを使わない開発環境の場合は、これで何も問題ありません。
ただGradleDLのユーザーはroot・Build時のユーザーは一般ユーザーといった構成だと~/.gradleが、一致しないことがあります。

--gradle-user-homeオプション[^4]を使って明示的にGradleユーザーホームディレクトリを指定するのが良いでしょう。

ちなみに後述のdistributionBaseよりこちらの設定のほうが優先されます。

distributionBase

Gradleユーザーホームディレクトリを指定するには、gradle-wrapper.properties内のdistributionBaseで指定する方法もあります。
通常distributionBaseは、GRADLE_USER_HOMEで設定されています。
そうです、このGRADLE_USER_HOMEが、ホームディレクトリ配下の.gradleを指しています。

じゃあ、gradle-wrapper.properties内のdistributionBaseを変更すればいいじゃんと思いました。
一番分かりやすいのは、distributionBaseをリポジトリRootディレクトリ配下の.gradleに変更することです。
ただ複数のAndroidアプリを開発している場合、リポジトリ毎にGradleがDLされます。
各リポジトリで同じバージョンのGradleを使用していたら、ディスク圧迫[1]してしまってう〜んですね。

残る問題点😕

CIのキャッシュを使うことで、2番目以降のJobは、1番目JobのGradleを使うのでDLが発生しません。
ただこれ逆を言えば、1番目のJobは必ずGradleのDLが発生してしまいます

ただ下記メリットのほうが重要だと思って今はこの構成にしています。

  1. DockerfileGitLab CIスクリプトにGradleバージョンを設定しなくて良い
  2. gradle-wrapper.propertiesを変更しないので、ローカル開発[2]&複数アプリ開発向け

今後の構想

Docker Build時にgradle-wrapper.propertiesをコピーして、そこからGradleバージョン取得する

取得したバージョンのGradleをDocker Imageに入れれば、環境差分問題とGradleバージョンのメンテナンス問題解決できるんじゃないかと思っています。
ただgradle-wrapper.propertiesからGradleバージョン取得するのが、どれだけ可読性に影響するかまだ検証できないので保留です。
これ出来ればオールオッケーな気がしています。

この記事を読んでくださった方向け

正直この構成も完璧だと思っておりません。
改善案あれば、この記事のコメントまたはこちらにご意見頂けると幸いです。

脚注
  1. Gradle v8.5で150MB程あるので、塵も積もれば厳しいです ↩︎

  2. Dockerを使わず、ホストOSにインストールしたAndroid Studioで開発する環境を指します。 ↩︎

GitHubで編集を提案

Discussion