【研究課題レポート抜粋】Jenkins+Unityで構築するスマフォアプリビルドサーバー | サイバーエージェント 公式エンジニアブログ
最近はもっぱらUnityでのスマフォアプリ開発に没頭している、yhouseiです

エンジニアブログ執筆ってことで、第7回研究課題レポートから”Jenkins+Unityで構築するスマフォアプリビルドサーバー”についてアップします。

はじめに

スマフォアプリ開発においてビルドサーバーの導入が進んでいないこと、またUnityアプリ開発での導入情報は少ないことから(日本語での情報は皆無、海外でも少ない)、今回はJenkins+Unityでスマフォアプリビルドサーバーの構築にチャレンジしました。

Unityアプリ開発について
1.Unityについて

  Unityについて一言で説明すると、マルチプラットフォーム対応(PC/Mac/Web/iOS/Andorid/Xbox360/PS3/Will)のゲームエンジンになります。
詳細はUnityのWebサイト[1]をみてもらえれば、魅力的な開発環境であることがわかると思います。
 無料版もあるのですぐに試すことができますが、対応プラットフォームがPC/MAC/Webのみで一部機能が使用できなくなっています。

2.Unityでのスマフォアプリ開発作業について

 ほとんどがUnity EditorとScript Editor上の作業になります、実機ではパフォーマンスチューニング、操作感、Device機能の確認になります。


① Unity Editor上でAsset管理(テクスチャ、スクリプトなど)、シーン作成(Assetの配置、初期化)、(Editor上での)アプリ実行、停止。
② Script Editor(MonoDevelop)でスクリプト記述、デバッグ(ブレイクポイントの設定,ステップ実行等)
③ 各プラットフォーム用のファイル出力。Androidのapkファイルダイレクトに出力できますが、iOSはxcodeのプロジェクトが出力されます。xcodeでプロジェクトを開きipaファイルを出力することになります。

3.ビルドサーバーの必要性について

Unityアプリやスマフォアプリ開発ではSCMを使った開発はしているが、リリースプロダクトが個人のマシンでビルドされリリースされることが多いように思います、それによって下記のような問題が発生する可能性があります。

1. リリースプロダクトのソースファイルがSCM上にある保証がない
2. 1を前提にするとリリースタグ、ブランチなどのよる切り戻しができない
3. コミット漏れによる、リリースプロダクトのソースコード紛失
4. リリースプロダクトが個人所有のローカルマシンでしかビルドできない

Webアプリ開発では当たり前?のようなことが浸透(or 整備)してない状態にあるのが、現状のスマフォアプリ開発だと思います。

この問題を解消するためにも、ビルドサーバーの構築は必須だと思います。

ビルドサーバーの構築
1.ビルドサーバーの要件

 今回構築するビルドサーバーの要件としては、SubversionにコミットされてあるUnityプロジェクトをビルドしてipa,apkを出力することになります。
 iOSアプリはMacでしか作成できないので、ビルドサーバーに使用するマシンはMacになります。
 また、ビルドの定期実行、手動実行、成果物(ipa,apk)の取得がブラウザで簡単にできるJenkins[2]を使用してビルドサーバーを構築していきます。

2.準備

 ビルドサーバー構築のために使用するマシンのスペック、OS,ミドルウェア,SDKは下記になります。

・ハードスペック
 MacBookPro 17 inch (2.8Ghz Intel Core2Duo , mem 4G)
・OS
 MacOS X 10.6.8 (Snow Leopard)
・ミドルウェア、SDK
 Unity3.4.0f5 Pro(add on iOS Pro,Andorid Pro)
 Xcode 4.0.2 for Snow Leopard
 Android SDK
 Jenkins 1.426
 
 JenkinsをMac用のnative packageからインストールした場合
はマシン起動時の自動起動が設定されます。起動ユーザーをDaemonからログインユーザーに変更しておきます。
 /Library/LaunchDaemons/org.jenkins-ci.plistファイルのUserName設定をDaemonからログインユーザーに変更することで対応できます。

------ /Library/LaunchDaemons/org.jenkins-ci.plist -------
<?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>EnvironmentVariables</key>
<dict>
<key>JENKINS_HOME</key>
<string>/Users/Shared/Jenkins/Home</string>
</dict>
<key>GroupName</key>
<string>daemon</string>
<key>KeepAlive</key>
<true/>
<key>Label</key>
<string>org.jenkins-ci</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/java</string>
<string>-Xmx512m</string>
<string>-jar</string>
<string>/Applications/Jenkins/jenkins.war</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>UserName</key>
<string>Daemon</string> <!—ログインユーザーに変更する -->
</dict>
</plist>

