開発プラクティス

クラウドMacでXcode警告ベースラインゲートを構築する

クラウドMacでXcode警告ベースラインゲートを構築する

長年メンテナンスされてきた Xcode プロジェクトには、数十件ものビルド警告が蓄積していることが珍しくありません。この状態で SWIFT_TREAT_WARNINGS_AS_ERRORS を有効にすると、メインブランチはすぐにビルドできなくなります。一方、警告を放置し続ければ、既存のノイズに新しい警告が埋もれてしまいます。より現実的なのは、レビュー済みの警告ベースラインをクラウド Mac 上で固定し、今回の変更で新たに追加された項目だけを CI で拒否する方法です。

ゲートの対象範囲を先に定義する

警告ゲートは、ログ内の warning: を単純に数える仕組みではありません。依存関係のダウンロード、ビルドスクリプト、システムツールも同じ文字列を出力することがあります。単純な件数集計は不安定なうえ、開発者がどのファイルを修正すべきかも特定できません。

警告は「リポジトリからの相対パス + 警告本文」という形式に正規化し、行番号、列番号、一時ディレクトリ、タイムスタンプを取り除くことを推奨します。これにより、コードの位置が前後しても新しいシグネチャは生成されません。一方、ファイル名が変更された場合や警告内容が変わった場合は、引き続きレビュー対象になります。

内容 シグネチャに含めるか 理由
リポジトリからの相対パス はい 対応責任のあるファイルを識別するため
警告本文 はい 問題の種類を区別するため
行番号と列番号 いいえ コード変更後にずれやすいため
DerivedData のパス いいえ ジョブごとに変わる可能性があるため
外部依存関係の警告 原則としていいえ 通常はチームが直接修正できないため

ベースラインは「現在把握され、受け入れられている技術的負債」を表すものであり、それらの警告が正しいことを意味するものではありません。既存の警告を削除するたびに、ベースラインもあわせて縮小する必要があります。

再現可能な警告シグネチャを生成する

まず、Workspace、Scheme、構成、SDK を固定します。開発環境では Debug、CI では Release を使うと、条件付きコンパイルの経路が変わり、生成したベースラインを比較できなくなります。次のスクリプトでは、呼び出し側が Workspace と Scheme を明示的に指定し、出力全体を保存します。

#!/bin/bash
set -u

WORKSPACE="${WORKSPACE:?set WORKSPACE}"
SCHEME="${SCHEME:?set SCHEME}"
ROOT="$(git rev-parse --show-toplevel)"
OUT="$ROOT/.ci-artifacts"
DERIVED="$OUT/DerivedData"

mkdir -p "$OUT"
rm -rf "$DERIVED"

set +e
xcodebuild \
  -workspace "$WORKSPACE" \
  -scheme "$SCHEME" \
  -configuration Release \
  -sdk iphoneos \
  -derivedDataPath "$DERIVED" \
  CODE_SIGNING_ALLOWED=NO \
  build 2>&1 | tee "$OUT/xcodebuild.log"
BUILD_STATUS=${PIPESTATUS[0]}
set -e

grep -F "$ROOT/" "$OUT/xcodebuild.log" \
  | grep " warning: " \
  | sed "s#${ROOT}/##" \
  | sed -E 's#:[0-9]+:[0-9]+: warning: # | #' \
  | LC_ALL=C sort -u \
  > "$OUT/warnings.current"

exit "$BUILD_STATUS"

CODE_SIGNING_ALLOWED=NO は、コンパイルだけを検証するジョブに適しています。アーカイブや署名検証が必要なパイプラインへ、そのまま適用してはいけません。また、ビルド失敗と警告の回帰は別々に処理する必要があります。xcodebuild 自体が失敗した場合は、空の警告ファイルで実際の障害を覆い隠すのではなく、その終了コードを優先して返します。

スクリプト出力と複数行診断を処理する

一部の Run Script フェーズは独自の警告を出力し、その形式にソースコード上の位置情報が含まれない場合があります。チームが管理しているスクリプトであれば、固定のプレフィックスを定め、別の解析ルールを用意できます。すべての出力を広範な正規表現ひとつで捕捉しないでください。ネットワーク関連の通知やツールの更新案内までベースラインに入ってしまいます。

Swift の補足診断行には通常 warning: が含まれないため、独立したシグネチャには適しません。ただし、完全なログには残しておく必要があります。ゲートは迅速な判定を担い、完全なログはコンテキストの復元を担います。両者を互いの代わりにすることはできません。

