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 制限を回避しつつ、レビュアーはリンクをクリックするだけでアプリを試せるようになりました。レビューサイクルが速くなり、開発効率が向上しています。