開発プラクティス

クラウドMacで作るXcodeプロジェクト整合性ゲート

クラウドMacで作るXcodeプロジェクト整合性ゲート

複数人が同時に Xcode プロジェクトを変更する場合、最も危険なのはコンパイルエラーではありません。project.pbxproj が壊れているにもかかわらず、テキスト上のマージが通ってしまうことです。ファイル参照の削除、Scheme の共有漏れ、GUI 操作による重要なビルド設定の意図しない書き換えなどは、アーカイブ段階まで発覚しないことがあります。より確実なのは、クラウド Mac 上でプロジェクトファイルを独立したビルド入力として扱い、低コストな整合性チェックを実行してから、依存関係の解決、テスト、アーカイブへ進む方法です。

ゲートで確認すべき項目

実用的なゲートでは、少なくとも次の4層を対象とし、この順序を崩さないことが重要です。最初にテキスト上の残骸を確認し、次にファイル形式を検証します。その後、Xcode に実際のプロジェクトを解析させ、最後に重要な設定を比較します。前の層で失敗した場合は即座に終了し、それ以上ビルド時間を消費する必要はありません。

レイヤー 確認対象 失敗が意味すること
テキスト コンフリクトマーカー、空ファイル マージが完了していない
構造 project.pbxproj plist 構造を解析できない
プロジェクト Project、Target、Scheme Xcode がプロジェクトモデルを構築できない
設定 SDK、デプロイ対象バージョン、署名方式 設定が意図せず変化している

git diff にコンフリクトがないからといって、Xcode プロジェクトが有効とは限りません。バージョン管理ツールが確認できるのはテキストのマージが完了していることだけであり、オブジェクト参照、Target、Scheme を Xcode が引き続き認識できるかどうかまでは把握できません。

チェック用スクリプトでは、実行ディレクトリと Xcode のパスを固定し、対話型 Shell の現在の状態に依存しないようにします。プロジェクトに .xcodeproj.xcworkspace の両方がある場合、通常のビルドでは実際に使用するエントリーポイントを指定しますが、基盤となる project.pbxproj も個別に確認する必要があります。

マージ残骸と形式エラーを先に遮断する

次のスクリプトを ci/check_xcode_project.sh として保存できます。この例ではプロジェクト名を App.xcodeproj としています。実際に使用する際は環境変数で上書きし、複数の CI 設定にプロジェクト名を分散させないようにします。

#!/bin/bash
set -euo pipefail

PROJECT_PATH="${PROJECT_PATH:-App.xcodeproj}"
PBXPROJ="${PROJECT_PATH}/project.pbxproj"

test -s "$PBXPROJ" || {
  echo "project.pbxproj is missing or empty"
  exit 1
}

if grep -nE '^(<<<<<<<|=======|>>>>>>>)' "$PBXPROJ"; then
  echo "merge conflict markers found"
  exit 1
fi

plutil -lint "$PBXPROJ"
xcodebuild -list -json -project "$PROJECT_PATH" > /tmp/xcode-project-list.json
plutil -lint /tmp/xcode-project-list.json

コンフリクトマーカーは行頭で一致させ、業務上のファイル名やコメントに偶然連続した等号が含まれていても誤検知しないようにします。plutil を通過した後で xcodebuild -list を実行します。Xcode のプロジェクトモデルを構築するのは後者だけだからです。コマンドがゼロ以外で終了した場合は、自動的な「修復」やプロジェクトファイルの再生成を行わず、標準エラー出力を保存してください。

Workspace プロジェクトの扱い

Workspace を使用する場合は、エントリーポイントの検証も追加します。

WORKSPACE_PATH="${WORKSPACE_PATH:-App.xcworkspace}"
xcodebuild -list -json -workspace "$WORKSPACE_PATH" \
  > /tmp/xcode-workspace-list.json
plutil -lint /tmp/xcode-workspace-list.json

Workspace だけを検証し、基盤となるプロジェクトの確認を省略してはいけません。Workspace が認識されても、その中に含まれるすべてのプロジェクトファイルからマージ残骸がなくなっているとは限りません。

共有 Scheme とターゲット構成を検証する

CI で必要な Scheme は、必ずバージョン管理の対象にします。まず共有 Scheme ファイルを確認し、次に xcodebuild -list の JSON 出力から名前が存在することを検証できます。解析にはシステム付属の Python で十分であり、追加の依存関係をインストールする必要はありません。

SCHEME_NAME="${SCHEME_NAME:-App}"
SCHEME_FILE="${PROJECT_PATH}/xcshareddata/xcschemes/${SCHEME_NAME}.xcscheme"

test -s "$SCHEME_FILE" || {
  echo "shared scheme is missing: $SCHEME_NAME"
  exit 1
}

python3 - "$SCHEME_NAME" /tmp/xcode-project-list.json <<'PY'
import json
import sys

expected = sys.argv[1]
path = sys.argv[2]

with open(path, encoding="utf-8") as handle:
    payload = json.load(handle)

schemes = payload.get("project", {}).get("schemes", [])
if expected not in schemes:
    raise SystemExit(f"expected scheme not found: {expected}")
PY

