クラウドMac CIでCocoaPodsを再現可能にする実践手順

CI/CD ·約 9 分

クラウドMac CIでCocoaPodsを再現可能にする実践手順

同じコミットが開発マシンでは正常にビルドできるのに、クラウドMacの新しいジョブでは突然異なる依存関係が解決されたり、pod install の実行後に Podfile.lock がひそかに書き換えられたりすることがあります。こうした問題の原因は、多くの場合Xcodeそのものではなく、CocoaPodsのバージョン、キャッシュ、インストール方式がビルド入力として管理されていないことです。目指すべきなのは「毎回インストールを完了できること」ではありません。同じ入力から必ず同じ依存関係グラフが生成され、差異が生じた時点で即座に処理を停止できることです。

再現可能なインストールの入力を定義する

CocoaPodsの結果は、少なくとも Podfile、Podfile.lock、CocoaPodsのバージョン、RubyとBundlerの環境、ローカル開発Podが参照するソースコードという5つの入力に左右されます。ロックファイルだけをコミットしても、実行環境で任意のCocoaPodsバージョンを使用していれば、プロジェクトの生成形式やインストール動作に差異が生じる可能性があります。

リポジトリには .ruby-version、Gemfile、Gemfile.lock、Podfile、Podfile.lock をコミットします。Gemfile でCocoaPodsのバージョンを明示し、マシンにグローバルインストールされた最新版には依存しないようにします。

source ENV.fetch("BUNDLE_GEM_SOURCE")

ruby "3.3.4"
gem "cocoapods", "1.15.2"

CIのエントリーポイントでは、常に bundle exec pod を使用します。これにより、ログに記録されるバージョンが固定済みの環境と一致し、対話型Shellが偶然グローバルコマンドを優先してしまう事態も防げます。

Podfile.lock が固定するのは依存関係の解決結果です。依存関係を解決してプロジェクトを生成するCocoaPods自体のバージョンまでは固定しません。

インストールコマンドを厳格モードにする

通常のビルドで pod update を実行してはいけません。このコマンドはバージョン制約の範囲内で依存関係を再解決するため、手動で開始するアップグレード作業には適していますが、既存コミットの検証には向きません。CIでは、次のようなインストール用エントリーポイントを使用できます。

#!/bin/bash
set -euo pipefail

export LANG=en_US.UTF-8
export COCOAPODS_DISABLE_STATS=true
export CP_HOME_DIR="${RUNNER_TEMP}/cocoapods-home"

bundle config set path "${RUNNER_TEMP}/bundle"
bundle check || bundle install
bundle exec pod install --deployment --clean-install
git diff --exit-code -- Podfile.lock

--deployment はロックファイルの変更が必要な場合に処理を失敗させ、--clean-install は古いインストール状態によって問題が隠れる可能性を下げます。最後の git diff は第2の防波堤です。インストール中に何らかのスクリプトがロックファイルを書き換えても、ジョブはビルドへ進めません。

リポジトリに複数のワークスペースが含まれる場合は、それぞれに独立したインストールディレクトリとログファイルを用意し、並列ジョブ間で同じ Pods ディレクトリを共有しないようにします。

キャッシュを分離し、むやみに無効化しない

問題なのはキャッシュ自体ではなく、その境界が曖昧なことです。クラウドMacでは、複数のリポジトリがCocoaPodsのダウンロードキャッシュを共有したり、2つのジョブが同じBundlerディレクトリを同時に読み書きしたりすることで、状態が汚染されるケースがよくあります。「ロックファイルのダイジェスト + ツールのバージョン」でキャッシュキーを構成し、インストールが成功した後にだけキャッシュへ書き込むことを推奨します。

キャッシュ対象 推奨キャッシュキー 無効化条件
Bundlerインストールディレクトリ Gemfile.lockのダイジェスト + Rubyのバージョン いずれかの入力が変更された場合
CocoaPodsダウンロードキャッシュ Podfile.lockのダイジェスト + CocoaPodsのバージョン ロックファイルまたはツールが変更された場合
Podsディレクトリ Podfile.lockのダイジェスト + プロジェクト設定のダイジェスト Podfile、スクリプト、設定が変更された場合

