はじめに
この記事は、 Unity Advent Calendar 2018 の10日目の記事になります。
9日目の記事は gremito さんの AR上にShaderを動かしてみた という記事でした!
本記事は、株式会社キッズスターが開発・運営を行っているごっこランドというアプリの裏側を支える技術を少しずつ紹介する(予定の)連載 *1 「ごっこランドを支える技術」の ビルド編 になります。
ごっこランドは Unity 製モバイルアプリとして約5年に渡って運営されているのですが、月日を重ねるにつれて日々巨大化していくプロジェクトや開発者を取り巻く環境・状況の変化への対応を行ってきました。
その結果として、様々な工夫が組み込まれたビルドシステムが構築されているので、本記事ではそのビルドシステムについて紹介をしていきたいと思います。
目次
- 概要
- 背景
- 詳細
- システム構成
- プロジェクト構成
- ビルドスクリプト
概要
もの凄くザックリ言うと、Slack にコマンドを打ち込んでしばらく待つと .ipa
や .apk
が DeployGate に配信される という仕組みです。
図で説明すると以下のような感じです。
上図をテキストでも解説すると以下のような感じです。
- Slack に
jenkins build player <project_name>
と発言する - Slack Outgoing Webhook が実行される
- AWS API Gateway により発行された URL を叩く
- AWS Lambda にデプロイ済の Node.js アプリが実行されて Slack 上での発言を解析する
- 社内に配備してあるビルドサーバの Jenkins のパラメータ付きジョブを実行する
- Unity Editor を
batchmode
で起動し、UnityEditor.BuildPipeline.BuildPlayer()
を実行する PostprocessBuild
内で作成されたアーカイブ ((iOS は.ipa
、Android は.apk
)) を DeployGate にデプロイ- DeployGate の Slack 連携設定により、Slack にデプロイ完了が通知される
詳細は次節以降で解説します。
Unity バージョン
2018/12/10 (Mon) 時点では Unity 2018.2.17f1 を用いています。
開発中は常に最新に上げ続けており、リリース内容が確定した時点で当該ブランチに於ける利用バージョンを最新バージョンでロックしビルドしています。
ビルドプラットフォーム
プロジェクトサイズ
$ du -sh Assets 5.1G Assets
2018/12/10 (Mon) 時点でプロジェクトの総サイズは 5.1GB です。 Library/
ディレクトリも含めると 20GB を超えていました。
背景
上述の通りプロジェクトのサイズが割と大変なことになっているため、ごっこランドの開発では プロジェクト分割 というテクニックを用いています。
以下のスライドで簡単に纏めていますが、ざっくり言うと 個別の Unity プロジェクトとして開発したものを npm を使って1つのプロジェクトとして束ねる 仕組みを構築した感じになります。
そして、複数の開発プロジェクトが並行して走っている状態で、開発者一人一人の開発生産性を最大化する必要性が高まってきていました。
そのためには、個々の開発プロジェクトや束ねたプロジェクトを、開発者のマシンリソースを使わずにビルドできるようにする必要があります。
この課題を解決するために、専用のビルドサーバを配備*3し、開発者が任意のタイミングで実機ビルドを作成出来るようにしようと思い立ちました。*4
また、株式会社キッズスターでは、結構カジュアルにリモートワークが行われています。
そのため、VPN とかを使わずとも社内ネットワークにあるビルドサーバでビルドできるようにする必要性もあります。
更に、可能な限り API Token や認証鍵などの 秘匿すべき情報をリポジトリに載せたくない ので、その辺りにも気を遣う必要があります。
これらの課題を解決するために回りくどいシステムを構築した次第です。
次節から各項目について掘り下げていきます。
詳細
システム構成
改めて概要の図を掲示します。
Slack → AWS API Gateway → AWS Lambda
いわゆる ChatOps の基本的な構成になるかと思います。
Slack の任意のチャンネルに対する発言などを任意の URL に対して POST してくれる Outgoing Webhook という仕組みを使います。
そんなに難しくないのでググれば簡単に設定できると思います。
AWS の権限管理の仕組みである IAM 辺りはハマるかもですが、基本はテンプレ通りにやれば問題ないかと思います。
Lambda に配置する関数としては、Slack の発言内容をパースして Jenkins のパラメータ付きビルド URL を構築する という要件を満たせば、どんな言語で書こうが問題ありません。*5
(今回のアドカレの趣旨からは外れてしまうので仔細は書きませんが、需要があればいつか別記事で掘り下げるかもしれません。)
AWS Lambda → Jenkins
組織によってはココが最大の障壁になると思います。
と、言うのも、AWS 側から社内ネットワークに向けた内向きのアクセスを許可する必要があるため、セキュリティが厳しい組織の場合ココで詰む可能性を秘めています。
その場合でも、外向きのアクセスは行けると思うので、Lambda からジョブパラメータを JSON か何かで S3 に Put して、Jenkins でソレをポーリングするとかもアリかもしれません。頑張ってください。
Jenkins → Unity Editor
Jenkins のジョブ設定で、シェルを叩くことができるので、 /Applications/Unity/Unity.app/Contents/MacOS/Unity
を実行するようなシェル(後述)を書きます。
前提として、ビルド時のエントリポイントとなる public static
なメソッドが必要になるので、何らかの形でプロジェクトに組み込んでおきましょう。
弊社では umm/simple_build: Provide BuildPlayer menu というライブラリを開発し利用しております。
Keystore 設定(Android のみ)
Android の APK を作成するにあたって、Keystore による署名を施す必要があります。
ビルド処理実行前に ProjectSettings を上書きしてあげる必要があるので、 UnityEditor.Build.IPreprocessBuildWithReport
を実装したクラスの OnPreprocessBuild()
メソッドにて UnityEditor.PlayerSettings.Android.(keystore|keyalias)(Name|Pass)
を上書きします。
鍵の情報をリポジトリに載せるのは危ないので、環境変数などから貰うとヨサソウです。
弊社では、 umm/keystore_manager: Manage Keystore for Android というライブラリを用いています。
Build
UnityEditor.BuildPipeline.BuildPlayer()
を実行します。
出力先は何でも良いと思いますが、弊社では simple_build 側の実装として <path_to_project>/Build/
の下にプラットフォーム毎・環境種別(開発か本番か)で分けて出力するようになっています。
Archive/Export (iOS のみ)
いくつか方法はありますが、PostprocessBuild として実装するのが筋が良いでしょう。
具体的には UnityEditor.Build.IPostprocessBuildWithReport
という interface を実装したクラスの OnPostprocessBuild()
メソッドにて /usr/bin/xcodebuild
コマンドを実行する形になります。
Android の場合は直接 .apk
ファイルが出力されるので、この手続きは不要です。*6
弊社では umm/xcode_archiver: Run xcodebuild on PostprocessBuild というライブラリを用いてアーカイブしております。
こちらを用いない場合でも、以下のようなコマンドを構成すれば Archive/Export が行えます。
Archive
Unity から出力された Xcode Project を .xcarchive
としてアーカイブします。
Firebase などの CocoaPods を利用するような Native Plugin を用いている場合は -project
の代わりに .xcworkspace
へのパスを -workspace
引数に指定する必要があります。
$ /usr/bin/xcodebuild \ -project "<path_to_build>/Unity-iPhone.xcodeproj" \ -scheme "Unity-iPhone" \ -archivePath "<path_to_build>/Unity-iPhone.xcarchive" \ -sdk iphoneos \ -configuration Release \ -allowProvisioningUpdates \ archive
Export
先ず、Export Option と呼ばれる .plist
ファイルを作成します。*7
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>method</key> <string>ad-hoc</string> <key>compileBitcode</key> <false/> <key>embedOnDemandResourcesAssetPacksInBundle</key> <false/> </dict> </plist>
続いて、作成した Export Option とあわせて xcodebuild
コマンドを実行します。
$ /usr/bin/xcodebuild \ -exportArchive \ -archivePath "<path_to_build>/Unity-iPhone.xcarchive" \ -exportPath "<path_to_build>/export" \ -exportOptionsPlist "<path_to_build>/ad-hoc.plist" \ -allowProvisioningUpdates
Deploy
検証用端末などにデプロイするために DeployGate というサービスを用いており、同サービスが提供するコマンドラインインタフェース*8を実行するコトでビルド成果物をデプロイします。
以下のようなコマンドを構成すれば OK です。
$ dg deploy <path_to_archive>
DeployGate の機能として、新しいビルドがデプロイされると Slack に通知する機能があるので、それを有効にしておくと便利です。
プロジェクト構成
「背景」の章でも述べたように、弊社では複数のプロジェクトとして開発したゲームコンテンツを一つのプロジェクトに束ねてアプリにしております。
そのテクニックや注意点などについて掘り下げて紹介します。
マージ
プロジェクトを束ねる際のツールとして npm を用いています。
個々のプロジェクトを npm のパッケージと見なして、本体のプロジェクト側の package.json
に依存パッケージとして定義するコトでマージを行います。
その際、インストールされた個別プロジェクトの中身を Assets/Projects/
以下に丸っとコピーするスクリプトを書いて postinstall*9 内で実行させています。
なお、実行速度の観点から、生の npm ではなく yarn を用いて実行しています。
ライブラリ管理
弊社では umm という仕組みを用いてライブラリ管理を行っています。
仔細は上記リポジトリをご参照いただくとして、概要としては各ライブラリを npm のパッケージとして取り扱えるようにすることで npm を用いた依存管理を行えるようにするものとなります。
現在の所、各ライブラリ npmjs へはパブリッシュしておらず*10、GitHub にて公開しているモノを利用しています。
SortingLayer / Tag
複数プロジェクトをマージする関係上、SortingLayer や Tag などに代表される個々のプロジェクト設定として保存される情報は原則的にコピーできません。
これらの情報は ProjectSettings/TagManager.asset
というファイルに保存されるのですが、全てのプロジェクトで設定が同じになるようにする必要があるため、無闇に増やせないなどの制約が生じています。
また、SortingLayer に関しては、表向き文字列ですが内部的には数値で管理しており、この値をプロジェクト間で統一することが難しかったため、独自のエディタ拡張*11を実装することで問題を回避しています。
特別な事情が無ければ SortingOrder だけで頑張るなどの工夫をした方が良いかも知れません。
Shader
個別のプロジェクトのスクリプトとシェーダを除くあらゆる Asset は基本的に AssetBundle として配信するため、設定に依ってはシェーダがアプリに含まれなくなることがあります。
そのため、都度 Always Included Shaders に含めるように設定するなどの作業が必要になる場合があります。
link.xml
Scripting Backend に IL2CPP を用いており、かつ AssetBundle を用いている場合、 Unity - Manual: Managed bytecode stripping with IL2CPP に記載があるように、一部のコードが削除されることがあります。
これを回避するために link.xml
に情報を追記したり、 [Preserve]
属性を付けるなどの工夫が必要になることがあります。
Packages
2018年12月現在、 Unity Package Manager では GitHub などの git リポジトリからのパッケージ取得を完全にはサポートしていません。
詳細はブログを書いたのでそちらをご参照いただくとして、弊社では上述の通りライブラリの管理も npm を通して GitHub から取得する方法を採っているため、個別プロジェクト側で必要になったパッケージの依存解決を自動では行えません。
そのため、個別のプロジェクトで必要になったパッケージは、本体側のプロジェクトの manifest.json
に手動で記載しないといけません。
この辺は Unity Package Manager が完全民主化されたあかつきには不要の悩みとなりそうです。
Assembly Definition Files
コンパイル時間の高速化とライブラリ間の循環依存を未然に防ぐために、Assembly Definition Files を積極的に導入しています。
各プロジェクトを個別の Assembly として切り出すため、 namespace などの重複は神経質にならなくても済むようになりました。*12
ビルドスクリプト
Jenkins が実行するビルドスクリプトの概要をご紹介します。
パラメータ
Jenkins のジョブとして受け付けるパラメータは以下のように設定しています。
Name | Example | Description |
---|---|---|
repository |
pretendland |
プロジェクトの名称 clone 先のルールを統一することで、リポジトリ名からパスを特定可能 |
branch |
develop/v4.10.0 |
ビルドするブランチ名 省略した場合は master をビルド |
platform |
iOS |
ビルド対象のプラットフォーム Switch Platform の効率を考えて、プラットフォーム毎に clone 先を分けている |
editor_version |
2018.2.17f1 |
起動する Unity Editor のバージョン 未指定の場合は ProjectSettings/ProjectVersion.txt の値を利用 |
development_build |
true |
開発ビルドを作成する場合に真を指定 |
処理の流れ
cd <path_to_project>
- 先ず、パラメータとして渡された
repository
とplatform
から推定されるプロジェクトディレクトリに遷移
- 先ず、パラメータとして渡された
git checkout <branch_name>
- パラメータに指定されたブランチに checkout
git lfs pull
yarn install
package.json
,yarn.lock
の情報に従い、パッケージを fetch, install
/Applications/Unity/Unity.app/Contents/MacOS/Unity
- simple_build のメソッドを実行
git reset && git clean -fd
- ビルド完了後、ビルドサーバ上のローカルリポジトリを掃除
git checkout master
- 元のブランチに戻って終了
Unity Editor 起動時引数
最低限、以下の引数が指定されていれば batchmode ビルドが可能です。
Argument | Value | Description |
---|---|---|
-quit |
ビルド完了時に Unity Editor のプロセスを終了させる | |
-batchmode |
Unity Editor の GUI は起動させない | |
-projectPath |
<path_to_project> |
プロジェクトのディレクトリAssets/ や ProjectSettings/ がある親ディレクトリを指定する |
-executeMethod |
<method_name> |
実行する public static なメソッド名を namespace を含む完全修飾名で指定 |
-logFile |
/dev/stdout |
Jenkins のログ管理に流すためにログの出力先を標準出力に設定する |
-buildTarget |
[iOS|Android] |
ビルド対象のプラットフォーム 既にプロジェクトが Switch Platform 済であれば不要 |
そのほかの Unity Editor 起動時の引数は Unity - Manual: Command line arguments をご参照ください。
おわりに
ここまでお読みいただき、まことにありがとうございました!
もし貴方がオレオレビルドシステムを構築しようと思っており、本記事に構築の一助となるような情報があったのならば、心から嬉しく思います。
Unity Advent Calendar 2018 の11日目は copo さんの「インテリアマッピング(interior mapping)~その1~」です。
*1:本記事が第一回目なんですけどね。
*2:iOS SDK、Android SDK ともに Latest でビルドしています。
*3:iOS のビルドを考慮し Mac mini を採用しています。
*4:勿論 Unity Cloud Build を用いる選択肢もあったのですが、柔軟性や後述のライブラリ管理の観点から断念しました。
*5:私は Node.js で書きました。
*6:Android Studio で開くためのプロジェクトを出力することも可能ですが、本記事では割愛します。
*7:この例では Bitcode や OnDemand Resources などを無効にしていますが、プロジェクトにあわせて変更してください。
*9:記事執筆時点でレイアウトが崩れていましたが、頑張れば読める…かな?
*10:中身は Node.js じゃないので Ban されても嫌だな、というコトで。
*11:現在の所、弊社 Organizations の private リポジトリで管理しています。
*12:とはいえ、可読性などの観点から namespace の設定ルールは割と厳格にしていますが。