Unityアプリ開発(iOS)へのfastlaneの導入とUnity Cloud Buildでの実行まで

## 本記事の目的

* 既存のUnityアプリ開発に fastlane( https://fastlane.tools )を導入する際の最初の手順をリストアップ
* 手元のMac環境と、CI環境としてUnity Cloud Build（ https://unity.com/ja/products/cloud-build ）で実行する流れに合う形でコマンド内容をピックアップする
* 最初の手順としてiOS向けストアアップロードと証明書管理を管理に追加するところまでをカバー

## 前提環境

* macOS
** 12.6 で動作確認
* Ruby
** macOSに標準でインストールされているものでOK
** 必要バージョンは2.5以上（ 記事執筆時点の[公式](https://docs.fastlane.tools)情報から ）
* Apple Developer、App Store Connect、Unity Cloud Buildの開発者アカウント
** 既に利用していて、それらの運用に fastlane を導入する場合を想定
* Unity
** 2020.3.34f1 で動作確認
** 各種ビルド設定やビルドスクリプトはUnityプロジェクト内で用意されている前提
* Slack
** fastlaneコマンドの動作確認例としてSlack通知を紹介

## インストール
ドキュメント：https://docs.fastlane.tools/#installing-fastlane
いくつか選択肢があるが、ここでは Homebrew (macOS) でインストールする。
~~~console:コマンド
brew install fastlane
~~~

## コマンドが通る事とバージョンを確認

~~~console:実行例
fastlane --version
fastlane installation at path:
/usr/local/Cellar/fastlane/2.210.0/libexec/gems/fastlane-2.210.0/bin/fastlane
-----------------------------
[✔] 🚀 
fastlane 2.210.0
~~~

## プロジェクトの初期設定
fastlane を使うプロジェクトのフォルダに移動して
~~~console:コマンド
fastlane init
~~~
を、実行する。
実行したフォルダ配下に fastlane フォルダが作成され、その中に Appfile, Fastfile が生成される。
iOS, Androidプロジェクトがある場合はそこから設定を拾ってくるらしいが、無い場合はマニュアル設定用の設定ファイルが生成される。

## fastlane設定ファイルの確認
設定ファイルの内容を確認しておく。
~~~conf:Appfile
# app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app
# apple_id("[[APPLE_ID]]") # Your Apple Developer Portal username


# For more information about the Appfile, see:
#     https://docs.fastlane.tools/advanced/#appfile
~~~
ドキュメント： https://docs.fastlane.tools/advanced/Appfile/#appfile

~~~conf:Fastfile
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
#     https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
#     https://docs.fastlane.tools/plugins/available-plugins
#

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

default_platform(:ios)

platform :ios do
  desc "Description of what the lane does"
  lane :custom_lane do
    # add actions here: https://docs.fastlane.tools/actions
  end
end
~~~

## Slack通知
FastfileにSlack通知を追加して、簡単な設定変更と動作確認をする。
以下のように、 custom_lane内にslack通知用の設定を追記する。
~~~conf:Fastfile
lane :custom_lane do
  slack(
    message: "Hello from fastlane.",
    slack_url: "Webhook URLを作成してここに貼る"
  )
end
~~~
Webhook URL作成の一連の流れは[【fastlane】テストを実行して結果をSlackに投稿する](https://www.hfoasi8fje3.work/entry/fastlane/running-tests)を参照。
その他のオプションはドキュメント：https://docs.fastlane.tools/actions/slack/

以下のコマンドで実行してみる。
~~~console:コマンド
fastlane custom_lane
~~~
問題無く通知が飛ばせればOK。

## App Store Connectの認証設定
ドキュメントでは4種類説明されている。
https://docs.fastlane.tools/getting-started/ios/authentication/
ここでは App Store Connect API key (recommended) を使う。
ドキュメント： https://docs.fastlane.tools/app-store-connect-api/#creating-an-app-store-connect-api-key
1. App Store Connect の[ユーザーページ](https://appstoreconnect.apple.com/access/users)で新規API Keyを作る
** 「キー」タブを選択
** ＋から「APIキーを生成」し、必要な権限を付ける
*** 権限については[ドキュメント](https://developer.apple.com/support/roles/)を確認
*** この記事の範囲では「Developer」を選択
2. API Keyファイル(.p8)をダウンロード

設定ファイルには、以下の3つの値とオプションを使用する。
* issuer_id: API Key作成後のページで確認できる「Issuer ID」
* key_id: API Key作成後のページで確認できる「キーID」
* key_content: ダウンロードした.p8ファイルに対して以下のコマンドで取得
* is_key_content_base64: true #base64エンコード有効

キーコンテンツの指定はいくつか方法があるが、ファイル指定はUnity Cloud Buildで扱いにくく、エンコードなし文字列は改行コードの問題が起こり得るため、base64エンコード文字列を使う。
~~~console:コマンド
cat AuthKey_ABCDEFGH.p8 | base64
~~~
参考記事： [Using App Store Connect API with Fastlane Match](https://sarunw.com/posts/using-app-store-connect-api-with-fastlane-match/#newline-problem)


余談：
App用パスワードを作成して環境変数 FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD に入れて使う方法は、Unity Cloud Build上ではうまくいかなかった。（FASTLANE_PASSWORDにApp Store Connectのパスワードを入れることを求められた）

## App Store Connectへのアップロード（ローカル）

手元環境からの App Store Connect アップロード用に以下のようなlane設定を追加する。
~~~conf:Fastfile
lane :ios_release_local do
  api_key = app_store_connect_api_key(
    issuer_id: "API Key作成後のページで確認できる「Issuer ID」",
    key_id: "API Key作成後のページで確認できる「キーID」",
    key_content: "先のコマンドで出力した文字列"
    is_key_content_base64: true,
  )
  upload_to_app_store(
    username: "App Storeのアカウント名",
    api_key: api_key,
    ipa: "ビルド、エクスポートしたIPAファイルのパス",
    skip_screenshots: true,
    skip_metadata: true,
    force: true,
    precheck_include_in_app_purchases: false,
  )
  end
end
~~~
※ precheck_include_in_app_purchases オプションは以下のエラーの回避のため付与

:::note alert
Precheck cannot check In-app purchases with the App Store Connect API Key (yet). Exclude In-app purchases from precheck, disable the precheck step in your build step, or use Apple ID login
:::
その他の設定パラメータ： https://docs.fastlane.tools/actions/upload_to_app_store/#parameters


Unityビルド〜IPAファイル出力を（手動で）実行し、以下のコマンドで App Store Connect アップロードを実行。
~~~console:コマンド
fastlane ios_release_local
~~~

（XCodeビルドやリサインなどもfastlaneで自動化することができるが、本記事ではカバーしない）

## App Store Connectへのアップロード（Unity Cloud Build）
以下の手順で導入する。

* 前提：Unity Cloud Build 環境の初期設定は済んでいるものとする
* Unity Cloud Build用lane設定をFastfileに追加
* 専用の設定ファイルを作成
* ファイル一式をリポジトリに追加
* Unity Cloud Build側のiOSビルド設定にfastlane設定を追加
* 実行

### Unity Cloud Build用lane設定をFastfileに追加

以下のようにlane設定を追加する。
~~~conf:Fastfile
lane :ios_release_ucb do
  api_key = app_store_connect_api_key(
    issuer_id: ENV['ASC_ISSUER_ID'],
    key_id: ENV['ASC_KEY_ID'],
    key_content: ENV['ASC_KEY_CONTENT_BASE64'],
    is_key_content_base64: true,
  )
  upload_to_app_store(
    username: ENV['APPLE_ID'],
    api_key: api_key,
    skip_screenshots: true,
    skip_metadata: true,
    force: true,
    precheck_include_in_app_purchases: false,
  )
  end
end
~~~
* ipaのオプションは付けない（Unity Cloud Build側で環境変数に入っている） 
* 各設定値は Unity Cloud Build側の設定で環境変数設定して渡す

### 専用の設定ファイルを作成
Unity Cloud Build 自体が XCodeビルドに fastlane を使用しており、ユーザーが手順を追加する独自の設定が用意されている。
公式フォーラム記事： https://forum.unity.com/threads/how-to-set-up-a-fastlane-gymfile-and-multiple-provisioning-profiles.545121/
（上記記事では複数のプロビジョニングプロファイルを扱うための設定が説明されているが、ストアへのアップロードもこの手順で成功した）

以下のように json設定ファイルを用意する。
~~~conf:fastlane/ucb_ios_fastlane.json
{
    "fastfile": "fastlane/Fastfile",
    "gymfile": "fastlane/Gymfile",
    "lanes": {
        "pre_build": "",
        "post_build": "ios_release_ucb"
    }
}
~~~

* 先の項で Fastfile に追加した ios_release_ucb のビルド後実行を指定
* fastfile はリポジトリルートからのパス

Gymfileは以下の通り新規作成する。
~~~conf:fastlane/Gymfile
export_options(
)
~~~

### ファイル一式をリポジトリに追加

ここまでで作成した、
fastlane/Appfile
fastlane/Fastfile
fastlane/Gymfile
fastlane/ucb_ios_fastlane.json
をバージョン管理に追加し、リポジトリにコミット（プッシュ）する。

### Unity Cloud Build側のiOSビルド設定にfastlane設定を追加

Unity Cloud Build の「Configurations」から対象のiOSビルドターゲット設定を編集する。
「Advancede Settings」タブで、以下を設定する。
<dl>
<dt>OS dependency settings / Ruby version</dt>
<dd>（fastlane バージョンも依存するので意識しておく）</dd>
<dt>Platform specific settings (iOS) / Custom Fastlane configuration path</dt>
<dd>fastlane/ucb_ios_fastlane.json</dd>
<dt>Environment variables</dt>
<dd>APPLE_ID, ASC_ISSUER_ID, ASC_KEY_ID, ASC_KEY_CONTENT_BASE64 をそれぞれ設定する</dd>
</dl>

### 実行

実行して動作を確認する。

うまくいかない場合はUnity Cloud Build、App Store Connect、fastlane それぞれの変更などを確認していく必要があるため、ローカルで実行して問題を切り分けていくのが良い。

## 既存の証明書のインポート

次のステップとして [fastlane match を使用した証明書の作成と更新](https://qiita.com/sooch/items/a55fb84adbdef4b4542b) が考えられるが、本記事ではその前の準備として既存の証明書を fastlane match で扱えるようにインポートするところだけ扱う。

まず証明書ファイルを管理する専用のGitリポジトリを用意する。（Unityプロジェクトのリポジトリとは別、複数の証明書をまとめて1リポジトリで管理できる）
ドキュメント： https://docs.fastlane.tools/actions/match/#git-storage

管理対象にする .cer, .p12 (, .mobileprovision) ファイルを確認し、インポートコマンドを実行する。
~~~console:コマンド
fastlane match import
~~~
対話形式で必要情報が聞かれるので答える
Certificate (.cer) path:
Private key (.p12) path:
Provisioning profile (.mobileprovision or .provisionprofile) path or leave empty to skip this file:
URL to the git repo containing all the certificates:
Passphrase for Match storage:
App Store Connectアカウント情報


対象のリポジトリにインポートした証明書ファイルが自動的に階層構造化、ファイル名変更、[暗号化](https://docs.fastlane.tools/actions/match/#is-this-secure)されて配置される。


## 関連記事

* [github actionsでunityビルドしてiOSとAndroidのストアに自動的に提出する](https://qiita.com/mogmet/items/1fafdf4e88bdd122b9a0)
* [fastlane docs / Continuous Integration](https://docs.fastlane.tools/best-practices/continuous-integration/)