GitHub Actions + Tophat で Android PR レビューを効率化
📋目次
背景
Android アプリの PR レビューでは、コードの変更内容だけでなく、実際の動作を確認することが重要です。
課題
実際の動作を確認するには、ビルドした APK ファイルをレビュアーに配布します。しかし、Android APK は依存ライブラリやリソースを含むと、容易に GitHub のコメント添付上限 25MB を超えてしまうため、PR コメントに直接添付できません。
レビュアーがアプリを試すには、いくつかの方法がありました。PR ブランチをローカルでビルドする方法は時間がかかります。Google Play Console を使った配布では設定が煩雑で配布まで時間がかかります。ファイル共有サービス経由で APK を送信する方法は手動操作が多く煩雑です。
これらはすべて手間がかかり、PR レビューの速度を低下させます。
解決策
Cloudflare R2(無料枠)+ Tophat のワンクリックインストールで、PR レビュー時のアプリ配布を簡単にしました。
レビュアーは PR コメントのリンクをクリックするだけで、自動的にエミュレーターにアプリがインストールされます。
アーキテクチャ
フロー
- GitHub PR 作成 → GitHub Actions トリガー
- GitHub Actions でビルド & APK 生成
- Cloudflare R2 にアップロード
- GitHub PR コメント に Tophat リンク自動投稿
- レビュアー がリンククリック → Tophat が R2 から APK 取得
- Tophat が自動でエミュレーターにインストール & 起動
実装方法
1. Cloudflare R2 バケット作成
R2 コンソールでバケットを作成し、R2.dev サブドメインを有効化します。これにより、公開アクセス可能な URL が発行されます。
GitHub リポジトリの Secrets に以下の環境変数を設定します。
Cloudflare R2 関連の設定は次の通りです。
R2_BUCKET_NAME=your-bucket-name
R2_ENDPOINT_URL=https://xxxxxxxx.r2.cloudflarestorage.com
R2_PUBLIC_URL=https://pub-xxxxxxxx.r2.dev
R2_ACCESS_KEY_ID=xxxxx
R2_SECRET_ACCESS_KEY=xxxxx
APK 署名関連の設定(オプション)は次のようになります。
DEBUG_KEYSTORE_BASE64=xxxxx
DEBUG_KEYSTORE_PASSWORD=xxxxx
DEBUG_KEY_ALIAS=xxxxx
DEBUG_KEY_PASSWORD=xxxxx
R2_PUBLIC_URL について
R2 バケットの設定で「R2.dev サブドメインを許可」を有効化すると発行される公開 URL です。バケット名は含めず、https://pub-xxx.r2.dev の形式で設定します。ワークフロー内で /android/... と結合されます。
2. GitHub Actions ワークフロー
.github/workflows/pr-review.yml を作成します。
name: PR Review - Build & Deploy APK
on:
pull_request:
types: [opened, synchronize]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Set up JDK 17
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Setup Debug Keystore
run: |
echo "${{ secrets.DEBUG_KEYSTORE_BASE64 }}" | base64 -d > debug.keystore
ls -l debug.keystore
- name: Build Debug APK
env:
CI: true
DEBUG_KEYSTORE_PASSWORD: ${{ secrets.DEBUG_KEYSTORE_PASSWORD }}
DEBUG_KEY_ALIAS: ${{ secrets.DEBUG_KEY_ALIAS }}
DEBUG_KEY_PASSWORD: ${{ secrets.DEBUG_KEY_PASSWORD }}
run: ./gradlew assembleDebug --no-daemon --stacktrace
- name: Find APK
id: find_apk
run: |
APK_PATH=$(find app/build/outputs/apk/debug -name "*.apk" | head -n 1)
if [ -z "$APK_PATH" ]; then
echo "Error: APK not found"
exit 1
fi
echo "apk_path=$APK_PATH" >> $GITHUB_OUTPUT
echo "apk_name=$(basename $APK_PATH)" >> $GITHUB_OUTPUT
echo "Found APK: $APK_PATH"
- name: Cache AWS CLI
id: cache-aws-cli
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: ~/.local/aws-cli
key: ${{ runner.os }}-aws-cli-${{ hashFiles('**/awscliv2.zip') }}
restore-keys: |
${{ runner.os }}-aws-cli-
- name: Install AWS CLI v2
if: steps.cache-aws-cli.outputs.cache-hit != 'true'
run: |
curl -sSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip -q awscliv2.zip
./aws/install -i ~/.local/aws-cli -b ~/.local/bin
- name: Add AWS CLI to PATH
run: |
echo "$HOME/.local/bin" >> $GITHUB_PATH
aws --version
- name: Upload to Cloudflare R2
id: upload
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }}
R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }}
R2_PUBLIC_URL: ${{ secrets.R2_PUBLIC_URL }}
run: |
set -euo pipefail
APK_PATH="${{ steps.find_apk.outputs.apk_path }}"
APK_NAME="${{ steps.find_apk.outputs.apk_name }}"
# Generate unique filename with PR number and commit SHA
PR_NUMBER="${{ github.event.pull_request.number }}"
COMMIT_SHA="${{ github.event.pull_request.head.sha }}"
SHORT_SHA="${COMMIT_SHA:0:7}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
UNIQUE_NAME="pr-${PR_NUMBER}-${SHORT_SHA}-${TIMESTAMP}.apk"
echo "Uploading ${APK_NAME} to R2 as ${UNIQUE_NAME}..."
# Upload to R2
aws s3 cp "$APK_PATH" "s3://${R2_BUCKET_NAME}/android/${UNIQUE_NAME}" \
--endpoint-url "$R2_ENDPOINT_URL" \
--content-type "application/vnd.android.package-archive" \
--no-progress
# Generate public URL
PUBLIC_URL="${R2_PUBLIC_URL}/android/${UNIQUE_NAME}"
# Generate Tophat install URL
TOPHAT_URL="http://localhost:29070/install/http?url=${PUBLIC_URL}"
echo "Upload successful!"
echo "Public URL: ${PUBLIC_URL}"
echo "public_url=$PUBLIC_URL" >> $GITHUB_OUTPUT
echo "tophat_url=$TOPHAT_URL" >> $GITHUB_OUTPUT
echo "unique_name=$UNIQUE_NAME" >> $GITHUB_OUTPUT
- name: Post PR Comment
if: success()
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
env:
PUBLIC_URL: ${{ steps.upload.outputs.public_url }}
TOPHAT_URL: ${{ steps.upload.outputs.tophat_url }}
UNIQUE_NAME: ${{ steps.upload.outputs.unique_name }}
with:
script: |
const publicUrl = process.env.PUBLIC_URL;
const tophatUrl = process.env.TOPHAT_URL;
const uniqueName = process.env.UNIQUE_NAME;
const commitSha = context.payload.pull_request.head.sha.substring(0, 7);
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const body = `## 📱 ビルド完了
GitHub Actions でビルドが完了しました。
**Commit:** \`${commitSha}\` | **Build:** [View Logs](${runUrl})
### 🚀 ワンクリックインストール(Tophat 必須)
<a href="${tophatUrl}">エミュレーターで起動</a>
> **注意**: このリンクを使用するには Tophat が必要です。
>
> 初回のみ: [Tophat をダウンロード](https://github.com/Shopify/tophat/releases)(macOS 15+ 必要)
`;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
3. Tophat セットアップ
macOS 15+ が必要です。Tophat Releases から最新版をダウンロードしてインストールします。
ちなみに、Tophat は Android だけでなく iOS にも対応しています。
使い方はとても簡単です。まず Tophat を起動すると、localhost:29070 でサーバーが起動されます。次に PR コメントのリンクをクリックします。すると自動的に APK ダウンロード、エミュレーターインストール、アプリ起動が実行されます。
4. GitHub の制約対応
GitHub はセキュリティ上、tophat:// のようなカスタム URL スキームをサニタイズします。
そのため、Tophat の HTTP サーバー(localhost:29070)を利用します。
# GitHub で動作する形式
http://localhost:29070/install/http?url=...
まとめ
GitHub Actions、Cloudflare R2、Tophat を組み合わせることで、Android アプリの PR レビュー時の APK 配布を完全自動化できました。
GitHub の 25MB 制限を回避しつつ、レビュアーはリンクをクリックするだけでアプリを試せるようになりました。レビューサイクルが速くなり、開発効率が向上しています。