Pods/Manifest.lock をキャッシュして、新しいインストール結果へ上書きしてはいけません。これは現在のサンドボックスに実際にインストールされている状態を記録するファイルであり、今回のインストールによって生成されるべきものです。キャッシュ復元後に異常が発生した場合は、まず分離したディレクトリでキャッシュを使わずにインストールします。キャッシュなしの結果が正常な場合に限り、問題の原因をキャッシュと判断できます。

ビルド前に依存関係グラフを検証する

インストールが成功しても、依存関係グラフが正しいとは限りません。少なくとも Podfile.lock と Pods/Manifest.lock が一致することを確認し、コミットされていない変更がないことも検証します。

test -f Podfile.lock
test -f Pods/Manifest.lock

cmp -s Podfile.lock Pods/Manifest.lock || {
  echo "CocoaPods sandbox differs from Podfile.lock" >&2
  exit 1
}

git status --porcelain -- Podfile Podfile.lock Gemfile Gemfile.lock

最後のコマンドから出力があった場合は、警告を表示するだけでなく、ジョブを失敗させる必要があります。:path を使用するローカルPodについては、対象ディレクトリのコミットIDまたはコンテンツのダイジェストも記録します。そうしなければ、ロックファイルが変わらないままディレクトリの内容だけが変化するため、再現可能性は確保できません。

アップグレード専用の経路を設ける

依存関係のアップグレードジョブでは、対象を指定した pod update SomePod を実行できます。ただし、ロックファイル内のバージョン変更、SPEC CHECKSUMS の変更、追加または削除された推移的依存関係という3種類の差分を必ず出力します。アップグレード用コミットには依存関係の変更だけを含め、アプリケーションコードの変更を混在させないでください。そうすることで、ロールバック時に以前のロックファイルへ直接戻せます。

失敗時の状態を保存し、順番に切り分ける

失敗した直後にすべてのキャッシュを削除してはいけません。まずCocoaPodsのバージョン、Rubyのバージョン、ロックファイルのダイジェスト、インストールログ、git status を保存し、その後で問題がどの層にあるのかを判断します。

  1. bundle check が失敗した場合は、最初にRubyと Gemfile.lock のプラットフォームを確認します。
  2. 厳格インストールでロックファイルの変更を要求された場合は、Podfile を変更したのにロックファイルをコミットし忘れていないか確認します。
  3. キャッシュを復元したジョブだけが失敗する場合は、独立したディレクトリでキャッシュなしのインストールを実行し、結果を比較します。
  4. Manifest.lock が一致しない場合は、インストールが中断されていないか、複数のジョブが Pods を共有していないか確認します。
  5. ローカルPodの内容が変動する場合は、ソースディレクトリにコミットIDまたはダイジェストの検証を追加します。

安定したCocoaPodsのフローは、最終的には退屈なほど予測可能であるべきです。入力が変わらなければインストール結果も変わらず、入力が変われば差分がコードレビューに入り、キャッシュが破損しても速度にだけ影響し、依存関係グラフは変わりません。これらの検証をXcodeのビルド前に実行すれば、数十分後に曖昧なエラーを見る代わりに、通常は数十秒で明確に失敗を検出できます。

よくある質問

CIではpod installとpod updateのどちらを使いますか?

通常のCIではpod install --deploymentを使います。pod updateは依存更新専用の作業で実行し、Podfile.lockの差分をレビュー対象にします。

Podfile.lockがあっても結果が変わるのはなぜですか?

RubyやCocoaPodsの版差、変更可能なローカルPod、破損したキャッシュ、インストール前にPodfileを書き換える処理が主な原因です。

PodsディレクトリはGitに含めるべきですか?

通常は含めません。Podfile、Podfile.lock、Gemfile、Gemfile.lockを管理し、CIで固定入力から復元する構成が扱いやすいです。

専用物理ノード

用途に合わせてLemonVMクラウドMacを選ぶ

Lemon M4とLemon M4 Proは東京、シンガポール、ソウル、香港、米国西部に対応し、日・週・月・四半期単位で利用できます。

クラウドMacを今すぐレンタル