🤖

EASをGitHub Actionsで使ってReact Native(Expo)アプリをCIする

2021/12/26に公開

Expo Application Servicesが正式版になり、ビルドとストアへの登録(Submit)が無料プランでもできるようになりました。
今回はManaged WorkflowのExpoアプリをGitHub Actionsを使ってBuild & Submitする仕組みを作ってみました。

EASとはなんぞや、とかEAS Buildについてはこちらの記事を参考にさせてもらいました。

https://zenn.dev/shwld/articles/9abcd1ce2dd695

前提

対象はExpo Managed WorkflowのReact Nativeアプリです。
Bare Workflowでも試そうと思ったのですが、SDK 44へのアップデートで躓いてしまったので見送りました。

EAS Buildをローカルで実行する

GitHub Acrionsに組み込む前に、まずローカルで実行できるように設定が必要です。
1度ローカルでのBuild & Submitができるようになると、以降はCIでGitHub Acrionsなどから実行できるようになります。

まずはeas-cliをインストールしましょう。

npm install -g eas-cli

インストールが終わったら、とりあえずビルドしてみます。

eas build

するとビルドするPlatformを確認されます。

? Select platform › - Use arrow-keys. Return to submit.
❯   All
    Android
    iOS

上下キーで対称を選んでEnterを押します。

その後、署名の設定などが必要になりますが、基本的にはデフォルトの設定値でEnterをバンバン打っていけばいけちゃいます。
一通り設定が終わると、ビルド待ちの状態になります。
正常に完了するのを待ちましょう。
うまくいかなかった場合は、ログなどを確認して修正します。

EAS Submitをローカルで実行する

ビルドが完了したら、続いてEAS Submit(Storeへの送信)を実施します。
こちらとりあえずコマンドを叩く・・・前に設定が必要なものがあります。
Google Play Storeにアクセスするためのサービスアカウントの作成と、手動によるStoreへのアップロードです。
(サービスアカウントの設定は、Expoアカウント毎に1回だけでOK。手動でのStoreへのアップロードはアプリ毎に1回実施が必要)

詳しくはこちらをみながらやってみてください。
https://docs.expo.dev/submit/android/

サービスアカウントの設定ができたら、コマンドを実行します。

eas submit

またプラットフォームの選択が出るので選択します。

? Select platform › - Use arrow-keys. Return to submit.
❯   All
    Android
    iOS

あとはデフォルト設定でOK...のはずです。
うまくいかないところがあればコメントなどで教えてください。
各ストアでアプリがアップロードされていることを確認します。
(iOSならTest Flightなど、Androidならアーティファクトなど)

eas.jsonの修正

ローカルでのBuild & Submitが完了したら、GitHub Actionsで実行するための設定を追加します。
eas buildをした際に、eas.jsonが追加されていると思います。
そのファイルに、以下の部分を追加します。

{
  "cli": {
    "version": ">= 0.42.4"
  },
  "build": {
    "development": {
      "distribution": "internal",
      "android": {
        "gradleCommand": ":app:assembleDebug"
      },
      "ios": {
        "buildConfiguration": "Debug"
      }
    },
    "preview": {
      "distribution": "internal"
    },
    "production": {}
  },
  "submit": {
    "production": {
      // 追加ここから
      "ios": {
        "ascAppId": "9999999999"
      }
      // 追加ここまで
    }
  }
}

ascAppIdにはAppStoreConnect -> 対称アプリの「一般」 -> 「App情報」 -> 「Apple ID」をコピーして設定します。
ひとまず設定するのはこれだけでOKです。

GitHub Actionsの設定ファイル追加

この辺を参考にGitHub Actions用の設定ファイルを作成して追加します。

https://docs.expo.dev/build/building-on-ci/

今回は以下のようなファイルを追加しました。

.github/workflows/eas-build.yml
name: EAS Build
on:
  push:
    branches:
      - master
  workflow_dispatch:

jobs:
  build:
    name: Install and build
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Setup Node.js
        uses: actions/setup-node@v1
        with:
          node-version: 16.x

      - name: Setup Expo
        uses: expo/expo-github-action@v5
        with:
          expo-version: 4.x
          expo-token: ${{ secrets.EXPO_TOKEN }}
          expo-cache: true

      - name: Install dependencies
        run: yarn install --frozen-lockfile

      - name: Build on EAS
        run: npx eas-cli build --platform all --non-interactive --auto-submit --no-wait

