JenkinsでUnityのAndroid・iOSビルド (Automatically manage signing,pod install対応) DeployGateにアップまで

#はじめに
この記事はメモ書きみたいなものなので、詳細な説明は省いています。
気になる方は都度調べてくださいｍｍ

#使用環境
- Xcode10.1
- Unity2017.4.20f1


#Jenkinsインストール
この記事ではHomebrewでインストールします。
  
```sh
brew install jenkins
```

インストール後下記コマンドで起動します

```sh
brew services start jenkins
//launchctl load ~/Library/LaunchAgents/homebrew.mxcl.jenkins.plist
```

停止は下記コマンドです

```sh
brew services stop jenkins
//launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.jenkins.plist
```

#ビルド用のシェルを用意
batchbuild時の`-platform`の指定をiOSにすればiOSビルドになります。

```sh
#!/bin/bash

# Unityアプリケーションパス
UNITY_APP_PATH=/Applications/Unity2017_4_20f1/Unity2017_4_20f1.app/Contents/MacOS/Unity
# 対象のUnityプロジェクトパス
UNITY_PROJECT_PATH=${WORKSPACE}  #自身のプロジェクトによって変えてください

# バッチモードで起動後に呼び出すメソッド
UNITY_BATCH_EXECUTE_METHOD=BatchBuild.Build  #自身の用意した関数名を記述 クラス名.関数名
# Unity Editor ログファイルパス
UNITY_EDITOR_LOG_PATH=~/Library/Logs/Unity/Editor.log

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

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

#正常終了
exit 0

```

#Unity側でiOSビルドの設定
Unity > Preference > External Tools > Automatically Signにチェック
Unity > Preference > External Tools > Automatic Signing Team Idに自身のチームIDを入力
Edit > Project Settings > Player > Identificatinにアプリ情報入力
 
#Unity側でビルドスクリプトの用意

```c#
#if UNITY_EDITOR
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class BatchBuild{

	private static BuildTarget m_TargetPlatform;
	private static bool m_IsRelease = false;

	// ***** Android KeyStore Settings *****//
	private static string keystorePath = "keyStoreの保存場所";
	private static string keystorePass = "ｋeyStoreのPass";
	private static string keyaliasName = "keyAliasの名前";
	private static string keyaliasPass = "keyAliasのPass";
	// ***** End Android KeyStore Settings *****//

	public static void Build()
	{
		GetCommandLineArgs ();
		if (m_TargetPlatform == BuildTarget.iOS) {
			bool status = BuildiOS (m_IsRelease);
			EditorApplication.Exit (status ? 0 : 1);
		} else {
			bool status = BuildAndroid (m_IsRelease);
			EditorApplication.Exit (status ? 0 : 1);
		}
	}

	public static bool BuildiOS(bool isRelease = false)
	{
		Debug.Log ("[ScriptLog] Start Build iOS");

		// リリースビルドではない場合Profiler等に繋げるようにする
		BuildOptions opt = BuildOptions.SymlinkLibraries;
		if (isRelease == false) {
			opt |= BuildOptions.Development | BuildOptions.ConnectWithProfiler | BuildOptions.AllowDebugging;
		}

		string[] scenes = GetEnabledScenes ();
		string errorMsg = BuildPipeline.BuildPlayer (scenes, "成果物保存場所(Assetsからの相対パス)", m_TargetPlatform, opt);

		if (string.IsNullOrEmpty (errorMsg)) {
			Debug.Log ("[ScriptLog] Success Build iOS");
			return true;
		}

		Debug.Log ("[ScriptLog] Failed Build iOS");
		Debug.Log (System.Environment.NewLine + errorMsg + System.Environment.NewLine);
		return false;
	}

	public static bool BuildAndroid(bool isRelease = false)
	{
		Debug.Log ("[ScriptLog] Start Build Android");

		// リリースビルドではない場合Profiler等に繋げるようにする
		BuildOptions opt = BuildOptions.None;
		if (isRelease == false) {
			opt |= BuildOptions.Development | BuildOptions.ConnectWithProfiler | BuildOptions.AllowDebugging;
		}

		// KeyStoreの設定
		SetAndroidKeyStoreSetting ();

		string[] scenes = GetEnabledScenes ();
		string errorMsg = BuildPipeline.BuildPlayer (scenes, "成果物保存場所(Assetsからの相対パス)", m_TargetPlatform, opt);

		if (string.IsNullOrEmpty (errorMsg)) {
			Debug.Log ("[ScriptLog] Success Build Android");
			return true;
		}

		Debug.Log ("[ScriptLog] Failed Build Android");
		Debug.Log (System.Environment.NewLine + errorMsg + System.Environment.NewLine);
		return false;
	}

	/// <summary>
	/// AndroidのKeyStoreの情報をPlayerSettingsに流す
	/// </summary>
	private static void SetAndroidKeyStoreSetting()
	{
		string keystoreName = System.IO.Directory.GetCurrentDirectory () + keystorePath;
		// KeyStoreの場所とパスワードを設定
		PlayerSettings.Android.keystoreName = keystoreName;
		PlayerSettings.Android.keystorePass = keystorePass;
		// KeyAliasの名前とパスワードを設定
		PlayerSettings.Android.keyaliasName = keyaliasName;
		PlayerSettings.Android.keyaliasPass = keyaliasPass;
	}


	/// <summary>
	/// コマンドライン引数を解釈して変数に格納する
	/// </summary>
	private static void GetCommandLineArgs()
	{
		string[] args = System.Environment.GetCommandLineArgs ();
		// ここで引数を判定
		for (int i = 0, max = args.Length; i < max; ++i) {
			switch (args [i]) {
			case "-platform":
				m_TargetPlatform = (BuildTarget)System.Enum.Parse (typeof(BuildTarget), args [i + 1]);
				break;
			case "-isRelease":
				m_IsRelease = bool.Parse (args [i + 1]);
				break;
			default:
				break;
			}
		}
	}

	/// <summary>
	/// BuildSettingで有効になっているSceneを取得
	/// </summary>
	/// <returns>The enabled scenes.</returns>
	private static string[] GetEnabledScenes()
	{
		EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes;

		List<string> sceneList = new List<string> ();

		for (int i = 0, max = scenes.Length; i < max; ++i) {
			if (scenes [i].enabled) {
				sceneList.Add (scenes [i].path);
			}
		}

		return sceneList.ToArray ();
	}
}
#endif
```

