より高速なモバイルCIのためのEAS WorkflowsとGitHub Actionsの統合方法
EAS WorkflowsがGitHub Actionsを強化し、最も困難なモバイルタスクを処理することで、React Nativeチームがより高速にビルドし、よりクリーンなCI設定を維持できる方法をご紹介します。
開発者とEAS Workflowsについて話すとき、他のCI/CDソリューションの話題が必然的に出てきます。それは当然のことです!世の中には多くの強力な継続的インテグレーション・デリバリーサービスが存在します。多くの開発者はすでにそれらのサービスに投資しており、重要なプロセスを変更することは大きな決断です。
開発者が知りたいのは次の2つのことです:
- EAS Workflowsは他のソリューションと何が違うのか?
- 既存のCIランナーにEAS Workflowsをどのように統合できるのか?
GitHub Actionsは当然ながら頻繁に比較対象となります。EAS Workflows自体にはGitHubアプリ統合があり、GitHub Actionsがサポートするのと同じGitHubイベント(プルリクエストの更新、ブランチのマージなど)からワークフローを実行できます。EAS Workflowsを検討している場合、すでにGitHub Actionsを試したことがあるか、本番環境で多くのアクションを実行している可能性が高いでしょう。
EAS WorkflowsはReact Nativeアプリを念頭に置いて特別に構築されたCI/CDサービスです。最小限の設定でEAS Build、Update、Submitと連携する組み込みジョブが含まれています。多くのモバイル中心のプロセスで他のCIソリューションの代わりに使用できますが、それらと併用することも可能です。
現在のCI/CDとWorkflowsを組み合わせることで、最適なプロセスを実現できます。そうすることで、署名を簡素化し、ビルドを高速化し、重要なジョブのみを実行し、最終的により高速で柔軟性があり、保守しやすいパイプラインを作成できます。
EAS Workflowsが提供するものと、GitHub Actionsとの比較・対比、組み合わせによってどのように統合できるかを理解する旅に出ましょう。React Nativeアプリ用のシンプルなGitHub Actionsから始めて、EAS Workflowsでの同等のものを確認し、その後、最も有用な場面でWorkflowsを使用しながら、すでにうまく機能しているものはGitHub Actionsで継続実行するという境界線を曖昧にしていきます。
シンプルなビルドワークフロー
mainブランチにPRをマージするたびにAndroidとiOSのプレビュービルドを作成し、QAが承認された変更をテストできるようにしたいとします。これをGitHubイベントに基づいてトリガーする必要があり、すでにGitHub Actionsにアクセスできるので、最初はシンプルなGitHub Actionを書くのが理にかなっています。
name: Build iOS and Android on merge to main
on:
push:
branches:
- main
jobs:
build-android:
name: Android Release Build
runs-on: ubuntu-latest
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Decode Android keystore
run: |
echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > android/release.keystore
- name: Write signing config gradle.properties
run: |
cat >> android/gradle.properties <<EOF
MYAPP_UPLOAD_STORE_FILE=release.keystore
MYAPP_UPLOAD_KEY_ALIAS=${ANDROID_KEY_ALIAS}
MYAPP_UPLOAD_STORE_PASSWORD=${ANDROID_KEYSTORE_PASSWORD}
MYAPP_UPLOAD_KEY_PASSWORD=${ANDROID_KEY_PASSWORD}
EOF
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- name: Set up Android SDK
uses: android-actions/setup-android@v3
- name: Assemble Release APK/AAB
working-directory: android
run: |
android/gradlew :app:bundleRelease --stacktrace
- name: Upload Android artifact
uses: actions/upload-artifact@v4
with:
name: android-release-aab
path: android/app/build/outputs/bundle/release/*.aab
build-ios:
name: iOS Release Build
runs-on: macos-latest
needs: build-android
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
APP_STORE_CONNECT_API_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }}
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: setup-cocoapods
uses: maxim-lobanov/setup-cocoapods@v1
with:
podfile-path: myApp/Podfile.lock
- name: Fetch signing assets with fastlane match
working-directory: ios
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
run: |
bundle exec fastlane match appstore --readonly
- name: Build iOS archive / ipa
working-directory: ios
env:
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
run: |
bundle exec fastlane ios build
- name: Upload iOS artifact
uses: actions/upload-artifact@v4
with:
name: ios-release-ipa
path: ios/build/*.ipa
私たちがやろうとしていることは、それほど複雑ではありません(そうですよね?)。AndroidビルドとiOSビルドを1つずつ作成しようとしているだけです。しかし、事態は急速にエスカレートします!
おそらく驚くことではありませんが、複雑さの多くは署名認証情報を中心としています。Androidキーストア用にいくつかの環境変数を設定する必要があります。Fastlane Matchを介してApple署名ステップを簡素化したとしても、それは完全に別のFastlane Matchリポジトリを設定する必要があることも意味します。
少しのEAS Buildを追加
長い間、開発チームはGitHub ActionsからEAS Buildを呼び出してきました(Expoチーム自身もそうしています)。GradleとFastlaneのステップをEAS Buildの呼び出しに置き換えてみましょう:
name: EAS Build on merge to main
on:
push:
branches: [main]
jobs:
trigger-eas-builds:
runs-on: ubuntu-latest
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Setup Node & Bun
uses: actions/setup-node@v4
with:
node-version: "20"
- run: |
bun install
- name: Kick off Android and iOS builds
run: |
npx eas build \
--platform all \
--profile preview \
--non-interactive \
--no-wait
これははるかに短くなりました。EAS Buildは、典型的なExpoプロジェクトに必要な適切なprebuild、Gradle、Fastlaneなどのコマンドがすべて設定済みです。
しかし、複雑さの大幅な削減は、ビルド認証情報管理の側面にあります。複数の環境変数とFastlane Match設定を、EAS Buildを呼び出すための単一のEXPO_TOKENに置き換えました。ビルド認証情報はすでにEASに保存されており、eas credentials経由でアップロードするか、初回に手動でビルドを呼び出すと自動生成されます。
上記のシンプルなワークフローは、EAS Buildを起動してから、ビルドの完了を待たずに完了します。これは、使用するCI分数の観点で最も効率的なルートです。ただし、ビルドの完了を待ってから、GitHub Action内で結果を処理するカスタムスクリプトを実行することもできます:
npx eas build --platform android --profile preview --json > build.json
BUILD_URL=$(jq -r '.[0].artifacts.buildUrl' build.json)
echo "Android build URL: $BUILD_URL"
これはEAS Buildも使用しながらGitHub Action CI分数を使用しますが、設定と保守が少なく、EAS Buildが高速なM4 ProワーカーなどでReact Nativeビルド速度に最適化されているという事実を考慮すると、これはしばしば価値のあるトレードオフになります。
EAS Workflowsへの完全移行
この時点で、私たちのGitHub Actionが提供しているのは、mainブランチへのプッシュでビルドをトリガーする機能だけです。リポジトリがExpo GitHubアプリに接続されると、同じトリガーをEAS Workflowに追加でき、GitHub Actionsをリレーとして使用する必要がなくなります:
name: Build preview on main push
on:
push:
branches: [main]
jobs:
build_ios:
type: build
params:
platform: ios
profile: preview
build_android:
type: build
params:
platform: android
profile: preview
これで、ボットユーザーを作成してEXPO_TOKENを提供したり、別のサービスで追加のCI分数を使用したりする必要がありません。また、iOS ビルド自体はEAS上で実行されるため、GitHub ActionsでMacランナーを使用する必要もありません。
強力なExpo最適化ワークフロー
ビルドを作成するためのEAS Workflow YAMLは短くて簡潔ですが、GitHub Actionsからビルドを呼び出すことも劇的に複雑ではありません。EAS Workflowsが真に輝くのは、Expoアプリの独特な特性に基づいて条件付きでジョブを実行する必要がある場合です。
基本的なbuildとupdateジョブに加えて、EAS Workflowsには、ネイティブ設定を一意に識別するハッシュの生成(fingerprint)、既存のビルドへの新しいJavaScriptの適用(repack)、シミュレーターでのMaestroエンドツーエンドテストの実行(maestro)などを行う組み込みジョブがあります。わずか数行のYAMLで、テストとデプロイメントを高速化する強力なワークフローを構築できます。
ビルドまたはアップデート
私たちのアプリが他の多くのReact Nativeアプリと同様で、平均的なプルリクエストがネイティブコードよりもJavaScriptのみに変更を加える可能性がはるかに高いとします。現在のワークフローは、実際にネイティブコードを変更するコミットがごくわずかである可能性が高いにもかかわらず、mainへのすべてのコミットで完全なビルドを実行するように設定されています。
ネイティブフィンガープリントを使用してネイティブコードが変更されたタイミングを判断し、それが真の場合にのみビルドするように修正しましょう:
name: Build or update preview version
on:
push:
branches: [main]
jobs:
fingerprint:
environment: preview
type: fingerprint
android_get_build:
needs: [fingerprint]
type: get-build
params:
fingerprint_hash: ${{ needs.fingerprint.outputs.android_fingerprint_hash }}
platform: android
profile: preview
android_update:
needs: [android_get_build]
if: ${{ needs.android_get_build.outputs.build_id }}
type: update
params:
channel: preview
platform: android
android_build:
needs: [android_get_build]
if: ${{ !needs.android_get_build.outputs.build_id }}
type: build
params:
platform: android
profile: preview
完全なFingerprintワークフローはこちらをご覧ください。
EASにはすでに最近の以前のビルドとフィンガープリント(すべてのビルドで計算される)があるため、このデータはEAS Workflow内ですでに利用可能です。お好みの方法で点と点を結ぶだけです。
フィンガープリントとrepackによる高速E2Eテスト
同じ原理(ネイティブフィンガープリントに基づく条件付きビルド)を使用して、エンドツーエンドテストを高速化できます。以前は、テスト実行に加えて完全なビルドが必要なため、エンドツーエンドテストをいつ、どこで実行するかを制限することを選択していたかもしれません。理想的には、ユニットテスト、リンティング、その他のチェックと同様に、プルリクエストを作成または更新するたびに実行したいでしょう。
フィンガープリントを使用すると、一致するビルドがすでに存在する場合にビルドの代わりにRepackを実行することで、Maestroエンドツーエンドテストのビルドフェーズを高速化できます。repackは「ビルド時アップデート」のようなものと考えることができます。repackジョブは既存のビルドを取得し、JavaScriptを再バンドルし、古いビルドのJavaScriptバンドルを新しいものに置き換え、再署名します。これにより、わずか1分でテスト実行用の新しいビルドを取得できます。