masterブランチへのpushをトリガーにしたActionsです。
公式のサンプルから変えている箇所としては、yarnを使用しているところと、最後のStepで--auto-submitを追加しているところです。
--auto-submitを追加することで、BuildだけでなくそのままStoreへの登録まで実施しています。
(ちなみに公式ではyarnではなくnpmを推奨しているようです。)

SecretsにEXPO_TOKENを追加

eas-build.ymlファイルの中で、secrets.EXPO_TOKENを使っているためこれを設定します。
https://expo.dev/settings/access-tokensにアクセスし、Access tokenをCreateします。
作成したTokenを、GitHubのSecretsに登録します。

GitHub SecretsへのEXPO_TOKENの追加

あとはmasterへPushしてやれば、自動的にActionが実行されて、Build & Submitが実施されます。

少し高度なビルド

シンプルなビルド構成のアプリであれば、以上のようにとても簡単にCIの構築が可能です。
しかし、ちょっと高度なビルドが必要な場合はもう一手間加える必要があります。

例えば以下のような場合です。

  • 環境毎にビルド設定を変えてビルドさせ、内部テスト向けに使用したい。
  • 一つのアプリリポジトリから2つのアプリを作成したい(有償版・無償版など)。

こういった場合、そもそもapp.jsonではやりきれないので、app.config.jsを使用している場合もあると思います。
自分が開発している「Fast Template」というアプリも、有償版と無償版があり、一部設定を切り替えてビルドしたいため、以下のようなapp.config.jsファイルを利用しています。

app.config.js(一部省略)
const commonConfig = {
    name: "Fast Template",
    platforms: [
        "ios",
        "android"
    ],
    version: "2.4.0",
    orientation: "portrait",
    splash: {
        image: "./assets/images/splash.png",
        resizeMode: "contain",
        backgroundColor: "#4472c4"
    },
    assetBundlePatterns: [
        "**/*"
    ],
    locales: {
        ja: "./locales/ja.json",
        en: "./locales/en.json"
    },
    ios: {
        buildNumber: "1",
        supportsTablet: true,
        userInterfaceStyle: "automatic",
        appStoreUrl: "https://apps.apple.com/jp/app/%E3%82%BB%E3%83%AF%E3%82%B7%E3%82%BF/id1482524590"
    },
    android: {
        versionCode: 2040001,
        playStoreUrl: "https://play.google.com/store/apps/details?id=com.apps.hal.fasttemplate"
    }
};

module.exports = () => {
    if (process.env.APP_ENV === "standard") {
        return {
            ...commonConfig,
            slug: "fast-template",
            icon: "./assets/images/icon.png",
            ios: {
                ...commonConfig.ios,
                bundleIdentifier: "com.apps.hal.fasttemplate"
            },
            android: {
                ...commonConfig.android,
                package: "com.apps.hal.fasttemplate"
            },
            extra: {
                eas: {
                    projectId: "d71783ad-7177-4fd1-af65-bf1ba0ace071"
                }
            }
        };
    } else if (process.env.APP_ENV === "free") {
        return {
            ...commonConfig,
            slug: "fast-template-free",
            icon: "./assets/images/icon_free.png",
            ios: {
                ...commonConfig.ios,
                bundleIdentifier: "com.apps.hal.fasttemplatefree"
            },
            android: {
                ...commonConfig.android,
                package: "com.apps.hal.fasttemplatefree"
            },
            extra: {
                eas: {
                    projectId: "4650513e-d9ea-4511-98d5-f034e9bc6b6a"
                }
            }
        };
    }
};

process.env.APP_ENVに応じてアプリ設定が切り替わり、アイコンやidが変わるような仕組みです。
これに対応するために、eas.jsonを変更します。

eas.json
{
  "cli": {
    "version": ">= 0.42.4"
  },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal"
    },
    "production_free": {
      "env": {
        "APP_ENV": "free"
      }
    },
    "production_standard": {
      "env": {
        "APP_ENV": "standard"
      }
    }
  },
  "submit": {
    "production_free": {
      "ios": {
        "ascAppId": "1496508495"
      }
    },
    "production_standard": {
      "ios": {
        "ascAppId": "1496475554"
      }
    }
  }
}

