エンジニアリングノート

クラウドMacで破損したXcodeビルドデータベースを復旧する

クラウドMacで破損したXcodeビルドデータベースを復旧する

リモートビルドノードを数日間連続稼働させていると、プロジェクトのコードに変更がないにもかかわらず、Xcodeのビルドが突然失敗することがあります。ログにはbuild.dbを開けない、ディスクイメージの形式が不正、データベースが破損しているといったエラーが記録され、再実行しても同じ段階で停止します。このときDerivedDataを丸ごと削除すれば手早く対処できますが、インデックスやモジュールキャッシュ、再利用可能なビルド成果物まで失われるため、その後のビルドが遅くなります。さらに、原因調査に最も有用な障害発生時の状態も消えてしまいます。

NUMACSのクラウドMacでこの種の問題に対処する場合は、プロジェクトのエントリーポイントを固定し、ログを保全し、ビルドプロセスが使用中でないことを確認してからデータベースの整合性を検査し、最後に対象プロジェクトのXCBuildDataだけを再構築する手順が安全です。

まずビルドデータベースが実際の原因か確認する

Xcodeの画面に表示された最後の1行だけで判断してはいけません。まず同じディレクトリから、同じschemeとconfigurationを指定してコマンドラインビルドを実行し、出力全体を保存します。

set -o pipefail
mkdir -p "$HOME/build-evidence"

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -destination 'generic/platform=iOS Simulator' \
  build 2>&1 | tee "$HOME/build-evidence/xcodebuild.log"

status=${PIPESTATUS[0]}
echo "$status" > "$HOME/build-evidence/exit-code.txt"
exit "$status"

特にbuild.dbdatabasemalformeddisk imageXCBuildDataを検索します。ログがソースコードのコンパイルエラー、依存関係の解決失敗、ディスク容量不足を明確に示している場合は、先に該当する問題を解決してください。すべてのビルド失敗をデータベースの問題と決めつけてはいけません。

復旧作業を始める前に、少なくとも完全なログ、終了コード、エラー発生時刻を保存してください。クリーンアップ後は、通常、元のデータベースの状態を再現できません。

現在のプロジェクトが実際に使用しているDerivedDataを特定する

同じクラウドMacに、同名プロジェクトのDerivedDataが複数残っている場合があります。ディレクトリの更新日時だけで推測すると、誤ったデータを削除するおそれがあります。xcodebuildから現在のビルド設定を取得し、BUILD_DIRを基にプロジェクトのDerivedDataルートを特定します。

BUILD_DIR=$(
  xcodebuild \
    -workspace App.xcworkspace \
    -scheme App \
    -configuration Debug \
    -showBuildSettings |
  awk -F ' = ' '$1 ~ /^[[:space:]]*BUILD_DIR$/ {print $2; exit}'
)

DERIVED_ROOT=$(dirname "$(dirname "$BUILD_DIR")")
XCBD="$DERIVED_ROOT/Build/Intermediates.noindex/XCBuildData"

printf 'DerivedData: %s
XCBuildData: %s
' \
  "$DERIVED_ROOT" "$XCBD"
test -d "$XCBD"

パイプラインで-derivedDataPathを使用している場合は、ユーザーディレクトリを改めて検索せず、その明示的なパスをそのまま使用してください。これにより、対話型セッションと無人実行タスクのどちらでもスクリプトが同じ結果を返します。

処理対象ファイルを記録する

クリーンアップする前に、データベースと関連ファイルのサイズおよび更新日時を記録します。

find "$XCBD" -maxdepth 1 -type f \
  -exec stat -f '%Sm %z %N' -t '%Y-%m-%dT%H:%M:%S%z' {} \; \
  > "$HOME/build-evidence/xcbuilddata-files.txt"

この一覧があれば、障害発生直前にデータベースが書き換えられたかを確認できます。また、同じ障害が繰り返されたときに、同一パスで発生しているかを比較する際にも役立ちます。

プロセスによる使用を除外し、build.dbを読み取り専用で検査する

実行中のビルドがデータベースを使用している場合、直接削除してはいけません。まず関連プロセスとファイルハンドルを確認します。

pgrep -alf 'Xcode|xcodebuild|XCBBuildService' || true
lsof "$XCBD/build.db" || true

現在のタスクに属するビルドがまだ実行中であれば、パイプラインの正規のキャンセル手順で停止し、子プロセスが終了するまで待ちます。マシン全体で同名のプロセスを無差別に終了しないでください。別の作業ディレクトリで有効なタスクが動作している可能性があります。

どのプロセスからも使用されていないことを確認したら、SQLiteデータベースを読み取り専用モードで検査します。

