React NativeのiOS/Androidビルドするbitrise.ymlを晒してみる&ビルド高速化など

この記事は[React Native Advent Calendar 2019](https://qiita.com/advent-calendar/2019/react-native)の11日目の記事です。

# はじめに

こんにちは。
React Native開発3年目のワダ([@takahi5](https://twitter.com/takahi5))です。

現在React Native(Expoなし)でiOS/Androidのアプリを開発しています。
bitriseでビルドを自動化しているのですが、おかげで運用が非常に楽になりました♪

一方でネイティブ知識の不足からか、はじめのセットアップには手間取りました。  
先人の残してくれた資料に助けられたのですが、bitriseの進化もあり資料の通りには行かない部分もありました。
そこで、2019年現在、利用しているbitrise.ymlを晒したいと思います！

# bitriseとは

モバイル開発に特化したCIです。
Circle CIやgithub actionsと同じ類のものですが、モバイル特化という部分が特徴になります。
React Native用のワークフローも雛形を自動で作成してくれます。

無料でも使えますが、無料プランだとビルド時間10分でタイムアウトするため、React Native(特にiOS)をビルドする場合は有料プランに入る必要があると思います。
僕のチームでは$36/月のプランを使っています。


# iOSビルドのbitrise.yml

まずiOSです。

このワークフローをmasterブランチへのpushをトリガーにして実行しています。

チーム内へのアプリ配布はTestFlightを利用していますが、
このワークフローではitune connectにipaファイルをアップロードするところまでしか対応していないので、最後にTestFlight配信するところは手動でやっています。
この作業も自動化できるといいですね。

審査への提出も最後のポチッは手動でやっています。

```yml

  deploy-ios:
    description: "(略)"
    steps:
    - activate-ssh-key@4.0.3:
        run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}'
    - git-clone@4.0.17: {}
    - script@1.1.5:
        inputs:
        - content: |-
            #!/usr/bin/env bash

            echo $ENV_FILE | base64 --decode > .env
    - cache-pull@2.1.1: {}
    - yarn@0.0.8:
        inputs:
        - command: install
    - install-react-native@0.9.2:
        inputs:
        - version: 2.0.1
    - cocoapods-install@1.9.0:
        inputs:
        - podfile_path: "./ios/Podfile"
        is_always_run: true
    - set-xcode-build-number@1.0.8:
        inputs:
        - build_short_version_string: "$APP_VERSION"
        - plist_path: "./ios/your-app-name/Info.plist"
    - certificate-and-profile-installer@1.10.1: {}
    - xcode-archive@2.6.0:
        inputs:
        - project_path: "$BITRISE_PROJECT_PATH"
        - scheme: "$BITRISE_SCHEME"
        - export_method: app-store
        - team_id: "$IOS_TEAM_ID"
        - force_team_id: "$IOS_TEAM_ID"
        - force_code_sign_identity: iPhone Distribution
        - force_provisioning_profile_specifier: ''
        - force_provisioning_profile: "$PROVISIONING_PROFILE"
        - xcodebuild_options: CODE_SIGN_STYLE="Manual" -UseModernBuildSystem=YES
        - configuration: Release
    - cache-push@2.2.1:
        inputs:
        - cache_paths: |-
            $BITRISE_CACHE_DIR
            node_modules -> yarn.lock
    - deploy-to-bitrise-io@1.9.2: {}
    - deploy-to-itunesconnect-application-loader@0.10.1:
        inputs:
        - password: "$APPLE_ID_PASSWORD"
        - app_password: "$APPLE_APP_SPECIFIED_PASSWORD"
        - itunescon_user: "$APPLE_ID"
    - ipa-info@1.1.0: {}
    - slack@3.1.3:
        inputs:
        - buttons: View Build|${BITRISE_BUILD_URL}
        - webhook_url: "$SLACH_CHANNEL_ENGINEERING"
        - channel: "#engineering"
        - from_username: Bitrise(iOS)
        - fields: |-
            App|${IOS_APP_NAME}
            Version|${IOS_APP_VERSION_NAME}
            Branch|${BITRISE_GIT_BRANCH}
            Build#|${BITRISE_BUILD_NUMBER}
            Message|${BITRISE_GIT_MESSAGE}
    meta:
      bitrise.io:
        stack: osx-xcode-11.2.x

```
## 補足
細かい設定について補足させていただきます。

### バージョン名やビルド番号をbitrise側で設定する

バージョン名(1.2.0など)とビルド番号(毎回インクリメントする通し番号)をbitrise側で指定しています。

バージョン名は環境変数`$APP_VERSION`に指定しているので、バージョンを上げたいときは手動で環境変数を変更しています。

ビルド番号はbitriseビルドの通し番号が自動的に使われるので、毎回変更する作業が不要になりとても便利です。

```yml
    - set-xcode-build-number@1.0.8:
        inputs:
        - build_short_version_string: "$APP_VERSION"
        - plist_path: "./ios/your-app-name/Info.plist"
```

### Slack通知

Slackのwebhook を指定すればOKです。
ここは標準的な設定です。

```yml
 - slack@3.1.3:
        inputs:
        - buttons: View Build|${BITRISE_BUILD_URL}
        - webhook_url: "$SLACH_CHANNEL_ENGINEERING"
        - channel: "#engineering"
        - from_username: Bitrise(iOS)
        - fields: |-
            App|${IOS_APP_NAME}
            Version|${IOS_APP_VERSION_NAME}
            Branch|${BITRISE_GIT_BRANCH}
            Build#|${BITRISE_BUILD_NUMBER}
            Message|${BITRISE_GIT_MESSAGE}
```

### .envの配置

このアプリではAPI KEYなどを`.env`ファイルで管理しており、このファイルはgithubリポジトリに上げていないため、bitriseビルド時になんらかの方法で配置してあげる必要がありました。

ここでは`.env`ファイルをbase64エンコードしたものをbitriseの環境変数`$ENV_FILE`に格納しておいて、ビルド時にデコードするようにしました。

もし`.env`を利用している場合は参考になるかと思います。

```yml

   - script@1.1.5:
        inputs:
        - content: |-
            #!/usr/bin/env bash

            echo $ENV_FILE | base64 --decode > .env

```

## iOSのビルドを速くするためにやったこと

もともとビルド時間が非常に長く40分〜45分くらいかかっており、ベーシックプランだと45分でタイムアウト終了してしまうので、bitriseのご機嫌次第ではビルドができない事がありました。  

そこで設定を見直した結果、ビルド時間は25分程に短縮され安心してビルドできるようになりました。

このプロジェクト特有の問題もありましたが、何らかの参考になればと共有したいと思います。  

### UseModernBuildSystem

これがほぼ原因だったのですが、`UseModernBuildSystem=NO`を`UseModernBuildSystem=YES`に変更することで大幅に改善されました。
特に指定しなければデフォルトでは`YES`になっているので問題ないと思います。

```yml
    - xcode-archive@2.6.0:
        - (中略)
        - xcodebuild_options: CODE_SIGN_STYLE="Manual" -UseModernBuildSystem=YES
```

もともと`UseModernBuildSystem=NO`に設定した経緯としては、`react-native-vector-icon`を導入した際にビルドが失敗するようになり、`NO`に設定することで解消したからでした。
しかし実際には`react-native-vector-icon`導入時の設定にミスがあったのが原因で、[それを直すことで根本解決](https://github.com/oblador/react-native-vector-icons/issues/1074#issuecomment-534053163)し`YES`でもビルドが通るようになりました。

### キャッシュの設定

これは基本的な設定ですが、これにより1分ほど速くなりました。

Podsファイルのキャッシュは特に何も指定しなくても大丈夫ですが、node_nodulesのキャッシュは下記の指定が必要になります。

```yml
    - cache-pull@2.1.1: {}
    - (中略)
    - cache-push@2.2.1:
        inputs:
        - cache_paths: |-
            $BITRISE_CACHE_DIR
            node_modules -> yarn.lock
```


# Androidビルドのbitrise.yml

続いてAndroidのビルドです。

iOSと同様にキャッシュの利用や`.env`の配置をしています。

iOSとワークフローを分けていますが、合体してもよいかなと思います。

```yml
  deploy-android:
    description: "(略)"
    steps:
    - activate-ssh-key@4.0.3:
        run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}'
    - git-clone@4.0.17: {}
    - script@1.1.5:
        inputs:
        - content: |-
            #!/usr/bin/env bash

            echo $ENV_FILE | base64 --decode > .env
    - cache-pull@2.1.2: {}
    - yarn@0.0.8:
        inputs:
        - command: install
    - install-react-native@0.9.2:
        inputs:
        - version: 2.0.1
    - install-missing-android-tools@2.3.7:
        inputs:
        - gradlew_path: "$PROJECT_LOCATION/gradlew"
    - set-android-manifest-versions@1.0.5:
        inputs:
        - version_name: "$APP_VERSION"
        - manifest_file: "./android/app/src/main/AndroidManifest.xml"
    - gradle-runner@1.8.3:
        inputs:
        - gradlew_path: "./android/gradlew"
        - gradle_file: "./android/build.gradle"
        - gradle_task: assembleRelease
    - sign-apk@1.4.1: {}
    - cache-push@2.2.3:
        inputs:
        - cache_paths: |-
            $BITRISE_CACHE_DIR
            node_modules -> yarn.lock
    - deploy-to-bitrise-io@1.7.1: {}
    - slack@3.1.3:
        inputs:
        - buttons: |-
            Install APK|${BITRISE_PUBLIC_INSTALL_PAGE_URL}
            View Build|${BITRISE_BUILD_URL}
        - webhook_url: "$SLACH_CHANNEL_ENGINEERING"
        - channel: "#engineering"
        - footer: Bitrise(Android)
        - from_username: Bitrise(Android)
        - fields: |-
            App|${BITRISE_APP_TITLE}
            Version|${APP_VERSION}
            Branch|${BITRISE_GIT_BRANCH}
            Build#|${BITRISE_BUILD_NUMBER}
            Message|${BITRISE_GIT_MESSAGE}

```

### チームへのAPK配布

ビルド完了するとSlack通知していますが、Slackメッセージの中にAPKのダウンロード導線を載せています。
(下記の`Install APK|${BITRISE_PUBLIC_INSTALL_PAGE_URL}`部分)

チームへ確認用アプリを配布する際は、Slack通知から直接APKをダウンロードしています。

```yml
 - slack@3.1.3:
        inputs:
        - buttons: |-
            Install APK|${BITRISE_PUBLIC_INSTALL_PAGE_URL}
            View Build|${BITRISE_BUILD_URL}
```


# まとめ

以上になります。

今までは永らくExpoでアプリを開発していて、ExpoなしのReact Nativeアプリの開発は今回が初めてでした。
Expoの`expo publish`や`expo build`によるアプリ配布・ビルドは非常に効率がよく、Expoなしの開発に少し不安がありましたが、bitriseのお陰でこの当たりの不便さは全く感じませんでした。

何かの参考になれば幸いです。

明日、12日目は[mrtry](https://qiita.com/mrtry)さんの記事になります。
楽しみにしています！