LoginSignup
24
19

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-04-22

はじめに

研究でiOSデバイスを3~4台使うアプリケーションを開発することになったのですが、以下の手順を手作業でやるのは流石にしんどいと思ったので、2~5までを自動化しました。
manual.png
1. 普段使いのWindows PCからGitHubへ変更をpush
2. 手元のMac PCでそれをpull
3. UnityプロジェクトをビルドしてXcodeプロジェクトの作成
4. Xcodeプロジェクトををビルドしてipaの作成
5. ipaを手元の実機3~4台へインストール

auto.png
1'. push時のGitHubからのWebhookをUltraHookでトンネリングしてローカル環境のMac上のJenkinsに通知する
6. ビルドが終了したタイミングでSlackに成否を通知

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

要件

  • 実機(Windows, Mac, iOSデバイス)が全て手元にあり、MacとiOSデバイスを有線接続できる
  • 開発時は実機の全てが常に手元にあるわけではない(開発環境がLAN内で完結できない)
  • 自動化は無料で実現したい(個人開発)
    • 有料で良いならdeploygateとか使うとよさげ?

環境

  • 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のものにしておきましょう。

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

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

なお、自分の環境ではXcodeがiOS 13.4をサポートしていなかったので自分でサポートファイルをダウンロードしました。
【Xcode】実機で動かそうとしたらこんなエラーが出た時

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

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

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

ipaファイルの作成までは以下の記事が非常に参考になりました。
UnityのiOSビルドをコマンドラインから行う (Mac用)

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

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つけた方がよかった

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プロジェクトの段階で有効にしていたことで、前述の記事に倣いプロビジョニングファイルのUUIDを指定すると、逆にエラーが出ました。

Xcode 8: xcodebuildで、Automatic Signingに対応する
自分は上記の記事のビルドエラー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について
上記の記事に従い、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 8080Port 9000に変更しておきました。
参考:macOS上で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インストール

3-2. Jenkinsの初期設定

Slack

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

image.png

今度はJenkinsのメニューからJenkinsの管理 > プラグインの管理を選択し、検索バーにSlackと打ち込みます。出てきた候補の中からSlack Notificationを選択し、画面に従ってインストール&Jenkinsを再起動してください。

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

image.png

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

image.png

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

SSH鍵

JenkinsがGitHubからgit pullするときに使うSSHの秘密鍵を登録します。
以下の記事が詳しかったので、そちらを参考に設定してください。
【Jenkins】GitのSSH接続をするための認証情報を設定する【GitHub】

Git

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

image.png

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

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

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

UltraHookの設定

GitHubからのWobhookをローカルマシンにフォワードするため、UltraHookというサービスを利用します。
まずは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

適当なトークン名を記入したらGenerateし、表示された文字列をどこかに控えておきます(一度閉じたらもう見れないので)。控え終わったら画面左下の保存を押してページを抜けます。
参考:Jenkinsのジョブをリモートからの実行する手順

次は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

ソースコード管理

ソースコード管理ではGitを選択し、clone時のパスを入力します。認証情報は3-2. Jenkinsの初期設定のSSH鍵の項で作成したものを選択してください。追加 ▼を押す必要はないです。
また、自分はビルドするブランチは特に指定しませんでした。
image.png

ビルド・トリガ

GitHub hook trigger for GITScm pollingを選択してください。
image.png

ビルド

Unityプロジェクトの直下へ移動し、git_pull.shを実行します。このシェルスクリプトには、git pullから2章で作成したbuild_unity2xcode.shを実行する処理までを記述します。
image.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理解してなかった

ビルド後の処理の追加

Slack Notificationを追加していれば、ビルド後の処理の追加 ▼からSlack Notificationを選択し以下のように設定できます。画像ではすべての通知を有効化しています。
image.png

これにより、Slack側で以下のように通知を受け取ることができます。
image.png

5. おまけ

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

上記のタイミングで毎回UltraHookクライアントを起動するのは面倒...というか絶対に忘れそうなので、MacのAutomatorを使って、Macの起動時にUltraHookクライアントが自動で立ち上がるようにしたり、エラー出力時に自動で再接続してくれるようにしました。

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

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

MacのSpotlight検索でautomatorと打って起動します。
image.png

新規書類からアプリケーションを選択します。
image.png

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

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

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

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

おわりに

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

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

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

24
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
19