GitHub+JenkinsでUnity製iOSアプリの自動ビルドと実機への自動インストール

# はじめに

研究でiOSデバイスを3~4台使うアプリケーションを開発することになったのですが、以下の手順を手作業でやるのは流石にしんどいと思ったので、2~5までを自動化しました。
![manual.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/f76e4412-e935-66bc-64f7-134214a1754e.png)
1. 普段使いのWindows PCからGitHubへ変更をpush
2. 手元のMac PCでそれをpull
3. UnityプロジェクトをビルドしてXcodeプロジェクトの作成
4. Xcodeプロジェクトををビルドしてipaの作成
5. ipaを手元の実機3~4台へインストール

![auto.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/074b2cd1-36ce-53c5-d4fc-8352d32b6c28.png)
1'. push時のGitHubからのWebhookをUltraHookでトンネリングしてローカル環境のMac上のJenkinsに通知する
6. ビルドが終了したタイミングでSlackに成否を通知

この記事は自分の備忘録的な意味合いが強いので、全てを詳細に説明することは出来ませんが、代わりに参考記事へのリンクをちょくちょく張っていきます。

## 要件

- 実機（Windows, Mac, iOSデバイス）が全て手元にあり、MacとiOSデバイスを有線接続できる
- 開発時は実機の全てが常に手元にあるわけではない（開発環境がLAN内で完結できない）
    - 出先でWindowsのノートPCで開発するとかの場面がある
    - LAN内で完結するならこの記事が参考になるかもです
        - [GitLab で Unity の iOS アプリを自動ビルドするようにしてみた](https://qiita.com/hkomo746/items/1460a9a6d3ae2450686d)
- 自動化は無料で実現したい（個人開発）
    - 有料で良いなら[deploygate](https://deploygate.com/)とか使うとよさげ？

## 環境

- Windows 10 Home 1909
- Unity 2018.4.20f1
- Mac mini (2018) ... ビルドサーバ
    - OS: macOS Mojave (10.14.6)
    - CPU: Intel Core i7
    - メモリ: 16GB
- Mac software
    - Xcode 10.3
    - Homebrew 2.2.12
        - Jenkins 2.230
    - npm 6.14.4
        - ios-deploy 1.10.0
    - gem 2.5.2.3
        - UltraHook client 0.1.5
- iPod touch 7th (3台)
    - version: 13.4

## 環境構築のための手順

自分の辿った環境構築の手順です。記事も以下の流れで解説します。

1. Unityプロジェクトの手動ビルドとipaファイルの作成
2. Unityプロジェクトからipaファイルを作成し実機へインストールするまでを**コマンドラインから行う**
3. Jenkinsのインストールと初期設定
4. Jenkinsのジョブの作成と設定

# 手順

## 1. Unityプロジェクトの手動ビルドとipaファイルの作成

ipaファイルとはiOSで動作するバイナリファイルです。

### 1-1. Unityプロジェクトの作成とGitHubへのアップロード

まずMacでUnityプロジェクトを作成し、GitHubに上げておきます。
この時、自分の環境ではリモートリポジトリへのパスがHTTPSだと後々Jenkinsからのgit pullが失敗しました。なのでGitHubに公開鍵を登録しておき、リモートリポジトリへのパスは`git@github.com:[ユーザID]/[リポジトリ].git`のようにSSHのものにしておきましょう。

- [GitHubでssh接続する手順~公開鍵・秘密鍵の生成から](https://qiita.com/shizuma/items/2b2f873a0034839e47ce)
- [gitのremote urlを変更する(レポジトリ移行時)](https://qiita.com/minoringo/items/917e325892733e0d606e)

### 1-2. Unityプロジェクトの手動ビルドと実機へのインストールテスト

次にUnityプロジェクトをiOS向けにビルドします。この時、Unity側でXcodeの`Signing`情報を設定しておきましょう。
ビルドしてできたXcodeプロジェクトを開いたら、`Identity`や`Signing`を必要に応じて設定して実機向けにビルド＆インストールします。

- [Unityの設定でXcodeのSigning情報を設定する](https://indie-du.com/entry/2017/01/25/232439)
- [UnityでiPhoneアプリ（iOS）向けにビルドする方法【初心者向け】](https://techacademy.jp/magazine/2483)

なお、自分の環境ではXcodeがiOS 13.4をサポートしていなかったので自分でサポートファイルをダウンロードしました。
[【Xcode】実機で動かそうとしたらこんなエラーが出た時](https://qiita.com/solabito331/items/17ee51d954fb3f87b37c)

### 1-3. Xcodeプロジェクトからipaファイルの作成

ipaファイルを作成するためには一旦XcodeプロジェクトからArchiveファイルというものを経る必要があります。
この段階で手動ではビルドできているはずなので、Xcodeで`Product > Archive`を選択しipaファイルを作成します。

- [Xcodeを使ったIPAファイルの作成手順](https://docs.deploygate.com/docs/export-ipa#section-%E3%82%A2%E3%83%BC%E3%82%AB%E3%82%A4%E3%83%96-%E5%86%8D)
- [【Xcode10】Exportボタンがない！？　あっ、Distribute Appにまとめられたんですね](https://qiita.com/toshi586014/items/401434946552123aea3c)

## 2. Unityプロジェクトからipaファイルを作成し実機へインストールするまでをコマンドラインから行う

ipaファイルの作成までは以下の記事が非常に参考になりました。
[UnityのiOSビルドをコマンドラインから行う (Mac用)](https://qiita.com/beckyJPN/items/ae73dd664b17f56be5ef)

スクリプトの大筋は記事と変わりませんが、自分は最終的に以下のようになりました。

### 2-1. Unityプロジェクト⇒Xcodeプロジェクト

#### C#スクリプト
以下のスクリプトを`Assets/Scripts/Editor/`以下に置きます。

```ApplicationBuilder.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEngine;

public class ApplicationBuilder
{
    public static void Build()
    {
        const string outputDirKey = "-output-dir";
        
        var args = Environment.GetCommandLineArgs();
        var locationPathName = GetArgumentValue(args, outputDirKey);
        
        var options = new BuildPlayerOptions();
        options.scenes = EditorBuildSettingsScene.GetActiveSceneList(EditorBuildSettings.scenes);
        options.locationPathName = GetArgumentValue(args, "-output-dir");
        options.target = BuildTarget.iOS;
        options.options = BuildOptions.None;

        var buildReport = BuildPipeline.BuildPlayer(options);

        if (buildReport.summary.result == BuildResult.Succeeded)
        {
            Debug.Log("[Success]");
            EditorApplication.Exit(0);
        }
        else
        {
            Debug.Log("[Failure]" + buildReport);
            EditorApplication.Exit(1);
        }
    }

    private static string GetArgumentValue(IReadOnlyList<string> args, string key)
    {
        var index = args.ToList().FindIndex(arg => arg == key);
        var paramIndex = index + 1;
        
        if (index < 0 || args.Count() <= paramIndex)
        {
            return null;
        }

        return args[paramIndex];
    }
}

```

#### シェルスクリプト

以下のスクリプトをUnityプロジェクトの直下に置きます。
ログがGitの管理下に置かれるとJenkinsで`git pull --rebase`する際に邪魔なので、出力先は`Builds/`以下にしました。
また、スクリプトが失敗した際にJenkinsのジョブがすぐに失敗するよう、ファイルの頭には`#!/bin/bash -xe`をつけておきます。
[Jenkinsで使うシェルスクリプトは-xeつけた方がよかった](https://sue445.hatenablog.com/entry/2014/05/23/143737)

```build_unity2xcode.sh
#!/bin/bash -xe

echo "Start Unity build"

UNITY_APP_PATH="/Applications/Unity/Hub/Editor/2018.4.20f1/Unity.app/Contents/MacOS/Unity"
UNITY_PROJECT_PATH="./"
UNITY_LOG_PATH="./Builds/build.log"
UNITY_BUILDER_NAME="ApplicationBuilder.Build"
XCODE_PROJECT_PATH="./Builds/iOS"

$UNITY_APP_PATH -batchmode \
    -quit \
    -projectPath $UNITY_PROJECT_PATH \
    -logFile $UNITY_LOG_PATH \
    -executeMethod $UNITY_BUILDER_NAME \
    -output-dir $XCODE_PROJECT_PATH

if [ $? -eq 1 ]; then
    echo "error!! check logfile: ${UNITY_LOG_PATH}"
    exit 1
fi

echo "Finish Unity build"

XCODE_PROJECT_PATH=$XCODE_PROJECT_PATH sh build_xcode2ipa.sh
```

`build_xcode2ipa.sh`は後述。

### 2-2. Xcodeプロジェクト⇒ipaファイル

以下のスクリプトをUnityプロジェクトの直下に置きます。
ここで、自分の環境ではXcodeの`Automatically Sign`をUnityプロジェクトの段階で有効にしていたことで、[前述の記事](https://qiita.com/beckyJPN/items/ae73dd664b17f56be5ef)に倣いプロビジョニングファイルのUUIDを指定すると、逆にエラーが出ました。

[Xcode 8: xcodebuildで、Automatic Signingに対応する](https://qiita.com/SCENEE/items/c170bca6b8e8bcb2769f)
自分は上記の記事の**ビルドエラー2**に該当したため、UUIDの指定を取り除きました。

```build_xcode2ipa.sh
#!/bin/bash -xe

echo "Start ipa build"

SCHEME="Unity-iPhone"
PROJECT_PATH="${XCODE_PROJECT_PATH}/${SCHEME}.xcodeproj"
ARCHIVE_FILE="${SCHEME}.xcarchive"
ARCHIVE_DIR="${XCODE_PROJECT_PATH}/archive"
ARCHIVE_PATH="${ARCHIVE_DIR}/${ARCHIVE_FILE}"
IPA_DIR="${ARCHIVE_DIR}/output_ipa"
EXPORT_OPTIONS_PLIST="ExportOptions.plist"

mkdir -p $ARCHIVE_PATH

# ARCHIVE
xcodebuild -project $PROJECT_PATH \
    -scheme $SCHEME \
    archive -archivePath $ARCHIVE_PATH

# ipaファイルの作成
xcodebuild -exportArchive -archivePath $ARCHIVE_PATH \
    -exportPath $IPA_DIR \
    -exportOptionsPlist $EXPORT_OPTIONS_PLIST

echo "Finish ipa build"

IPA_DIR=$IPA_DIR sh install_ipa.sh
```

**ExportOptions.plist**に関しては、1項でXcodeのGUIからビルドしたipaファイルと同じフォルダに存在するため、それをシェルスクリプトと同じくUnityのプロジェクト直下にコピーしてきて配置しましょう。
`install_ipa.sh`は後述。

### 2-3. ipaファイルを実機へインストール

作成したipaファイルを実機へインストールするには、**`ios-deploy`**というコマンドを利用します。
[iOSでコマンドラインで実機にアプリをインストールできる、ios-deployについて](https://qiita.com/lazyppp/items/d517e0c75d139935c772)
上記の記事に従い、`npm`を用いて`ios-deploy`をMacにインストールしてください。

ios-deployでMacに接続されているiOSデバイスを検出すると、自分の環境では以下のように表示されました。
※ **xxxxxx...xxxxxx**部分は本来デバイスIDが表示され、**yyyyyyyyyy**部分は本来デバイス名が表示されます

```
~ $ ios-deploy --detect
[....] Waiting up to 5 seconds for iOS device to be connected
[....] Found xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (N112AP, iPod Touch 7G, iphoneos, arm64) a.k.a. 'yyyyyyyyyy' connected through USB.
[....] Found xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (N112AP, iPod Touch 7G, iphoneos, arm64) a.k.a. 'yyyyyyyyyy' connected through USB.
[....] Found xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (N112AP, iPod Touch 7G, iphoneos, arm64) a.k.a. 'yyyyyyyyyy' connected through USB.
```

ここで、`ios-deploy`は以下のコマンドでデバイスを指定してipaファイルをインストールできます。

```
$ ios-deploy --bundle <path_to_ipa> --id <device_id>
```

Macに接続されているすべてのiOSデバイスにipaファイルをインストールするため、`ios-deploy --detect`で検出したすべてのデバイスIDに関して上記のコマンドを実行するシェルスクリプトを自作します。
以下のスクリプトもUnityプロジェクトの直下に配置します。

```install_ipa.sh
#!/bin/bash -xe

echo "Start install"

# Jenkinsのジョブからios-deployを利用するためローカルコマンドへのパスを通す
export PATH=/usr/local/bin:$PATH
source ~/.bashrc

# $IPA_DIRはbuild_xcode2ipa.shから引き継ぐ
cd $IPA_DIR

IPA_FILE=`ls *.ipa`
ID_LIST=($(ios-deploy --detect | grep "iPod touch" | awk '{ print $3 }'))

for i in ${ID_LIST[@]}
do
    ios-deploy --bundle $IPA_FILE --id $i
done

echo "Finish install"
```

まとめると、`ApplicationBuilder.cs`, `build_unity2xcode.sh`, `build_xcode2ipa.sh`, `ExportOptions.plist`, `install_ipa.sh`等は以下のようなディレクトリ構成になります。

```
UnityProj/
　├ Assets/
　│　└ Editor/
　│　　　└ ApplicationBuilder.cs
　├ Builds/
　│　└ build.log
　├ build_unity2xcode.sh
　├ build_xcode2ipa.sh
　├ ExportOptions.plist
　└ install_ipa.sh
```

この状態で`sh build_unity2xcode.sh`をターミナルから実行すればUnityプロジェクトのビルドから実機へのインストールまで良しなにやってくれるはずです。

## 3. Jenkinsのインストールと初期設定

### 3-1. HomebrewでJenkinsをインストール

`Homebrew`を使ってmacOSにJenkinsを直接インストールします。

```
$ brew update
$ brew upgrade
$ brew install jenkins-lts
```

ここで、Jenkinsはデフォルトだとインストールされたマシンからのアクセスしか受け付けないのですが、自分はWindowsのブラウザからJenkinsを操作できるようにしたかったため、バックアップを取ったうえで設定ファイルを書き換えました。ついでに念のため、他のアプリでも何だかんだよく使われる`Port 8080`も`Port 9000`に変更しておきました。
参考：[macOS上でJenkinsを自動起動させる](https://blog.sato0123.com/2018/08/13/auto-start-jenkins/)

```/usr/local/Cellar/jenkins/[Jenkinsのバージョン]/homebrew.mxcl.jenkins.plist
変更前
...
      <string>--httpListenAddress=127.0.0.1</string>
      <string>--httpPort=8080</string>
...

変更後
...
      <string>--httpListenAddress=0.0.0.0</string>
      <string>--httpPort=9000</string>
...
```

その後、以下のコマンドでJenkinsをmacOSの起動と同時に起動するよう設定しました。

```
$ brew services start jenkins
```

上記コマンドは同時にJenkinsの起動も行うので、出力が落ち着いたタイミングで`http://localhost:9000/`へアクセスし、画面に従いセットアップ、プラグインのインストール等を行ってください。
参考：[MacにJenkinsインストール](https://michimani.net/post/develop-install-jenkins-to-mac/)

### 3-2. Jenkinsの初期設定

#### Slack

JenkinsのジョブとSlackを連携させるため、SlackとJenkinsの初期設定を行います。
まずはSlackのアプリケーション管理画面（`https://[チャンネルID].slack.com/apps/manage`）で`Jenkins CI`を検索し`Slackに追加`してください。
その後`Jenkins CI`の設定画面で好きなチャンネルにこのアプリを追加すると、ランダムな英数文字列のトークンが発行されるので控えておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/2fc9d7af-5a5c-c237-4212-8667dc4b4763.png)

今度はJenkinsのメニューから`Jenkinsの管理 > プラグインの管理`を選択し、検索バーに*Slack*と打ち込みます。出てきた候補の中から[Slack Notification](https://plugins.jenkins.io/slack/)を選択し、画面に従ってインストール＆Jenkinsを再起動してください。

再起動後、`Jenkinsの管理 > システムの管理`を選択するとSlackの設定項目が追加されています。`Workspace`の項目にはワークスペースのIDを入力し、`Default channel / member id`には先ほど設定したSlackのチャンネル名を記述します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/15d75a86-73a8-2849-55f0-5fb47fe27f60.png)

次に`Crediential`の`追加 ▼ > jenkins`を押すとポップアップ画面が出てくるので設定します。
`種類`は`Secret text`を選択し、`Secret`に先ほど得たSlackのチャンネルへのトークンをコピペします。`ID`には好きな名前を入れておいてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/b5a231f3-d57d-e960-44b8-c6dec5128d13.png)

`追加`ボタンを押すとポップアップが消えるので、戻った画面の`Crediential`の項目でたった今作成したものを選択しましょう。
最後に画面左下の`保存`ボタンを押して完了です。

#### SSH鍵

JenkinsがGitHubから`git pull`するときに使うSSHの**秘密鍵**を登録します。
以下の記事が詳しかったので、そちらを参考に設定してください。
[【Jenkins】GitのSSH接続をするための認証情報を設定する【GitHub】](https://qiita.com/nkjzm/items/865f71efbf494ed57006)

#### Git

自分の環境ではJenkinsがgitコマンドへのパスを持っていなかったので、登録します。
`Jenkinsの管理 > Global Tool Configuration`で、gitのパスを以下のように追加します。パス自体は環境によって異なると思うので、コマンドで`which git`とか打って確認してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/447c87e5-f122-6dc7-da6b-e4d64b5f361c.png)

## 4. Jenkinsのジョブの作成と設定

GitHubからローカル環境のJenkinsジョブを実行させるにあたり、以下の記事が参考になりました。
[GithubからWebhookでJenkinsのジョブを自動実行](https://hayateasdf.hatenablog.com/entry/2018/08/27/190350)
ただ、ビルドトリガーを**リモートからビルド**でなく**GitHub hook trigger for GITScm polling**にするともう少し楽ができるので、この記事では後者を使う前提で話を進めます。

### 4-1. ジョブを作成する準備

#### UltraHookの設定

GitHubからのWobhookをローカルマシンにフォワードするため、[**UltraHook**](http://www.ultrahook.com/)というサービスを利用します。
まずはUltraHookのトップページで`Get Started Now!`というボタンを押してユーザ登録をします。登録が完了するとAPIキーがもらえるので、Macのルートディレクトリに以下のように保存しましょう

```
$ echo "api_key: <APIKEY>" > ~/.ultrahook
```

あとは以下のコマンドで起動できます。（9000はJenkinsのポート番号）

```
$ ultrahook stripe 9000
Authenticated as <yourId>
Forwarding activated...
http://stripe.<yourId>.ultrahook.com -> http://localhost:9000
```

#### GitHub Webhookの設定

Webhookを設定する前に、外部からJenkinsにジョブを実行させるためのユーザトークンを取得します。
`Jenkinsの管理 > ユーザーの管理 > [自身のJenkinsユーザIDを選択] > 設定`の中の`APIトークン`の欄で`Add new Token`を押してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/4c2bd97f-f9d9-777d-2f4e-5e99fa41f81a.png)

適当なトークン名を記入したら`Generate`し、**表示された文字列をどこかに控えておきます**（一度閉じたらもう見れないので）。控え終わったら画面左下の`保存`を押してページを抜けます。
参考：[Jenkinsのジョブをリモートからの実行する手順](https://tomokazu-kozuma.com/how-to-execute-jenkins-job-remotely/)

次はGitHubのリポジトリのページで`Setting > Webhooks`から`Add webhook`ボタンでWebhookを追加し、`Payload URL`に以下のURLを入力します。

```
http://<JenkinsのユーザID>:<Jenkinsのユーザトークン>@stripe.<UltraHookのユーザID>.ultrahook.com/github-webhook/
```

注意点として、`http://...ultrahook.com/github-webhook`のように**末尾にスラッシュを付け忘れるとダメ**です。スラッシュを付け忘れても302リダイレクトが起こるのですが、どうやらUltraHookはそれを握りつぶしてしまうようで、結果として**GitHub側ではWebhookで200レスポンスが帰ってきているのにJenkinsのジョブは実行されない**という現象が起こります。（一敗）

### 4-2. Jenkinsジョブの作成

ようやっっとJenkinsジョブを作成していきます。ここまで来ればあとはもう少しです。
Jenkinsの`新規ジョブ作成`を押し、ジョブ名を入力したら`フリースタイル・プロジェクトのビルド`を選択します。

#### General

`General`は以下のように設定します。GitHubのリポジトリへのURLはclone時のものではなく、普通にブラウザのURL欄に表示されてるやつです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/7f591295-240e-cc80-0484-828bdf03f25d.png)

#### ソースコード管理

`ソースコード管理`では`Git`を選択し、clone時のパスを入力します。認証情報は[3-2. Jenkinsの初期設定のSSH鍵の項](#ssh鍵)で作成したものを選択してください。`追加 ▼`を押す必要はないです。
また、自分はビルドするブランチは特に指定しませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/0acf2dc8-2f14-0eae-a8e2-6a48f3323eea.png)

#### ビルド・トリガ

**GitHub hook trigger for GITScm polling**を選択してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/8cf48246-9b2d-af33-8be1-553f0e1c9751.png)

#### ビルド

Unityプロジェクトの直下へ移動し、`git_pull.sh`を実行します。このシェルスクリプトには、`git pull`から[2章](#2-unityプロジェクトからipaファイルを作成し実機へインストールするまでをコマンドラインから行う)で作成した`build_unity2xcode.sh`を実行する処理までを記述します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/65f98584-910e-dccb-d46f-27fb44b7e73f.png)

```git_pull.sh
#!/bin/bash -xe

echo "Start git pull"

git fetch origin

# GIT_LOCAL_BRANCHが空の場合はGIT_BRANCHから"origin/"以降の文字列を抽出して代入
GIT_LOCAL_BRANCH=${GIT_LOCAL_BRANCH:-${GIT_BRANCH##origin/}}

# ローカルにGIT_LOCAL_BRANCHという名のブランチが存在する場合は単純にHEADを移動。存在しない場合は作成し移動。
# 参考：https://qiita.com/knknkn1162/items/b3af70918770d85bc313
git checkout $GIT_LOCAL_BRANCH

git pull --rebase

echo "Finish git pull"

sh build_unity2xcode.sh
```

`$GIT_LOCAL_BRANCH`や`$GIT_BRANCH`はJenkins側で勝手に設定してくれる環境変数です。ここら辺の環境変数はシェルスクリプト記述欄の下の**ビルドから利用可能な環境変数の一覧**から確認できます。
ここで、pushのあったブランチがローカルに存在しない場合は`$GIT_LOCAL_BRANCH`が空欄になるので、スクリプトでは`$GIT_BRANCH`から**"origin/"**という文字列を除去したものを代入してcheckoutしています。
参考：[git checkout理解してなかった](https://qiita.com/knknkn1162/items/b3af70918770d85bc313)

#### ビルド後の処理の追加

[Slack Notification](https://plugins.jenkins.io/slack/)を追加していれば、`ビルド後の処理の追加 ▼`から`Slack Notification`を選択し以下のように設定できます。画像ではすべての通知を有効化しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/65a36baf-c634-f6bf-cbec-4fdc957bdfaf.png)

これにより、Slack側で以下のように通知を受け取ることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/fdd91122-7330-6802-0e07-9a762c9ca7ef.png)


## 5. おまけ

さて、以上の作業が完了すればGitHubへのpushをトリガーにiOSアプリの実機インストールまでを自動化することができます。
しかし、現状ではMacを起動するたびにUltraHookクライアントをコマンドから起動する必要があり、また体感一時間くらい何もしてないと、UltraHookクライアントはエラーを吐いてプロセスが終了してしまいます。

上記のタイミングで毎回UltraHookクライアントを起動するのは面倒...というか絶対に忘れそうなので、Macの[**Automator**](https://support.apple.com/ja-jp/guide/automator/welcome/mac)を使って、Macの起動時にUltraHookクライアントが自動で立ち上がるようにしたり、エラー出力時に自動で再接続してくれるようにしました。

**※注意**
下記の設定を行うと、UltraHookへPOSTリクエストがあってもコマンドプロンプト上でその結果を見ることができないため、場合によってはデバッグがし辛くなります。この設定は1~4章までの手順を完了した後に行うと良いでしょう。

### AutomatorでUltraHookの起動と継続を自動化

MacのSpotlight検索で*automator*と打って起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/0f4a0542-0b84-b22c-a94b-24486f83bdf5.png)

`新規書類`から`アプリケーション`を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222054/1a7a9bb0-ed15-8bb6-518a-8d7edb8cc334.png)

アクションから`シェルスクリプト実行`を選んで右側のスペースにドラッグアンドドロップし、以下の内容を記述します。

```sh:シェルスクリプトを実行
export PATH=$PATH:/usr/local/bin
while true
do
    ultrahook stripe 9000 || continue
done
```

一行目では、Automatorからシェルスクリプトを実行する際にユーザコマンドへのパスが読み込まれないので、無限ループに入る前にパスを追加して`ultrahook`コマンドが使えるようにしています。
二行目以降では無限ループを作り、いささか行儀が悪いですが`ultrahook stripe 9000`コマンドの実行結果をOR演算子で受け取ると無条件でループを再開しています。
参考：[シェルスクリプトでエラー時の処理を行う方法](https://qiita.com/sh19910711/items/db382a9da77e8ebbf041)

右上の実行ボタンでスクリプトを実行してエラーが出ないことを確認したら、名前を付けて`アプリケーション`フォーマットとして保存しましょう。
作成したアプリケーションは、以下の記事を参考にMacへのログイン時に自動で起動するよう設定しておきましょう。
[Mac - ログイン時にアプリケーションを自動起動](https://pc-karuma.net/mac-app-startup/)

# おわりに

お疲れさまでした。ここまで読んでいただきありがとうございます。
実際に今回の構成を使ってみると、SceneにCubeしか置いてないUnityプロジェクトでもビルドに12分とかかかってるので、自動化して良かったなぁと感じています。

Jenkinsまわりも今まで知識しか持っていなかったので、今回の件で簡単な実践を積めて満足です。
あとはこの環境の構築にかかってしまった時間を、今後の開発で取り返していきたいですね。

余談ですが、最初はなんとなくJenkinsをMac上のDockerコンテナで動かそうとしていたものの、単純に作業量が増えるうえ（今回の件だと）特に恩恵もないことが判明したので途中で諦めました。
JenkinsもDockerもほぼ素人なので、将来的にJenkinsにもっと慣れて、ビルドの並列化とかを考えるようになったら組み合わせていろいろやってみたいですね。