SDK 58 に上げる前に、課金まわりの依存を棚卸ししていた夜のことです。Expo の Issue 一覧を眺めていて、題名に Stripe と書かれた報告で手が止まりました。
useFrameworks を dynamic にしているのに、Stripe が static ライブラリとしてビルドされていると言われ、pod install が失敗します。設定は足りているのに落ちる、という形は、原因探しの方向を間違えやすい種類のつまずきなのだと思います。
私自身は、この構成を手元で再現したわけではありません。ここに書くのは、報告された Issue と、Expo 側が EAS で再現したコメントを読んで整理したことです。そのうえで、自分のプロジェクトが当たる条件かどうかを、ビルドの前に確かめる方法まで書き残します。
最初にお伝えしたいのは結論です。Stripe を入れていて、useFrameworks: "dynamic" で、precompiled modules が有効なら、この件の対象です。 逃げ道は三つあり、どれを選ぶかは、リリースまでの時間と、iOS のビルド時間をどこまで許せるかで決まります。
何が起きているか
報告されたのは expo/expo の Issue 51652 です。条件を表にします。
| 項目 | 報告された値 |
|---|---|
| Expo | 58.0.11 |
| React Native | 0.88.0 |
| @stripe/stripe-react-native | 0.81.0 |
| expo-modules-autolinking | 58.0.13 |
| CocoaPods | 1.17.0 |
| expo-build-properties の ios 設定 | useFrameworks: dynamic、usePrecompiledModules: true |
npx expo prebuild --platform ios --clean を走らせると、CocoaPods が次の趣旨のエラーで止まります。
[!] [stripe-react-native] Resolving the Stripe iOS SDK through Swift Package
Manager requires dynamic frameworks, but stripe-react-native is building as
a static library. To fix, add `use_frameworks! :linkage => :dynamic` to your Podfile読み返すと、ここに罠があります。メッセージは「Podfile に dynamic を足してください」と言っていますが、足すべき設定は、もう入っているのです。足りないのは設定ではなく、別の場所で上書きされた結果でした。
報告者は Ruby 側で原因を追っています。expo-modules-autolinking の precompiled_modules.rb が、React-Core に依存する Pod を pods_to_downgrade に集めて、ビルド種別を static ライブラリに差し替えていました。Stripe の Swift Package Manager 連携は dynamic を要求するので、ここで衝突します。
Expo 側の確認コメントも、同じ見立てでした。同一のプロジェクトで三通りのビルドを EAS の iOS preview プロファイルで試した結果が、次の表です。
| Stripe 0.81.0 | usePrecompiledModules | 結果 |
|---|---|---|
| あり | true | 失敗(Install pods) |
| あり | false | 成功 |
| なし | true | 成功 |
つまり、Stripe と precompiled modules が両方そろったときだけ落ちます。 どちらか片方なら通ります。なお、EAS の画面には「Unknown error. See logs of the Install pods build phase」としか出ないそうです。本文のエラーは、Install pods の段階のログまで開かないと見えません。
自分のプロジェクトが当たるかを、先に確かめる
app.json と package.json を読んで、三つの条件がそろっているかだけを判定する小さなスクリプトを書きました。usePrecompiledModules は、ドキュメントの上で既定が true なので、書かれていないときも有効として数えています。
// check-stripe-linkage.js — Stripe と precompiled modules の組み合わせを、ビルド前に点検する
// 使い方: node check-stripe-linkage.js [プロジェクトのパス]
const fs = require('fs');
const path = require('path');
const root = path.resolve(process.argv[2] || '.');
const readJson = (name) => {
const p = path.join(root, name);
return fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, 'utf8')) : null;
};
const pkg = readJson('package.json');
if (!pkg) { console.error('package.json が見つかりません: ' + root); process.exit(2); }
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
const stripeVer = deps['@stripe/stripe-react-native'];
const expoVer = deps['expo'];
// app.json は { "expo": {...} } 形式と、中身だけの形式の両方がある
const app = readJson('app.json');
const cfg = app ? (app.expo || app) : null;
if (!cfg) {
const dyn = ['app.config.js', 'app.config.ts'].find((f) => fs.existsSync(path.join(root, f)));
console.log(dyn
? `⚠️ ${dyn} は実行しないと中身が分かりません。plugins の expo-build-properties を目で確かめてください`
: '⚠️ app.json がありません');
process.exit(dyn ? 0 : 2);
}
// plugins の中から expo-build-properties を探す(文字列だけの指定なら設定なし)
let ios = {};
for (const pl of cfg.plugins || []) {
if (Array.isArray(pl) && pl[0] === 'expo-build-properties') ios = (pl[1] && pl[1].ios) || {};
}
const useFrameworks = ios.useFrameworks || '(未設定)';
// ドキュメント上、usePrecompiledModules の既定値は true
const precompiled = ios.usePrecompiledModules === undefined ? true : ios.usePrecompiledModules;
console.log(`expo : ${expoVer || '(なし)'}`);
console.log(`@stripe/stripe-react-native: ${stripeVer || '(なし)'}`);
console.log(`ios.useFrameworks : ${useFrameworks}`);
console.log(`ios.usePrecompiledModules: ${precompiled}${ios.usePrecompiledModules === undefined ? '(既定)' : ''}`);
if (!stripeVer) { console.log('✅ Stripe が入っていないので、この件の対象外です'); process.exit(0); }
if (useFrameworks === 'dynamic' && precompiled !== false) {
console.log('❌ Stripe + useFrameworks: dynamic + precompiled modules 有効。SDK 58 の prebuild / pod install で止まる組み合わせです');
console.log(' → ios.usePrecompiledModules を false にするか、Stripe を外した構成で確かめてください');
process.exit(1);
}
console.log('✅ この組み合わせでは、報告されている失敗の条件に当たりません');手元で、Stripe あり・dynamic・usePrecompiledModules 未指定の設定を置いて走らせると、終了コード 1 で止まります。usePrecompiledModules を false にすると通り、Stripe を外した構成も通ります。確かめられたのは、この判定の動きまでです。実際の pod install を走らせたわけではありません。
終了コードを返すようにしたのは、EAS のビルドを投げる前の確認として、package.json の scripts や CI にそのまま置けるからです。
逃げ道は三つ
1. precompiled modules を切る
報告者も Expo 側も、実際に通ったのはこの方法です。
{
"expo": {
"plugins": [
[
"expo-build-properties",
{
"ios": {
"useFrameworks": "dynamic",
"usePrecompiledModules": false
}
}
]
]
}
}代償はドキュメントの文面から読み取れます。usePrecompiledModules は、事前ビルド済みの XCFramework を使うか、ソースからビルドするかを切り替える設定です。切れば、Expo のモジュールをソースからビルドする側に戻るので、iOS のビルドは重くなる方向に動くはずです。どれだけ増えるかは、私は測っておりません。
2. Stripe 側の設定で逃がす
Expo 側のコメントによると、Stripe には Podfile で $StripeDisableSPM = true を指定する方法があります。ただし、Stripe がこの指定を非推奨としていること、マネージドなプロジェクトでは config plugin を書いて Podfile に差し込む必要があること、そして Expo 側もこの方法は試していないことが、あわせて書かれていました。
私なら、ここは選びません。試されていない道に、リリース前の時間を使うことになるからです。
3. 待つ
Issue は 10 月 22 日の時点でまだ open で、Expo 側は「precompiled modules を dynamic の必要な Pod に対応させるか、static のままにして警告を出すか」を設計上の判断として挙げています。Stripe 側が直す可能性にも触れられていました。つまり、いつ直るかの見通しはありません。
SDK 57 のまま出荷を続けて、58 への更新を止めておく、という選び方もできます。この件だけが理由で更新を止めるなら、Issue の動きを見ながらで構いません。
どれを選ぶか
私が使っている線引きは、次の二つの質問です。
- 今月中に iOS を出す必要がありますか。 必要なら、1 を選びます。Stripe を外すという選択肢は、課金が止まるので現実的ではありません。
- 今月中に出す必要はなく、58 に上げる理由も特にありませんか。 なら、3 を選びます。
個人開発で課金を足している状況だと、ビルドが少し重くなることより、審査に出せなくなることのほうが、はるかに高くつきます。ビルド時間は後から測って縮められますが、出荷の機会は戻ってきません。
直せる遅さは払い、取り戻せない遅れは払わない。 この線引きだけは、SDK の更新でも崩さないようにしています。
切ったあとに確認すること
usePrecompiledModules: false に変えたら、npx expo prebuild --platform ios --clean を一度走らせて、pod install が最後まで通ることを確かめます。EAS で投げるなら、失敗時は Install pods の段階のログを開いてください。
同じ SDK 58 まわりの点検として、.env.staging が読まれない件と、Info.plist に増える権限の説明文も、アップグレードの前に一度見ておくと、後で慌てずに済みます。Stripe の組み込み自体は、Rork × Stripe サブスクリプション実装にまとめています。
まずは check-stripe-linkage.js を一度だけ走らせて、自分のプロジェクトが当たるかどうかを確かめてみてください。