まぁ、修正したのはproductionproduction_freeproduction_standardに分けて、それぞれのenvを設定しただけですね。
これでapp.config.jsの実行時にAPP_ENVを使用してビルドしてくれます。
また、submitの方もproductionを分けて、それぞれのアプリにあったascAppIdを設定しました。

さらにGitHub Actionsの設定ファイルも変更します。

.github/workflows/eas-build.yml
name: EAS Build
on:
  push:
    branches:
      - master
  workflow_dispatch:

jobs:
  build:
    name: Install and build
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Setup Node.js
        uses: actions/setup-node@v1
        with:
          node-version: 16.x

      - name: Setup Expo
        uses: expo/expo-github-action@v5
        with:
          expo-version: 4.x
          expo-token: ${{ secrets.EXPO_TOKEN }}
          expo-cache: true

      - name: Install dependencies
        run: yarn install --frozen-lockfile

      - name: Build on EAS(free)
        run: npx eas-cli build --platform all --non-interactive --profile=production_free --auto-submit-with-profile=production_free

      - name: Build on EAS(standard)
        run: npx eas-cli build --platform all --non-interactive --profile=production_standard --auto-submit-with-profile=production_standard

変更したのは最後のeas-cli buildのパラメータですね。
--profile--auto-submit-with-profileを使用して、それぞれの対応するprofileを使用してもらうようにしています。
これで、1つのリポジトリへの変更から2バージョンのアプリをビルドしてストアに登録することができます。

EAS Build & SubmitをGitHub Actionsで利用する利点

今回EAS Build & SubmitをGitHub Actionsで利用してみて、こんなところが良かったなぁと思いました。

1、煩雑なビルド・Store登録という作業がなくなる

一つは当たり前ですが自動化したため楽になった、という点です。
特にReact Nativeアプリの場合、iOSとAndroidの両方向けにアプリを作成することが多いと思いますが、そうすると当然ビルドやStoreへの登録作業も2倍発生します。
ここがGitHub Actionsによって指定のブランチへのPush(マージ)作業のみで済んでしまうというのはとても便利です。

自分の場合、iOS向けの登録作業をして満足してしまい、Android向けのアプリを更新し忘れた、みたいなことがしょっちゅう発生するので、それを防止できるのも助かります。
また、高度な(というほどでもないですが)ビルドができることで、2バージョン作る作業もなくなるのでとても便利です。

2、自動化にかかるコストを下げられる

自動化にかかるコストとして、細かくいうと「自動化するためのコスト」と「自動化を継続するコスト」の二つがあると思います。

まず、「自動化するためのコスト」については、EASというExpo(React Native)に特化したビルドサービスなので、非常に簡単、というところがあります。
今回の自動化の対応も、Managed Workflowについてはほとんど時間がかからず実現できてしまいました。
(Bare WorkflowについてはEASではない別の問題で実施できてません)
手軽に導入できるので自動化するためのコストを下げることができました。

また、「自動化を継続するコスト」としては、EASの利用自体は現状無料で利用することが可能です。
並列ビルドができないとか、ビルドタイムアウトの制約などはありますが、十分実用レベルの設定値になっています。

GitHub Actions側も、無料枠が2000分ついています。
しかも、これまでGitHub ActionsでReact Nativeアプリをビルドしようと思った時、Macインスタンスは10倍コストがかかるため200分しか利用できなかったわけですが、GitHub Actions + EASの場合、GitHub Actions側の実行はLinuxインスタンスで実行が可能なので、2000分丸々利用することができます。

これにより、十分に無料枠内でも利用できる自動化が可能となりました。
これは自分のような個人開発者にとってもとても嬉しいことではないかと思います。
(もちろん、将来的にEASの発展に貢献するため課金したいところではありますが・・・)

今後やりたいこと

とりあえず今回は、ビルド〜Storeへの登録までを自動化しました。
本当はさらにRelease ChannelやExpo-Updateも活用していきたいと思っています。
また、Bare Workflowでの確認ができていないので、そちらもやりたいですね。

最後までご覧いただきありがとうございました。
内容の不明点・誤りなどあれば、コメントやTwitterなどで指摘いただけると助かります。

Discussion