Xcode Cloud + Tophat で iOS PR レビューを効率化

はじめに

iOS アプリの PR レビューでは、コードの変更内容だけでなく、実際の動作を確認することが重要です。しかし、動作確認にはブランチを切り替えてからビルドしてアプリを起動する必要があり大変です。

.app ファイルは依存ライブラリや画像リソースを含むと容易に 25MB を超えてしまいます。GitHub の PR コメントには 25MB までしか添付できないため、直接添付することができません。

TestFlight での配布は Apple Developer Program の設定が必要で、配布までに時間がかかります。さらに、TestFlight には 1 日あたりのアップロード回数に Rate Limit があるため、頻繁な PR レビューには向いていません。

そこで、Cloudflare R2(無料枠)と Tophat を組み合わせることで、ワンクリックでアプリをインストールできる仕組みを構築しました。レビュアーは PR コメントのリンクをクリックするだけで、自動的に Simulator にアプリがインストールされます。

アーキテクチャ

Xcode Cloud → Tophat ワークフロー

フローは次のようになります。

  1. GitHub で PR を作成すると、Xcode Cloud がトリガーされます
  2. Xcode Cloud でビルドし、.app を .zip に圧縮します
  3. .zip ファイルを Cloudflare R2 にアップロードします
  4. GitHub PR コメントに Tophat リンクを自動投稿します
  5. レビュアーがリンクをクリックすると、Tophat が R2 から .zip を取得します
  6. Tophat が自動で Simulator にインストールし、アプリを起動します

実装方法

1. Cloudflare R2 バケット作成

R2 コンソールでバケットを作成し、R2.dev サブドメインを有効化します。これにより、公開アクセス可能な URL が発行されます。

Xcode Cloud に以下の環境変数を設定します。

R2_BUCKET_NAME=todomemo-pr-builds
R2_ENDPOINT_URL=https://xxxxxxxx.r2.cloudflarestorage.com
R2_PUBLIC_URL=https://pub-xxxxxxxx.r2.dev
AWS_ACCESS_KEY_ID=xxxxx
AWS_SECRET_ACCESS_KEY=xxxxx
GITHUB_TOKEN=ghp_xxxxx

R2_PUBLIC_URL は、R2 バケットの設定で「R2.dev サブドメインを許可」を有効化すると発行される公開 URL です。バケット名は含めず、https://pub-xxx.r2.dev の形式で設定します。スクリプト内で /pr-builds/... と結合されます。

2. Xcode Cloud ビルドスクリプト

ci_scripts/ci_post_xcodebuild.sh を作成します。

Tophat は .zip ファイルからのインストールに対応しているため、.app を zip 化してアップロードします。

#!/bin/bash
set -e

# JSON エスケープ関数
function json_escape() {
  local string="$1"
  string="${string//\\/\\\\}"
  string="${string//\"/\\\"}"
  string="${string//$'\n'/\\n}"
  echo "$string"
}

# エラーハンドリング
function error_exit {
  echo "❌ $1" >&2
  exit 1
}

# 必須環境変数チェック
for var in R2_BUCKET_NAME R2_ENDPOINT_URL R2_PUBLIC_URL AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY GITHUB_TOKEN; do
  [[ -z ${!var} ]] && error_exit "${var} が未設定"
done

# .app を検索(Debug ビルド)
APP_PATH=$(find "$CI_DERIVED_DATA_PATH" -name "*.app" -type d \
  -path "*/Build/Products/Debug-*/*.app" \
  -print -quit 2>/dev/null)

[[ -z $APP_PATH ]] && error_exit ".app が見つかりません"

# AWS CLI インストール
brew install awscli

# .app を zip 化
APP_NAME=$(basename "$APP_PATH" .app)
ZIP_NAME="${APP_NAME}.app.zip"
ZIP_PATH="${CI_DERIVED_DATA_PATH}/${ZIP_NAME}"

cd "$(dirname "$APP_PATH")"
zip -rq "$ZIP_PATH" "$(basename "$APP_PATH")"

# R2 アップロード
aws s3 cp "$ZIP_PATH" "s3://${R2_BUCKET_NAME}/pr-builds/pr-${CI_PULL_REQUEST_NUMBER}/${ZIP_NAME}" \
  --endpoint-url "${R2_ENDPOINT_URL}" \
  --region auto || error_exit "R2 アップロード失敗"

# URL 生成
PUBLIC_URL="${R2_PUBLIC_URL}/pr-builds/pr-${CI_PULL_REQUEST_NUMBER}/${ZIP_NAME}"
TOPHAT_URL="http://localhost:29070/install/http?url=${PUBLIC_URL}&platform=ios&destination=simulator"

# PR コメント投稿
COMMENT_BODY=$(cat <<EOF
## 📱 ビルド完了

Xcode Cloud でビルドが完了しました。

### 🚀 ワンクリックインストール(Tophat 必須)

<a href="${TOPHAT_URL}">シミュレーターで起動</a>

> **注意**: このリンクを使用するには Tophat が必要です。
>
> 初回のみ: [Tophat をダウンロード](https://github.com/Shopify/tophat/releases)(macOS 15+ 必要)
EOF
)

ESCAPED_COMMENT=$(json_escape "$COMMENT_BODY")
curl -s -X POST \
  -H "Authorization: token ${GITHUB_TOKEN}" \
  -H "Accept: application/vnd.github.v3+json" \
  "https://api.github.com/repos/${CI_PULL_REQUEST_TARGET_REPO}/issues/${CI_PULL_REQUEST_NUMBER}/comments" \
  -d "{\"body\":\"${ESCAPED_COMMENT}\"}" || error_exit "コメント投稿失敗"

3. Tophat セットアップ

Tophat は macOS 15 以降で動作します。Tophat Releases から最新版をダウンロードしてインストールします。

Tophat は iOS だけでなく Android にも対応しています。

使い方は次のようになります。

  1. Tophat を起動します(localhost:29070 でサーバー起動)
  2. PR コメントのリンクをクリックします
  3. 自動的に .zip をダウンロードし、Simulator にインストールし、アプリが起動します

4. GitHub の制約対応

GitHub はセキュリティ上、tophat:// のようなカスタム URL スキームをサニタイズします。

そのため、Tophat の HTTP サーバー(localhost:29070)を利用します。

# GitHub で動作する形式
http://localhost:29070/install/http?url=...

まとめ

Xcode Cloud、Cloudflare R2、Tophat を組み合わせることで、PR レビュー時の .app 配布を完全自動化できました。

GitHub の 25MB 制限を回避しつつ、レビュアーはリンクをクリックするだけでアプリを試せるようになりました。PR レビューの速度が大きく向上し、とても便利です。

参考リンク