リポジトリに複数のアプリや拡張機能が含まれる場合は、「Scheme が1つ以上存在する」という条件で済ませず、明示的な Scheme リストを管理してください。また、個人用ディレクトリ内の Scheme を CI の入力として扱ってはいけません。別の物理ノードへ切り替えると、これらの未共有ファイルは存在しません。

重要なビルド設定のスナップショットを作る

プロジェクトを正常に解析できても、設定が誤って変更されている可能性はあります。スナップショットには、成果物の意味を変えるフィールドだけを選ぶことを推奨します。たとえば PRODUCT_BUNDLE_IDENTIFIERIPHONEOS_DEPLOYMENT_TARGETSWIFT_VERSIONCODE_SIGN_STYLESUPPORTED_PLATFORMS です。パスや一時ディレクトリを含む -showBuildSettings の完全な出力は保存しないでください。ノード間で比較すると大量のノイズが発生します。

xcodebuild -project "$PROJECT_PATH" \
  -scheme "$SCHEME_NAME" \
  -configuration Release \
  -showBuildSettings |
awk -F ' = ' '
  /PRODUCT_BUNDLE_IDENTIFIER =/ ||
  /IPHONEOS_DEPLOYMENT_TARGET =/ ||
  /SWIFT_VERSION =/ ||
  /CODE_SIGN_STYLE =/ ||
  /SUPPORTED_PLATFORMS =/ {
    gsub(/^[ 	]+/, "", $1)
    print $1 " = " $2
  }
' | LC_ALL=C sort > /tmp/build-settings.current

diff -u ci/build-settings.release /tmp/build-settings.current

最初に作成した ci/build-settings.release は、バージョン管理へ追加します。デプロイ対象バージョンや署名方式を意図的に変更する場合は、設定の差分をレビューしたうえで、同じ変更内でスナップショットも更新します。CI にベースラインを自動上書きさせてはいけません。そうすると、あらゆる設定変化が新しい標準として扱われてしまいます。

パイプラインへの組み込みと誤検知への対処

整合性ゲートは、依存関係のダウンロードや完全なビルドより前に配置し、スクリプトの失敗段階が分かるようにします。推奨する順序は、コードのチェックアウト、固定 Xcode の選択、プロジェクトゲートの実行、依存関係の解決、コンパイル、テスト、アーカイブです。ローカル環境とクラウド Mac では、次のように同じエントリーポイントを使用します。

DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer" \
PROJECT_PATH="App.xcodeproj" \
SCHEME_NAME="App" \
bash ci/check_xcode_project.sh

よくある誤検知の原因は3つあります。1つ目は、開発者が Scheme を変更したものの共有していないケースです。ノード上で手動作成するのではなく、xcshareddata/xcschemes をコミットして解決します。2つ目は、スナップショットに絶対パスが含まれているケースです。対象フィールドを絞り込んでください。3つ目は、タスクごとに異なる Xcode パスを使用しているケースです。ゲートの開始前に xcodebuild -version を出力して確認し、同時に DEVELOPER_DIR を固定します。

チェックが失敗した場合、チケットまたはビルド記録には、少なくともコミット識別子、Xcode のバージョン、失敗したコマンド、標準エラー、プロジェクトのエントリーポイントを残します。機密値を含む完全な環境変数はアップロードしないでください。物理ノードを変更する必要がある場合は、コンソールで現在選択できる構成を確認し、新しいノードで同じリポジトリからゲートを再実行します。古いノードの一時的なプロジェクト状態をコピーしてはいけません。

マージ前チェックリスト

ゲートをコミットする前に、次の項目を1つずつ確認してください。スクリプトで set -euo pipefail が有効になっていること。プロジェクトパスと Scheme を環境変数で上書きできること。コンフリクトマーカーのチェック対象がプロジェクトファイルだけであること。Project と Workspace が実際のエントリーポイントに応じて個別に解析されること。共有 Scheme がバージョン管理に追加されていること。設定スナップショットに安定したフィールドだけが含まれていること。ベースラインの更新がすべて人手でレビューされていること。

このチェックはコンパイルやテストの代わりにはなりませんが、従来はアーカイブ段階で発覚していた種類の問題を、数秒で終わる工程まで前倒しできます。プロジェクトファイルを明示的かつレビュー可能で、繰り返し検証できる入力として扱えば、メインブランチが健全かどうかを「特定の開発者のローカル環境ではまだプロジェクトを開ける」という事実に頼って判断する必要がなくなります。

よくある質問

plutilの検査だけでXcodeプロジェクトの正常性を確認できますか?

できません。plutilは主に構文と構造を検査するため、xcodebuild -listも実行し、Xcodeがプロジェクトを解析できることと必要なSchemeが見えることを確認します。

整合性ゲートはいつ実行するべきですか?

各変更の完全ビルドより前に実行し、共有ブランチでも再実行します。軽量な検査を先に置くことで、壊れた工程への計算時間を減らせます。

意図したビルド設定変更はどう扱いますか?

差分をレビューした後、バージョン管理された基準スナップショットを明示的に更新します。CIが自動更新すると意図しない変化を判別できません。

専用物理Mac mini

次のビルドを専用の物理ノードで実行しましょう。

チップ、メモリ、ストレージ、ノード、利用期間を選択し、他のお客様とリソースを共有しないクラウドMacを利用できます。

構成を選んでレンタル