#iOSでXCodeProjectのOtherLinkerやFrameworkを追加する場合

PostProcessBuildでFramework追加処理を挟む
追加したいLinkerやFrameworkが増えた場合、ここに記述していく。

```c#
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;

public static class XCodeBuildPostProcess {

	[PostProcessBuild(100)]
	public static void OnPostProcessBuild(BuildTarget target, string path)
	{
		if (target == BuildTarget.iOS)
		{
			PostProcessBuild_iOS (path);
		}
	}

	private static void PostProcessBuild_iOS (string path)
	{
		string projectPath = PBXProject.GetPBXProjectPath(path);
		PBXProject pbxProject = new PBXProject();

		pbxProject.ReadFromString(File.ReadAllText(projectPath));
		string target = pbxProject.TargetGuidByName("XcodeProject名");

		// OtherLinker追加
		pbxProject.AddBuildProperty(target, "OTHER_LDFLAGS", "追加したいLinker名");

		// Framework追加 第三引数は Requiredならfalse,Optionalならtrue
		pbxProject.AddFrameworkToProject(target, "追加したいFramework", false);

		File.WriteAllText(projectPath, pbxProject.WriteToString());
	}
}

```


#Jenkins側のJob作成
今回はパイプラインではなく、フリースタイル・プロジェクトのビルドで作成します。
パラメータやソースコード管理は適宜設定してください。
`BULILD_SHELL_ROOT`をJenkinsの環境変数に追加しています。
またシェルの実行時にPermission deniedが出た場合、
シェルの権限設定が間違っているので下記のコマンドを叩いて見てください
`chmod 755 作ったシェル名`

#iOSのシェル
exportOptionPlist.plistは自分で作成してください。
developmentビルド、Releaseビルドなどの設定もここで行う事になります。
自分はこの記事を参考にしました
=> <https://qiita.com/roworks/items/7ef12acabf9679561d84>

pod installを行うので
Jenkins端末にcocoapodsをインストールしておいてください
また、pod install完了済みのprojectを使用するため、xcworkspaceを指定しています。

```sh
sudo gem install cocoapods
pod setup
```

シェル本文

```sh
#!/bin/bash -l

#ビルドシェルの実行
echo "=======================Unity build=============================="
${BUILD_SHELL_ROOT}/iOS_dev.sh

echo "======================pods Install==============================="
cd ${WORKSPACE}/"Unityで指定した保存先"
pod install --repo-update


echo "======================xcodebuild　開始============================"
xcodebuild \
-allowProvisioningUpdates \
-workspace Unity-iPhone.xcworkspace \
-scheme Unity-iPhone \
archive \
-archivePath "${WORKSPACE}/build_archive/archive" \
-configuration Debug

echo "========================ipaExport 開始============================"
xcodebuild \
    -exportArchive -archivePath "${WORKSPACE}/build_archive/archive.xcarchive" \
    -exportPath "${WORKSPACE}/build_archive" \
    -exportOptionsPlist ${WORKSPACE}/Tools/plist/exportOptions.plist
```

#Androidのシェル
特に説明することもなく、シェルを実行すればapkが作られます。

```sh
#ビルドシェルの実行
${BUILD_SHELL_ROOT}/android_dev.sh 
```

#DeployGateにアップロード
この記事を参考
<https://qiita.com/ginrou@github/items/498d02acbbe9e1327bb7>

apkFileの項目にipaを書いても動作します。