レガシーなGradleプロジェクトでビルドを速くする(かもしれない)方法

Gradleを使ったKotlinプロジェクトを引き継ぐことになった。引き継ぐというか、チームに入れてもらうというか。

そのチームにもあまりGradleに詳しい人がいるわけでもなく、過去の遺産的に、初期開発者がセットアップしたbuild.gradle.ktsファイルをそのまま使っている、という感じ。そういう意味での「レガシー」。

 

 

[症状:ビルドが遅い]

チームに入った以上、自分も何かしら貢献せねばと思い、ちょっとしたライブラリを実装した。詳細は、またいずれ。

 

ライブラリ自体は単体のプロジェクトを作って別で実装し、後から問題のプロジェクトに組み込む予定でいた。

単体で作っているので、ビルドも速い。1分はかからなかった。コンパイルエラーが出ればすぐに直せたし、テストが失敗すればすぐに修正できた。

 

しかし、いざ自作したコードを問題のプロジェクトに追加してビルドを実行してみると、これが異常に遅い。

速い時でも./gradlew buildコマンドの結果を得るのに20分近くかかったし、ひどいときには40分近くかかった。ビルドを実行している間、画面をずっと見ているわけにもいかないので、他の仕事をしていた。そうすると、ビルドをしていたことを忘れて、ビルドを開始してから2時間くらいしてようやくテストが失敗しているのを発見する、というありさまだった。

フィードバックサイクルが遅すぎる。

 

[処方1:ググる]

なんとか出来ないか、と思い、Google先生に聞いてみるといくつかのサイトが見つかった。

 

最初に見つけたのはこれ。

medium.com

 

--configure-on-demandというオプションがあるらしい。試してみたがこれはあまり効果がなかった。

あとはdaemonを使うとか、最新のGradleを使うとかが紹介されていた。daemonの設定はすでに有効になっていたし、Gradleのバージョンを変えるのはちょっと影響が大きそうなのでやりたくなかった。

 

次に見つけたのはこれ。

www.linkedin.com

 

オフラインモードというのがあるらしい。コマンドラインの場合、--offlineオプションで設定可能とのこと。

こちらを試してみると、20分かかっていたビルドが2〜3分で終わるようになった。これなら戦える!

しかし、当然オフラインなので、ライブラリの追加時などにはこの作戦は使えない。困った……。

 

[処方2:専門家に相談する]

ここまで書いたような状況を、Slackの分報チャンネルで逐一書き込んでいた。自分の分報チャンネルにはなぜか社内の10%程度の人間が参加している。たまたま他のチームにいるGradleや社内のCI/CDまわりに詳しい専門家が状況を察知して、救いの手を差し伸べてくれた。

 

プロジェクトの設定ファイルを見てもらったところ、settings.gradle.ktsファイルに問題があるらしい。

曰く、「10行目で指定している社内リポジトリはもう廃止されていて、それがタイムアウトを引き起こしており、そのためビルドに時間がかかっているのではないか」とのことだった。

 

教えてもらった通りに、該当行を削除してビルドを実行してみると、オフラインモードとほぼ変わらない実行速度でビルドが完了した。

 

これでライブラリを追加してもオフラインモードとほぼ同じ速度で開発できるようになった!

 

念のため、タイムアウトしていたかどうかを確認してみる。削除した設定行を元に戻して、./gradlew build --debugデバッグモードで実行してみたところ、以下のようなログが出力された。java.net.ConnectException: Connection timed out: connectとあり、たしかにタイムアウトが発生していた。

[DEBUG] [org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ErrorHandlingModuleComponentRepository$ErrorHandlingModuleComponentRepositoryAccess] Error while accessing remote repository maven. Waiting 1000ms before next retry. 2 retries left
org.gradle.internal.resolve.ModuleVersionResolveException: Could not resolve io.gitlab.arturbosch.detekt:detekt-bom:1.17.1.
Caused by: org.gradle.api.resources.ResourceException: Could not get resource 'http://172.xx.xx.xx:8081/repository/internal/io/gitlab/arturbosch/detekt/detekt-bom/1.17.1/detekt-bom-1.17.1.pom'.
        at org.gradle.internal.resource.ResourceExceptions.failure(ResourceExceptions.java:74)
        at org.gradle.internal.resource.ResourceExceptions.getFailed(ResourceExceptions.java:57)
        (略)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
        at java.lang.Thread.run(Thread.java:748)
Caused by: org.gradle.internal.resource.transport.http.HttpRequestException: Could not HEAD 'http://172.xx.xx.xx:8081/repository/internal/io/gitlab/arturbosch/detekt/detekt-bom/1.17.1/detekt-bom-1.17.1.pom'.
        at org.gradle.internal.resource.transport.http.HttpClientHelper.performRequest(HttpClientHelper.java:101)
        (略)
        at org.gradle.api.internal.artifacts.repositories.resolver.DefaultExternalResourceArtifactResolver.downloadByCoords(DefaultExternalResourceArtifactResolver.java:139)
        ... 197 more
Caused by: org.apache.http.conn.HttpHostConnectException: Connect to 172.xx.xx.xx:8081 [/172.xx.xx.xx] failed: Connection timed out: connect
        at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:156)
        (略)
        at org.gradle.internal.resource.transport.http.HttpClientHelper.performRequest(HttpClientHelper.java:97)
        ... 216 more
Caused by: java.net.ConnectException: Connection timed out: connect
        at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
        at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)
        at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
        at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
        at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
        at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
        at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
        at java.net.Socket.connect(Socket.java:607)
        at org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket(PlainConnectionSocketFactory.java:75)
        at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
        ... 228 more

 

感謝の意をこめて、専門家には社内通貨を送っておいた。端数が設定できなかったので39ポイントにはできなかったけれど。

 

[まとめ]

  • Gradleビルドを速くするかもしれない方法その1:--configure-on-demand
  • Gradleビルドを速くするかもしれない方法その2:--offline
  • 使われなくなった社内リポジトリを指定している場合は該当の設定を削除する

 

ビルドがスピーディに実行できるようになったところで、ようやく俺たちの戦いはこれからだ!という感じ……課題が山積みである。