3.テスト用Unityアプリ

 今回のビルドサーバー構築テスト用にCubeが画面中央で回転するシンプルなアプリPiggCubeをUnityで作成しました。


このアプリをSubversionで管理してビルド対象にします。

 UnityのプロジェクトをSubversionで管理する場合は UnityEditor上の設定で(Edit→ProjectSetting→Editor)External Version Control SupportをEnableに設定する必要があります。これによりUnity Editor上でAssetファイル作成すると *.metaファイルも一緒に作成されるようになります。このファイルも管理対象にします。Unityプロジェクト内のLibraryフォルダは下記のファイル以外は管理対象にしないようにします。
 
EditorBuildSettings.asset
InputManager.asset
ProjectSettings.asset
QualitySettings.asset
TagManager.asset
TimeManager.asset
AudioManager.asset
DynamicsManager.asset
NetworkManager.asset

 PiggCubeプロジェクトのディレクトリ構成について説明します、Assets,LiblaryフォルダはUnity管理のフォルダになります。
 batch_buildフォルダは後述するJenkinsのビルドシェルやBatchBuild.csで利用します。

PiggCube →プロジェクトルート
|-- Assets
| |-- Editor
| | |-- BatchBuild.cs →バッチモードで実行するクラス
| | `-- BatchBuild.cs.meta
| |-- Editor.meta
| |-- _Materials
| | |-- PiggFaceCube.mat →Cube用のマテリアル
| | `-- PiggFaceCube.mat.meta
| |-- _Materials.meta
| |-- _Prefab
| | |-- Cube.prefab →Cubeオブジェクト
| | `-- Cube.prefab.meta
| |-- _Prefab.meta
| |-- _Scenes
| | |-- PiggCube.unity →シーンファイル
| | `-- PiggCube.unity.meta
| |-- _Scenes.meta
| |-- _Scripts
| | |-- CubeRotate.cs →回転制御スクリプト
| | `-- CubeRotate.cs.meta
| |-- _Scripts.meta
| |-- _Textures
| | |-- housei_face_glad.png →Cubeに貼り付けるテクスチャ
| | `-- housei_face_glad.png.meta
| `-- _Textures.meta
|-- Library
| |-- AudioManager.asset
| |-- DynamicsManager.asset
| |-- EditorBuildSettings.asset
| |-- InputManager.asset
| |-- NetworkManager.asset
| |-- ProjectSettings.asset
| |-- QualitySettings.asset
| |-- TagManager.asset
| |-- TimeManager.asset
`-- batch_build →バッチビルド用に使用するファイル
|-- Android
| `-- piggcube.keystore →署名用ファイル
`-- iOS
|-- ios_piggcube.p12 →コード署名に使用する証明書
`-- ios_ piggcube.mobileprovision →プロビジョニングファイル

4.JenkinsのJob作成

 Jobを新規作成してSCM設定はSVNを選択、リポジトリURLを対象のサンプルに設定,ローカルモジュールディレクトリをPiggCubeに設定しました。

 ビルドはシェルを使用しています。シェルではまずUnityをバッチモードで起動させます、バッチモードではUI Windowなどは表示されず、指定したクラスメソッドを呼び出して利用します。呼び出されたクラスメソッドではiOSのxcodeプロジェクト、Andoridはapkを出力します。
 出力されたxcodeプロジェクトをxcodebuid[3]を使ってコマンドラインでビルドしていますが、コード署名はSVN(PiggCube/batch_build/iOS/)にあるものを使用するのでxcodebuild引数に設定しています、またdSYMを出力するための設定も引数に設定しています。その他はxcodeプロジェクト設定はUnityが出力したものが使用されます。

 ビルド後の処理は“成果物を保存“にチェックをしてPiggCube/*.*にしています。ビルドシェルで生成したipa,dSYM(zip圧縮したもの),apkを成果物として保存しています。dSYMはiOSのcrash logを解析するのに使用するので追加しています。

・Unityのバッチモードから呼び出されるBatchBuild.ReleaseBuildを実行する
----- PiggCube/Assets/Editor/BatchBuild.cs -----
using UnityEngine;
using UnityEditor;
using System.Collections;

public class BatchBuild {
// ビルド対象のシーン
private static string[] scene = {"Assets/_Scenes/PiggCube.unity"};
// keystore Path
private static string keystorePath = “batch_build/Android/piggcube.keystore”;
// keystoreのパスワードはUnityEditorで設定できるが保持されないのでここに記述
private static string keystorePass = "hogehoge";
private static string keyaliasPass = "hogehoge";

// リリースビルド
public static void ReleaseBuild(){
if ( BuildiOS(true)==false ) EditorApplication.Exit(1);
if ( BuildAndroid(true)==false ) EditorApplication.Exit(1);
EditorApplication.Exit(0);
}
// 開発用ビルド
public static void DevelopmentBuild(){
Debug.Log("DevelopmentBuild");
if ( BuildiOS(false)==false ) EditorApplication.Exit(1);
if ( BuildAndroid(false)==false ) EditorApplication.Exit(1);
EditorApplication.Exit(0);
}
// iOSビルド
private static bool BuildiOS(bool release){
Debug.Log("Start Build( iOS )");
BuildOptions opt = BuildOptions.SymlinkLibraries;
// 開発用ビルドの場合のオプション設定
if ( release==false ){
opt |= BuildOptions.Development|BuildOptions.ConnectWithProfiler|BuildOptions.AllowDebugging;
}
// ビルド
// シーン、出力ファイル(フォルダ)、ターゲット、オプションを指定
string errorMsg =
BuildPipeline.BuildPlayer(scene,"ios",BuildTarget.iPhone,opt);
// errorMsgがない場合は成功
if ( string.IsNullOrEmpty(errorMsg) ){
Debug.Log("Build( iOS ) Success.");
return true;
}
Debug.Log("Build( iOS ) ERROR!");
Debug.LogError(errorMsg);
return false;
}
// Androidビルド
private static bool BuildAndroid(bool release){
Debug.Log("Start Build( Android )");
BuildOptions opt = BuildOptions.None;
// 開発用ビルドの場合のオプション設定
if ( release==false ){
opt |= BuildOptions.Development|BuildOptions.ConnectWithProfiler|BuildOptions.AllowDebugging;
}
// keystoreファイルのの場所を設定
string keystoreName =
System.IO.Directory.GetCurrentDirectory()+"/"+ keystorePath;
// set keystoreName
PlayerSettings.Android.keystoreName = keystoreName;
// パスワードの再設定
PlayerSettings.Android.keystorePass = keystorePass;
// パスワードの再設定
PlayerSettings.Android.keyaliasPass = keyaliasPass;

// ビルド
// シーン、出力ファイル(フォルダ)、ターゲット、オプションを指定
string errorMsg =
BuildPipeline.BuildPlayer(scene,”PiggCube.apk”,BuildTarget.Android,opt);
// errorMsgがない場合は成功
if ( string.IsNullOrEmpty(errorMsg) ){
Debug.Log("Build( Android ) Success.");
return true;
}
Debug.Log("Build( Android ) ERROR!");
Debug.LogError(errorMsg);
return false;
}

・Jenkins Jobのビルドシェル
#-----------------------------------------------------------------------------------------------
# Unityをバッチモードで起動し、xcodeプロジェクト,apkファイルを出力する
#-----------------------------------------------------------------------------------------------
# Unityアプリパス
UNITY_APP_PATH=/Applications/Unity/Unity.app/Contents/MacOS/Unity
# 対象のUnityプロジェクトパス
UNITY_PROJECT_PATH=$WORKSPACE/PiggCube
# バッチモードで起動後に呼び出すメソッド
UNITY_BATCH_EXECUTE_METHOD=BatchBuild.DevelopmentBuild
# Unity Editor ログファイルパス
UNITY_EDITOR_LOG_PATH=~/Library/Logs/Unity/Editor.log

# 指定のUnityプロジェクトをバッチモード起動させて、指定のメソッド(UnityScript)を呼び出す
$UNITY_APP_PATH -batchmode -quit -projectPath "${UNITY_PROJECT_PATH}" -executeMethod $UNITY_BATCH_EXECUTE_METHOD

# Unityでのbuildに失敗した場合は終了
if [ $? -eq 1 ]; then
cat $UNITY_EDITOR_LOG_PATH
exit 1
fi

# Unity Editorが出力したログを表示する
cat $UNITY_EDITOR_LOG_PATH

#--------------------------------
# apkファイル名の変更
#--------------------------------
APK_FILE_PATH=$WORKSPACE/PiggCube/PiggCube.apk
NEW_APK_FILE_PATH=$WORKSPACE/PiggCube/PiggCube-$BUILD_NUMBER-$BUILD_ID.apk

# ファイル名変更
mv $APK_FILE_PATH $NEW_APK_FILE_PATH


#----------------------------------------------------------------------
# コマンドラインでのipaと.dSYMファイルの作成
#----------------------------------------------------------------------

#------- 証明書インポートととプロビジョニングファイルのコピー -------#

# Keychain Location
KEYCHAIN_LOCATION=~/Library/Keychains/login.keychain
# Mac OSX管理パスワード
OSX_ADMIN_PASSWORD=hogehoge

# 証明書ファイルパス
IOS_P12_FILE_PATH=$WORKSPACE/PiggCube/batch_build/iOS/ios_piggcube.p12
# 証明書ファイルパスワード
IOS_P12_PASSWORD=hogehoge
# プロビジョニングファイルパス
IOS_PROVISIONING_FILE_PATH=$WORKSPACE/PiggCube/batch_build/iOS/ios_piggcube.mobileprovision
# プロビジョニングファイルのUUID
PROFILE_UUID=`grep "UUID" ${IOS_PROVISIONING_FILE_PATH} -A 1 --binary-files=text 2>/dev/null |grep string|sed -e 's/^[[:blank:]]<string>//' -e 's/<\/string>//'`


# Keychainをアンロックにする
security unlock-keychain -p $OSX_ADMIN_PASSWORD "${KEYCHAIN_LOCATION}"

# 証明書のimport(すでにimport済みでも実行)
security import "${IOS_P12_FILE_PATH}" -f pkcs12 -P $IOS_P12_PASSWORD -k "${KEYCHAIN_LOCATION}" -T /usr/bin/codesign

# プロビジョニングファイルをコピーする(本来は改行してないです)
cp $IOS_PROVISIONING_FILE_PATH
~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE_UUID.mobileprovision

#------- xcodeプロジェクトをビルドして .appと.dSYMを生成する -------#

# xcodeプロジェクトパス
XCODE_PROJECT_PATH=$WORKSPACE/PiggCube/ios
XCODE_PROJECT_CONFIG_PATH=$XCODE_PROJECT_PATH/Unity-iPhone.xcodeproj
# ビルド CONFIGURATION設定
CONFIGURATION=Release
# コード署名用 IDENTITY
IDENTITY="iPhone Developer: hogehoge"

# .dSYM生成のためのビルド設定
BUILD_OPT_MAKE_DSYM="GCC_GENERATE_DEBUGGING_SYMBOLS=YES DEBUG_INFORMATION_FORMAT=dwarf-with-dsym DEPLOYMENT_POSTPROCESSING=YES STRIP_INSTALLED_PRODUCT=YES SEPARATE_STRIP=YES COPY_PHASE_STRIP=NO"

# ビルド開始(本来は改行してないです)
xcodebuild
-project "${ XCODE_PROJECT_CONFIG_PATH }"
-configuration "${CONFIGURATION}"
CODE_SIGN_IDENTITY="${IDENTITY}"
OTHER_CODE_SIGN_FLAGS="--keychain ${KEYCHAIN_LOCATION}"
$BUILD_OPT_MAKE_DSYM

#------- .appからipa作成 -------#

# .appのPATH設定
TARGET_APP_PATH=$ XCODE_PROJECT_PATH/build/PiggCube.app
# ipaのPATH設定
IPA_FILE_PATH=$WORKSPACE/PiggCube/PiggCube-$BUILD_NUMBER-$BUILD_ID.ipa

# ipa生成開始(本来は改行してないです)
/usr/bin/xcrun
-sdk iphoneos
PackageApplication
-v "${TARGET_APP_PATH}"
-o "${IPA_FILE_PATH}"
--sign "${IDENTITY}"
--embed "${IOS_PROVISIONING_FILE_PATH}"

#------ .dSYMをzip圧縮する -----#

# .dSYMのPATH設定
TARGET_DSYM=PiggCube.app.dSYM
# Zip出力する.dSYMファイルのPATH設定
DSYM_ZIP_PATH=$WORKSPACE/PiggCube/PiggCube.app.dSYM-$BUILD_NUMBER-$BUILD_ID.zip

# 対象.dSYMが存在するディレクトリに移動
cd $XCODE_PROJECT_PATH/build/

# zip圧縮開始
zip -r $DSYM_ZIP_PATH $TARGET_DSYM


結論・まとめ

 Unityはバッチモードが用意されコマンドラインで各プラットフォームの出力できる環境は整っているので構築するに当たって問題は有りませんでした。AndroidはUnityEditorで設定したkeystoreのパスワードが保存されないのでBatchBuild.csで再設定する必要がありました。また、ロビジョニングファイルのUUIDの取得、Keychainのコマンドライン操作[4]、初回コード署名時のダイアログが表示される問題、Releaseビルドでの.dSYM出力するためのビルド設定の調査に時間がかかりました。
 今後の課題としては、native pluginを含んだプロジェクトもビルド可能な対応も検討しています。

参考文献