初回ベースラインをレビューしてコミットする

初回に生成された warnings.current を、自動ジョブからそのままベースラインへコピーしてはいけません。まずパスごとに担当者を割り当て、重複を削除し、生成ファイル、依存関係のソースコード、一時ディレクトリが含まれていないことを確認します。レビューが完了したら、次を実行します。

mkdir -p .ci
LC_ALL=C sort -u .ci-artifacts/warnings.current > .ci/xcode-warnings.baseline
git add .ci/xcode-warnings.baseline
git commit -m "Add reviewed Xcode warning baseline"

ベースラインはコードと一緒にバージョン管理する必要があります。ベースラインを増やすコミットでは、新しいシグネチャとその理由を必ず示します。失敗したジョブに新しい警告を自動で「学習」させてはいけません。最も阻止すべきタイミングで、ゲートが自ら通過を許してしまうからです。

Xcode をアップグレードすると、コンパイラの診断メッセージが変更されることがあります。正しい手順は、専用ブランチで完全なビルドを実行し、実際に増えた問題と文言だけの変更を切り分けたうえで、ベースラインを一括更新してツールチェーンのバージョンを記録することです。比較スクリプトへ際限なく緩いあいまい一致を追加するべきではありません。

CI では新しい警告だけを阻止する

現在の結果とベースラインをどちらもソートすれば、comm を使って現在のビルドにだけ存在するシグネチャを抽出できます。

BASELINE=".ci/xcode-warnings.baseline"
CURRENT=".ci-artifacts/warnings.current"
NEW=".ci-artifacts/warnings.new"

test -f "$BASELINE"
LC_ALL=C sort -u "$BASELINE" -o "$BASELINE"
LC_ALL=C sort -u "$CURRENT" -o "$CURRENT"
LC_ALL=C comm -13 "$BASELINE" "$CURRENT" > "$NEW"

if test -s "$NEW"; then
  printf '%s
' "New Xcode warnings detected:"
  cat "$NEW"
  exit 42
fi

xcodebuild.logwarnings.currentwarnings.new はまとめてアーカイブします。終了コード 42 はチーム内の取り決めにすぎません。重要なのは、「コンパイル失敗」と「新しい警告」を異なる原因として表示することです。開発者がパスと警告本文を確認できれば、1 回のフィードバックサイクル内で修正できます。

複数のジョブが異なる Target を並列ビルドする場合は、それぞれで結果を生成し、最後に結合、ソート、重複排除を行います。複数のプロセスから同じファイルへ同時に書き込まないでください。ファイルの切り詰めや出力の混在によって、散発的な誤判定が発生します。

ベースラインを永久に固定せず継続的に縮小する

ゲートの導入後は、既存の警告を修正するたびに、対応する行をベースラインから削除します。ビルドを阻止しないチェックを追加し、「ベースラインには存在するが、現在のビルドでは消えている」項目を検出して、コミット作成者にあわせて削除するよう促すこともできます。これにより、ベースラインは一方向に縮小し、誰にも内容を理解できない履歴一覧になることを防げます。

安定運用へ移行する前に、さらに次の 4 項目を確認します。CI とローカル環境で同じ Xcode バージョンを使用しているか、Scheme に想定どおりの Target が含まれているか、ロケール設定がツールの出力へ影響していないか、リポジトリパスのフィルタが自社管理のすべてのソースコードを対象にしているかです。OnceMac ノード上の実行ディレクトリもジョブごとに固定して作成し、前回のビルドで生成されたログや DerivedData を再利用しないようにします。

ベースラインがゼロになったら、差分比較を削除し、コンパイラで警告をエラーとして扱う方針を有効にできます。それまでは、ベースラインゲートが現実的な移行手段になります。既存の負債を覆い隠すことなく、新たな負債の増加も許しません。

よくある質問

最初から警告をエラーとして扱わないのはなぜですか?

既存警告が多いプロジェクトでは、すべてのビルドが直ちに失敗するためです。まず基準を固定して新規警告を止め、既存分を減らしてからエラー化する方が安全です。

ソースの行番号が変わると誤検出されますか?

行番号と列番号を署名から除けば、多くの誤検出を防げます。相対パスと警告本文をキーにし、Xcode更新による文言変更は更新作業として別途レビューします。

外部依存関係の警告も記録すべきですか?

通常は除外します。プロジェクト配下の管理対象ソースだけを抽出し、追跡が必要な社内依存関係のみ明示的なパス条件で追加します。

専用物理Mac mini

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

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

構成を選んでレンタル