MiniDebug エンジニアリングノート

クラウドMacでiOSアプリ容量の回帰ゲートを構築する

クラウドMacでiOSアプリ容量の回帰ゲートを構築する

一見するとごく普通の機能変更をマージしただけなのに、アーカイブ成果物が突然十数メガバイトも増えることがあります。ところがコミット履歴を確認しても、大きな画像や明確な新規依存関係は見当たりません。リリース直前にIPAを手動確認するだけでは、問題がすでに複数のブランチへ入り込んでいる可能性があります。より確実なのは、条件を固定したクラウドMacのビルド環境で容量のベースラインを保存し、アーカイブのたびにApp、メイン実行ファイル、動的フレームワークを自動で分けて計測する方法です。しきい値を超えた場合はマージを停止します。

比較可能な計測対象を先に定義する

「パッケージ容量」には少なくとも3種類の指標があり、同じグラフ上で混同してはいけません。

指標 計測対象 主な用途
App展開後容量 .xcarchive 内の .app ディレクトリ リソース、フレームワーク、ローカライズファイルの増加を検出
メイン実行ファイル容量 CFBundleExecutable が指すMach-Oファイル コード、静的ライブラリ、シンボルの変化を検出
IPA容量 エクスポート後の圧縮ファイル ユーザーが実際にダウンロードするパッケージの推移を把握

ゲートでは最初の2項目を主な判定材料にします。IPAは圧縮率やファイルの並び順に左右されるため、同じリソース群への小さな変更でも、圧縮後のサイズ差が大きくなったり小さくなったりします。デバッグシンボルはアーカイブ内の dSYMs ディレクトリにあり、ユーザーがインストールするパッケージの容量には含めません。ただし、後から問題を調査できるよう、別途アーカイブしておく必要があります。

容量差に意味を持たせるには、ビルド条件が同一でなければなりません。Xcodeのパス、構成、対象プラットフォーム、エクスポート方法、コンパイルオプションをすべて固定してください。

固定コマンドでアーカイブを生成する

まずクリーンな作業ディレクトリで依存関係を解決し、実機向けのReleaseアーカイブを生成します。workspace名とSchemeはジョブのパラメーターとして渡し、プロジェクト固有の情報をスクリプトへ直接埋め込まないようにします。

set -euo pipefail

WORKSPACE="${WORKSPACE:?missing WORKSPACE}"
SCHEME="${SCHEME:?missing SCHEME}"
OUT="${OUT:-$PWD/build-size}"
ARCHIVE="$OUT/App.xcarchive"

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

xcodebuild \
  -workspace "$WORKSPACE" \
  -scheme "$SCHEME" \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  -archivePath "$ARCHIVE" \
  clean archive \
  CODE_SIGNING_ALLOWED=NO

プロジェクトのアーカイブ工程で署名関連のスクリプトを実行する必要がある場合は、署名を無理に無効化せず、プロジェクトで従来使用している管理下の構成を使ってください。ゲートの目的は「本番ビルドと同じ条件」を再現することであり、すべてのリポジトリに通用する単一のコマンドを追求することではありません。初回導入時には、Release構成にテスト用リソース、診断ライブラリ、開発サーバーのアドレスが誤って含まれていないことも確認します。

App、実行ファイル、フレームワークを分けて計測する

次のスクリプトは、アーカイブ内から唯一のAppを検索し、メイン実行ファイル名を取得して、機械処理可能なTSVを出力します。du -sk はディレクトリ使用量を継続的に比較する用途に適しており、stat はメイン実行ファイルの正確なバイト数を取得するために使います。

set -euo pipefail

ARCHIVE="${1:?usage: measure.sh path/to/App.xcarchive}"
APP_ROOT="$ARCHIVE/Products/Applications"
APP_PATH="$(find "$APP_ROOT" -maxdepth 1 -type d -name '*.app' -print -quit)"

test -n "$APP_PATH"
EXECUTABLE="$(/usr/libexec/PlistBuddy \
  -c 'Print :CFBundleExecutable' "$APP_PATH/Info.plist")"

printf "kind	name	bytes
"
APP_KB="$(du -sk "$APP_PATH" | awk '{print $1}')"
printf "app	%s	%s
" "$(basename "$APP_PATH")" "$((APP_KB * 1024))"
printf "executable	%s	%s
" "$EXECUTABLE" \
  "$(stat -f '%z' "$APP_PATH/$EXECUTABLE")"

FRAMEWORKS="$APP_PATH/Frameworks"
if test -d "$FRAMEWORKS"; then
  find "$FRAMEWORKS" -maxdepth 1 -type d -name '*.framework' -print0 |
  while IFS= read -r -d '' item; do
    kb="$(du -sk "$item" | awk '{print $1}')"
    printf "framework	%s	%s
