Android のビルドログを下から読んでいくと、最後の数行にだけ赤い文字が並んでいる夜があります。
Could not resolve project :react-native-async-storage_async-storage
> No matching variant of project :react-native-async-storage_async-storage was found.
> No variants exist.
「variant が存在しない」と言われても、手がかりがありません。依存の書き方を疑い、Gradle のバージョンを疑い、結局どこも直せないまま日付が変わります。
Expo の GitHub に上がっていた報告を読み比べながら、この症状がどこから来るのかを辿り直してみました。私自身の手元で再現したものではない点は、先にお伝えしておきます。私は iMac で作業しているため、同じ症状に出会っていません。その代わり、報告者の方々がたどった順番を、自分のプロジェクトで先回りして確かめられる形に整えました。
報告された症状は「手元は通る、EAS だけ落ちる」です
報告の元は、Expo SDK 54(54.0.31)のプロジェクトです。Android の Gradle ビルドが EAS Build の上でだけ失敗し、メッセージは冒頭に載せたものでした。
注目したいのは、報告者がその後に確かめた事実です。同じプロジェクトを Windows の手元で gradlew assembleRelease すると、ビルドは成功しました。コードも SDK も同じなのに、結果だけが違います。
こういうとき、私たちはつい「依存の解決順が悪いのだ」と考えます。けれど手元で通っている以上、依存の宣言そのものは間違っていないはずです。差は、手元とビルド機のあいだで何が入れ替わったか、にあります。
見つかった原因は、別 OS で作られたキャッシュでした
同じスレッドに、後から原因を突き止めたという書き込みがあります。Expo サポートの回答として紹介されていた内容を要約すると、次のとおりです。
Windows で生成された build キャッシュが android フォルダに含まれていたが、Linux のビルド機には存在しないものだった
具体的には、android/build/generated/autolinking/autolinking.json というファイルです。Windows の手元で作られたため、モジュールの場所が Windows の絶対パスで書かれていました。それが EAS 側のキャッシュ復元の仕組みを通ってビルド機に戻り、各モジュールの projectDir が Linux 上には存在しない場所を指します。すると Android プラグインが適用されず、variant が1つも作られません。メッセージの「No variants exist」は、この状態をそのまま言っていたことになります。
ここで正直に書いておくと、この説明は報告者の1人がサポートの回答として引用したものです。公式のドキュメントで裏が取れた事実ではありません。私は「有力な仮説」として扱っております。
もう一つ、別の報告者は違う手当てで直したと書いています。expo-doctor が指摘した設定の不整合を直し、npm dedupe で重複した依存を揃え、eas build --clear-cache で作り直した、という流れです。こちらは同じ症状に別の原因が重なっていた可能性を示しています。
| 報告された手当て | 効きそうな原因 | 手間 |
|---|---|---|
expo-doctor の指摘を直す | 設定や依存の不整合 | 小さい |
npm dedupe で依存を揃える | 重複したバージョンの混在 | 小さい |
eas build --clear-cache | 古いキャッシュの復元 | 小さい(ビルドは長くなる) |
| autolinking のキャッシュを作らせない | 別 OS のパスを含むファイルの混入 | 中くらい |
上から順に試すのが、遠回りのようで最短なのかもしれません。少なくとも私は、そう感じています。
投げる前に、自分の android/build を点検します
ビルドを投げて待ってから失敗に気づくと、1回ぶんの待ち時間がまるごと消えます。気づいたのは、いつも待ち終わったあとでした。そこで、android/build の中身を送る前に確かめるスクリプトを用意しました。確かめるのは3点です。Git が追跡していないか、ignore されているか、そして JSON に Windows 形式のパスが混ざっていないかです。
#!/usr/bin/env bash
# android/build の autolinking キャッシュが、別 OS のパスを抱えたまま
# EAS Build へ上がっていないかを、ビルドを投げる前に確かめます。
set -u
F="android/build/generated/autolinking/autolinking.json"
NG=0
if [ ! -f "$F" ]; then
echo "OK: $F はありません。ビルド機で作り直されます"
exit 0
fi
echo "見つかりました: $F"
# 1) Git が追跡していないか
if git ls-files --error-unmatch "$F" >/dev/null 2>&1; then
echo "NG: Git に追跡されています(git rm --cached で外します)"; NG=1
fi
# 2) ignore されているか(android/.gitignore だけでなくルートも見る)
if git check-ignore -q "$F"; then
echo "OK: Git の ignore 対象です"
else
echo "NG: ignore されていません(ルートの .gitignore に android/build/ を足します)"; NG=1
fi
# 3) Windows 形式の絶対パス(C:\ や C:/)が混ざっていないか
if grep -qE '"[A-Za-z]:(\\\\|/)' "$F"; then
echo "NG: Windows 形式のパスが入っています。Linux のビルド機には存在しない場所です"; NG=1
else
echo "OK: Windows 形式のパスは見つかりません"
fi
# 4) .easignore があるなら、そちらにも載っているか
if [ -f .easignore ] && ! grep -qE '^/?android/build/?$' .easignore; then
echo "注意: .easignore に android/build/ がありません"
fi
exit "$NG"このスクリプトは、Windows 形式のパスを含む JSON と、含まない JSON の両方を作って、出力と終了コードを確かめてあります。前者では NG と終了コード 1、後者では OK と終了コード 0 になりました。ただし、実際の EAS Build で症状が消えるかどうかまでは、私の手元では確かめておりません。
手順2を「android/.gitignore だけでなくルートも見る」としたのには理由があります。報告者の1人は、android/build/ をルートの .gitignore に足す必要があったと書いていました。サブフォルダ側の .gitignore に書いてあるだけでは足りない構成が、現にあったということです。
それでも残るなら、ビルド機側で捨てさせます
報告者が実際に使った手当ては、android/settings.gradle の先頭に、キャッシュを毎回消す数行を置くものでした。Windows 以外のマシン、つまり Linux のビルド機では、autolinking のキャッシュを必ず作り直させます。
// autolinking のキャッシュを、ビルド機で毎回作り直させます。
// Windows で生成されたパス入りのキャッシュが EAS の Linux ビルド機へ
// 渡っても、ここで捨てられます。
def autolinkingCacheDir = new File(rootDir, "build/generated/autolinking")
if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
autolinkingCacheDir.deleteDir()
}pluginManagement ブロックより前に置く、という指定でした。
ただし、これは症状を黙らせる処置で、根は元のキャッシュが上がってしまうことにあります。android/ を毎回 expo prebuild で作り直す運用なら、ここへ手を入れる前に、そもそも android/ を Git に載せない選択のほうが筋が通ります。.easignore を使っている場合は、.gitignore が読まれなくなる点にも注意が要ります。詳しくは .easignore を置くと .gitignore は読まれなくなります — EAS Build に上がるファイルを手元で数える に書きました。
どの順番で試すか
手戻りが小さい順に並べると、次のようになります。
npx expo-doctorを走らせて、指摘された設定の不整合を直します。- 上のスクリプトで
android/buildを点検します。 eas build --platform android --clear-cacheで作り直します。- それでも落ちるなら、
settings.gradleにキャッシュを捨てる数行を置きます。 - ここまで来ても直らなければ、
eas.jsonのimageを疑います。これは eas.json に image を書かないままだと、SDK を上げた日に Xcode も pnpm も一緒に動きます の領域です。
「手元では通る」は、ビルド機でも通ることの証拠ではありません。 手元とビルド機のあいだで入れ替わるものは、送る前に数えます。この線引きだけは、これからも崩さないようにしています。
まずは今夜、ご自身のプロジェクトで android/build が Git に載っていないかを1行で確かめていただければと思います。git ls-files android/build | head で、何も出なければ最初の関門は越えています。