Gradleでハマりがちな設定ミスと回避策

Gradleでハマる原因の多くは「設定ファイルが読まれているつもりになっている」「便利そうな設定を深く考えずに入れている」ことにあります。ビルドが通らない、なぜかCIだけ失敗する、突然ビルドが遅くなる──そうしたトラブルは、派手なバグよりも地味な設定ミスから生まれがちです。この記事では、Gradleで特に遭遇しやすい設定ミスを取り上げ、なぜ起きるのか、どう回避すればよいのかを具体例ベースで整理します。

Gradleで設定ミスが起きやすい理由

Gradleは非常に柔軟なビルドツールです。GroovyやKotlinでビルドロジックを書ける反面、「書けてしまう」ことが罠になります。設定フェーズと実行フェーズの違い、プロジェクトとサブプロジェクトのスコープ、キャッシュやデーモンの存在など、暗黙の前提が多く、少し理解がずれるだけで意図しない挙動になります。

さらに、Gradleは「動いているように見える」状態が多いのも厄介です。ローカルでは問題ないのに、CIで失敗する。キャッシュを消すと直る。こうした症状は、設定ミスが隠れているサインであることが少なくありません。

build.gradleに書いた設定が効いていない

よくある勘違い

build.gradleに書いた設定は、すべてのタスクに自動的に反映されると思い込んでしまうケースです。しかし実際には、設定のスコープや評価タイミングによって、効く・効かないが分かれます。

例えば、次のような記述です。

tasks.withType(Test) {
    useJUnitPlatform()
}

一見すると問題なさそうですが、プラグイン適用前に書かれていると、対象のタスクが存在せず、結果として設定が反映されないことがあります。

なぜ起きるのか

Gradleには「設定フェーズ」と「実行フェーズ」があり、設定フェーズ中にタスクがまだ定義されていない場合があります。特にプラグイン由来のタスクは、プラグイン適用後でないと安全に触れません。

回避の考え方

  • pluginsブロックの後に設定を書く
  • tasks.named や configureEach を使う
  • afterEvaluateに安易に逃げない

設定が効いていないと感じたら、「この時点でタスクは存在するか?」を疑うのが第一歩です。

afterEvaluateの使いすぎ

便利だが危険なafterEvaluate

Gradleに慣れてくると、afterEvaluateを使えばだいたい何でも解決するように見えます。

afterEvaluate {
    tasks.withType(JavaCompile) {
        options.encoding = "UTF-8"
    }
}

確かに動くことは多いですが、これは将来的なトラブルの種になりやすい書き方です。

実際に起きがちな問題

  • 設定の評価順が分かりづらくなる
  • プラグインの内部挙動と競合する
  • Gradleのバージョンアップで壊れやすい

どう考えるべきか

afterEvaluateは「どうしても必要な場合のみ」使うものです。多くの場合、tasks.configureEach や extensions の正しい使い方で回避できます。動いたからOKではなく、「なぜそれが必要なのか」を説明できるかを基準にすると、後悔しにくくなります。

サブプロジェクト設定のコピペ地獄

allprojects / subprojects の落とし穴

マルチプロジェクト構成では、次のような設定をよく見かけます。

subprojects {
    apply plugin: "java"
    repositories {
        mavenCentral()
    }
}

一見すると合理的ですが、これが後々足かせになることがあります。

失敗しがちなパターン

  • 本当は一部のサブプロジェクトだけに適用したい
  • Androidや別言語のプロジェクトまで影響を受ける
  • プラグインの競合が起きる

改善の方向性

共通化したい気持ちは分かりますが、「本当に全サブプロジェクト共通か?」を一度立ち止まって考える価値があります。必要に応じて convention plugin(ビルドロジックの共通化)を作る方が、長期的には安全です。

依存関係のバージョン指定ミス

+ や latest.release の誘惑

Gradleでは次のような指定が可能です。

implementation "org.example:library:+"

短期的には便利ですが、これは再現性を壊す典型例です。

実際に困る場面

  • ある日突然ビルドが壊れる
  • ローカルとCIで挙動が違う
  • 数ヶ月後に原因調査が困難になる

安定した運用のために

  • 明示的なバージョン指定をする
  • version catalog を使って集中管理する
  • 定期的にアップデートする運用を前提にする

「常に最新を使いたい」という欲求は分かりますが、ビルドは再現できてこそ意味があります。

Gradle Daemonとキャッシュの誤解

キャッシュが原因でハマる

「さっきまで動いていたのに、設定を変えても挙動が変わらない」という経験は、Gradleユーザーなら一度はあるはずです。

よくある誤解

  • build.gradleを直せば必ず反映される
  • cleanすればすべてリセットされる

実際には、Gradle Daemonやビルドキャッシュが影響していることがあります。

切り分けのポイント

  • --no-daemon で実行してみる
  • ~/.gradle/caches を疑う
  • CIとローカルのオプション差分を見る

キャッシュは敵ではありませんが、理解せずに使うと「見えない状態」に振り回されます。

CIでだけ失敗するGradle設定

ローカル信仰の危険性

ローカルで通るから大丈夫、という考え方はGradleでは通用しないことがあります。

よくある原因

  • Javaのバージョン違い
  • 環境変数依存の設定
  • ローカルにしか存在しないファイル参照

対策の基本

  • toolchain を明示的に指定する
  • 環境依存の値はgradle.propertiesやCI側で管理する
  • 「ローカル専用設定」を意識的に分離する

まとめ:Gradleは「動いた」より「説明できる」を目指す

Gradleでの設定ミスは、スキル不足というより「考え方のズレ」から生まれることが多いです。動いたから正しい、ではなく、「なぜこの設定が必要で、どこに効いているのか」を説明できる状態を目指すと、ハマりは確実に減ります。

設定を足す前に一度立ち止まり、スコープ・評価タイミング・再現性を意識する。それだけでも、Gradleとの付き合い方はずいぶん楽になるはずです。