sqlite3 "file:$XCBD/build.db?mode=ro" \
  'PRAGMA quick_check;'

結果がokであれば、データベース構造に明らかな破損は検出されていません。ログに戻り、権限、ディスク容量、プロジェクト設定を引き続き確認します。コマンドが0以外の終了コードを返す、データベースを読み取れない、または整合性エラーを出力する場合に限り、再構築へ進みます。

検査結果 次の手順
lsofに実行中のビルドが表示される タスクを正規の手順で停止して再検査
quick_checkokを返す データベースを保持し、ほかのエラーを調査
使用中ではなく整合性検査に失敗する 対象のXCBuildDataを再構築
パスが存在しない workspace、scheme、DerivedDataのパラメータを確認

XCBuildDataだけを最小限の範囲で再構築する

破損したディレクトリはすぐに完全削除せず、まず証拠保全用のディレクトリへ移動します。コピー時間を短縮するため、移動は同じファイルシステム内で行います。

STAMP=$(date '+%Y%m%d-%H%M%S')
QUARANTINE="$HOME/build-evidence/XCBuildData-$STAMP"

mv "$XCBD" "$QUARANTINE"
mkdir -p "$(dirname "$XCBD")"

続いて、同じビルドパラメータで再実行します。Xcodeによって新しいXCBuildDataとbuild.dbが作成され、DerivedData内のほかのディレクトリはそのまま保持されます。

set -o pipefail

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -destination 'generic/platform=iOS Simulator' \
  build 2>&1 | tee "$HOME/build-evidence/recovery-build.log"

最小限の再構築後も失敗する場合でも、すぐに削除範囲を広げないでください。まず新旧のログを比較し、エラーが引き続きデータベースを指しているか確認します。エラーが依存関係、権限、ソースコードの問題に変わっていれば、データベース障害は解消されています。その後は新しいエラーに応じて対処します。

2回のビルドで検証を完了し、再発を防ぐ

1回成功しただけでは、状態が安定したとは判断できません。1回目のビルドでは新しいデータベースを生成し、2回目のインクリメンタルビルドでは、そのデータベースを再度読み取り、更新できることを検証します。どちらもworkspace、scheme、configuration、destination、DerivedDataのパスを固定し、それぞれのログと終了コードを個別に保存します。

検証時には次の項目を確認します。

  1. 新しいbuild.dbが生成され、読み取り専用のquick_checkokを返す。
  2. クリーンビルドとインクリメンタルビルドがどちらも正常に完了する。
  3. データベース形式またはXCBuildDataの読み取りエラーがログに再発していない。
  4. ビルド成果物が想定したconfigurationと出力先ディレクトリから生成されている。
  5. 復旧を確認するまで、障害発生時の証拠が削除されていない。

再発防止で重要なのは、定期的にキャッシュを空にすることではありません。各作業ディレクトリに明確なDerivedDataパスを割り当て、タスクのキャンセル時にはビルドの子プロセスが終了するまで待機できるようにすることです。また、実行ノードでは空きディスク容量を継続的に監視し、データベースへの書き込み中に容量が尽きて不完全なファイルが残らないようにします。

同じパスで破損が繰り返される場合は、クリーンアップコマンドを毎回のビルド前に実行するのではなく、発生前の異常終了、ディスクの状態、Xcodeのバージョンを記録してください。毎回キャッシュをリセットすると、本当の問題が隠れるだけでなく、インクリメンタルビルドの利点も失われます。

最終的には、証拠の保存、パスの確認、読み取り専用検査、最小限の再構築という4つの作業に手順を集約できます。これらの手順でも復旧できない場合に限り、より広い範囲のDerivedDataを削除することを検討してください。これにより、障害復旧時間を短縮しながら、再発原因の特定に必要な情報も十分に保全できます。

よくある質問

build.dbエラーではDerivedData全体をすぐ削除すべきですか?

最初の対応としては推奨しません。関連ビルドを停止してログを保存し、対象プロジェクトのXCBuildDataだけを削除して再生成します。

データベース破損とプロセスによる占有はどう見分けますか?

まずlsofでbuild.dbを開いているプロセスを確認します。占有がなく、読み取り専用のSQLite quick_checkが失敗する場合は破損を疑います。

復旧後の確認には何が必要ですか?

workspace、scheme、configuration、DerivedDataパスを固定し、クリーンビルドと増分ビルドを続けて実行して終了結果を保存します。

Numacs クラウドMac

ビルド作業を専用の物理ワークステーションへ

2種類の Apple Silicon 構成を、専用の物理マシンとして提供します。仮想マシンではなく、日単位、週単位、月単位、四半期単位でレンタルできます。シンガポール、東京、ソウル、香港のノードに対応しています。

デバイスを選んで注文