" "$(basename "$item")" "$((kb * 1024))"
  done
fi

レポートは合計値を表示するだけでなく、そのビルドの成果物として保存してください。回帰が発生したとき、レビュー担当者は増加分がメイン実行ファイル、特定のフレームワーク、またはAppディレクトリ全体のリソースのどこから生じたのかを直接判断できます。

リソースディレクトリをさらに細分化する

App全体の容量だけが増え、メイン実行ファイルとフレームワークが安定している場合は、Assets.car、ローカライズ用ディレクトリ、オフラインデータファイル、メディアリソースに対して個別に stat または du を実行します。重複しているように見えるファイルをすぐに削除してはいけません。異なるtarget、オンデマンドリソースのルール、ローカライズ工程によって生成されたものではないかを先に確認してください。

二重しきい値で誤検出を減らす

割合だけを使うと小さなコンポーネントが過敏に反応し、固定バイト数だけを使うと大規模なAppの継続的な肥大化を見逃す可能性があります。許容増加量は次のように定義できます。

allowed = max(8 MiB, baseline_app_bytes × 3%)
failed  = current_app_bytes - baseline_app_bytes > allowed

ここでの8 MiBと3%は導入時の初期値にすぎず、一般的な標準ではありません。まず正常なアーカイブを数回記録して変動幅を把握し、その後プロジェクトに合わせて調整してください。メイン実行ファイルと個々のフレームワークには、より小さい独立したしきい値を設定する必要があります。そうしないと、新たに追加された依存関係がApp全体の容量に埋もれてしまう可能性があります。

ベースラインファイルはソースコードと一緒にレビューしますが、失敗したジョブが自動で上書きしてはいけません。適切な流れは、ジョブが差分を出力し、開発者が理由を説明し、レビュー担当者が変更内容を要件に合致すると確認したうえで、同じ変更内でベースラインを更新するというものです。これにより、「増加を妥当と判断して受け入れた」のか、「チェックを通すために数値をリセットしただけ」なのかを区別できます。

よくある異常な容量増加を調査する

メイン実行ファイルが増えた場合は、まず静的依存関係、ジェネリクスの実体化、重複リンク、コンパイル条件を確認します。フレームワークが増えた場合は、依存関係のバージョンと埋め込み方法を確認します。リソースが増えた場合は、元画像、フォント、音声・動画ファイル、重複したローカライズ内容を調べます。

さらに、次の4つのよくある誤りを避けてください。

  1. 異なるXcode環境で生成された結果をそのまま比較する。
  2. シミュレーター向けビルドと実機向けアーカイブを混在させる。
  3. 合計容量だけを保存し、コンポーネントの内訳やコミット識別子を保存しない。
  4. ゲート失敗後にしきい値を自動で引き上げたり、ベースラインを上書きしたりする。

最終レポートには、少なくともコミット識別子、アーカイブ構成、App全体の容量、メイン実行ファイルの容量、容量が大きい上位のフレームワーク、ベースラインとの差分、判定結果を含めます。ゲートを通過した場合もレポートを保存しておくことで、緩やかでも継続的な増加傾向を把握できます。ゲートが失敗した場合は、アーカイブと詳細データを保持し、開発者のローカルで再ビルドするのではなく、同じ環境で再確認してください。

よくある質問

最終的なIPAサイズだけを比較してはいけないのはなぜですか?

IPAは圧縮ファイルであり、リソースの内容やファイル順序、アーカイブ処理によってサイズが変動します。未圧縮のアプリディレクトリと主実行ファイルを主要指標にします。

容量回帰ゲートのしきい値はどう決めますか?

正常なリリースを複数回計測して変動幅を把握し、絶対増分と相対増加率を併用します。例の8 MiBと3%は初期値であり、プロジェクト履歴に合わせて調整します。

依存関係の更新後は基準値をすぐ更新してよいですか?

先に追加されたシンボル、リソース、フレームワークが意図したものか確認します。増加理由を変更記録に残してレビューした後、その変更と一緒に基準値を更新します。

専有物理Mac

次回のiOSビルドをMiniDebug M4で実行しましょう。

M4、16GB RAM、256GB SSDを標準搭載。日単位、週単位、月単位、四半期単位でレンタルでき、シンガポール、東京、日本、ソウル、韓国、香港、米国東部の5つのノードから選択できます。実際の利用可能状況はコンソールのリアルタイム表示をご確認ください。

ノードを選択して注文する