JenkinsでUnityプロジェクトをビルドする

この記事は[Unity Advent Calendar 2019](https://qiita.com/advent-calendar/2019/unity)16日目の記事です

# はじめに
- 自動ビルドというとゲーム業界ではJenkinsを使うのが一般的だと思います
- かれこれ私も5年以上Jenkinsおじさんをやっています
- 今回はJenkinsでUnityビルドを作成する方法をご紹介します
    - 今回はWindowsビルドを作成するまでを記事にしていますが、Androidビルドもほぼ同じ方法で実行可能です
    - iOSの場合はXCodeプロジェクトを出力した後に、コマンドラインからXcodeを実行するシェルスクリプトを書く必要があります

# Unity側の準備
## コマンドラインからUnityを使う
- Unityはコマンドラインから、プロジェクト内の関数を実行することができます
    - [コマンドライン引数 - Unity マニュアル](https://docs.unity3d.com/ja/2018.4/Manual/CommandLineArguments.html)
- コマンドラインから関数を呼び出すには`Editor`フォルダ内にスクリプトを作成し、`static`な関数を準備します

```c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CommandLineTest
{
    public static void TestFunction()
    {
        Debug.Log("Hello World!");
    }
}

```

呼び出し方

```
/Applications/Unity/Hub/Editor/2019.3.0f1/Unity.app/Contents/MacOS/Unity \
-batchmode -quit -logFile log.txt \
-projectPath ~/UnityProjects/JenkinsTest \
-executeMethod CommandLineTest.TestFunction 
```

実行結果

```
Hello World!
UnityEngine.DebugLogHandler:Internal_Log(LogType, LogOption, String, Object)
UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
UnityEngine.Logger:Log(LogType, Object)
UnityEngine.Debug:Log(Object)
CommandLineTest:TestFunction() (at Assets/Editor/CommandLineTest.cs:12)
 
(Filename: Assets/Editor/CommandLineTest.cs Line: 12)
```

`Debug.Log`の場合は上記のようにログファイルに実行結果が出力されます

コマンドの意味を説明していきます

```
/Applications/Unity/Hub/Editor/2019.3.0f1/Unity.app/Contents/MacOS/Unity \
-batchmode -quit -logFile log.txt \
-projectPath ~/UnityProjects/JenkinsTest \
-executeMethod CommandLineTest.TestFunction 
```

Unityはバージョンごとにインストールされる場所が異なるので、それぞれの場所を指定してコマンドを実行します。
Windowsの場合は`C:\Program Files\Unity\Editor\Unity.exe`のような形になります。

| オプション|意味| 
|:-----------------|------------------|
|-batchmode|コマンドラインで実行します。これをつけない場合はUnityEditorが開きます|
|-quit|実行終了後、自動的にUnityを閉じます|
|-logFile|ログファイルの出力場所を設定します。設定しない場合、[UnityEditorデフォルトの位置](https://docs.unity3d.com/jp/460/Manual/LogFiles.html)に出力されます。|
|-projectPath|プロジェクトのパスを設定します|
|-executeMethod|実行する関数名を設定します。<br>ClassName.MethodNameの形で指定しますが、クラスにnamespaceが付与されている場合は<br>NameSpace.ClassName.MethodNameのようにnamespaceを頭につける必要があります。|

## 引数でパラメータを渡す
このままでは関数を実行することしかできません。
パラメータを渡すには`System.Environment.GetCommandLineArgs`を使用します

先ほどの関数を以下のように書き換えてみて、同じコマンドを実行すると

```c#
public static void TestFunction()
{
    string output = "";
    var args = System.Environment.GetCommandLineArgs();
    for(int i=0;i< args.Length;i++)
    {
        output += i + "->" + args[i] + "\n";
    }
    Debug.Log(output);
}
```

このような出力になります
配列の0番目がコマンド自体、それ以降が引数のようなシェルスクリプトと同じような感じになります。

```
0->/Applications/Unity/Hub/Editor/2019.3.0f1/Unity.app/Contents/MacOS/Unity
1->-batchmode
2->-quit
3->-logFile
4->log.txt
5->-projectPath
6->/Users/Name/UnityProjects/JenkinsTest
7->-executeMethod
8->CommandLineTest.TestFunction
```

`GetCommandLineArgs()`は`string[]`として引数を返すので
`string`以外の型を使用したい場合は、自前でパースする必要があります。
新しい関数を作って引数を受け取れるようにした例が下記のコードです。
`-option`の形でオプションを受け取って、その次に入っているのがオプションのパラメータという作りになっています。

```c#
public static void ArgumentTest()
{
    string message = "";
    int number = -1;
    var args = System.Environment.GetCommandLineArgs();
    for(int i=0;i<args.Length;i++)
    {
        switch(args[i])
        {
            case "-message":
                message = args[i + 1];
                break;
            case "-number":
                number = int.Parse(args[i + 1]);
                break;
            default:
                break;
        }
    }

    Debug.Log("Message is " + message + " Number is " + number);
}
```

以下のコマンドで実行します

```
/Applications/Unity/Hub/Editor/2019.3.0f1/Unity.app/Contents/MacOS/Unity \
-batchmode -quit -logFile log.txt \
-projectPath ~/UnityProjects/JenkinsTest \
-executeMethod CommandLineTest.ArgumentTest \
-message hello -number 123
```

実行結果は以下のようになります。
`message`にはhelloが入り、`number`には123を入力することができました。

```
Message is hello Number is 123
UnityEngine.DebugLogHandler:Internal_Log(LogType, LogOption, String, Object)
UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
UnityEngine.Logger:Log(LogType, Object)
UnityEngine.Debug:Log(Object)
CommandLineTest:ArgumentTest() (at Assets/Editor/CommandLineTest.cs:39)
 
(Filename: Assets/Editor/CommandLineTest.cs Line: 39)
```

## ビルド用関数を作る
- コマンドラインからUnityを実行できるようになったら、次はビルドできるよう準備をします
- 通常、ビルドするときはFile→Build Settings→Buildとするのが一般的です
- UnityではC#のスクリプト上からもビルドを実行することができ、様々なパラメータを動的に設定することができます
- [BuildPipeline.BuildPlayer](https://docs.unity3d.com/ja/current/ScriptReference/BuildPipeline.BuildPlayer.html)

```C#
public static void Build()
{
    var output = "";
    bool isDevelopment = false;
    var args = System.Environment.GetCommandLineArgs();
    for (int i = 0; i < args.Length; i++)
    {
        switch (args[i])
        {
            case "-outputPath":
                output = args[i + 1];   //出力先の設定
                break;
            case "-development":
                isDevelopment = true;   //Developmentビルドにする
                break;
            default:
                break;
        }
    }

    var option = new BuildPlayerOptions();
    option.locationPathName = output;
    if(isDevelopment)
    {
        //optionsはビットフラグなので、|で追加していくことができる
        option.options = BuildOptions.Development | BuildOptions.AllowDebugging;
    }
    option.target = BuildTarget.StandaloneWindows64; //ビルドターゲットを設定. 今回はWin64
    var result = BuildPipeline.BuildPlayer(option);
    if(result.summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded)
    {
        Debug.Log("BUILD SUCCESS");
    }
    else
    {
        Debug.LogError("BUILD FAILED");
    }
}
```

```
/Applications/Unity/Hub/Editor/2019.3.0f1/Unity.app/Contents/MacOS/Unity \
-batchmode -quit -logFile log.txt \
-projectPath ~/UnityProjects/JenkinsTest \
-executeMethod CommandLineTest.Build \
-outputPath ~/UnityProjects/Build/test.exe \
-development
```
とコマンドを叩けば、`~/UnityProjects/Build/test.exe`の場所にdevelopmentビルドが完成します

# Jenkinsの準備
- 最後にJenkins側の設定です
- Jenkinsのインストールについては下記の記事を参照してください
    - Mac:[MACへJenkinsをインストール(インストールの仕方編)](https://qiita.com/t_n/items/22e6c5fd9f2ced3d5fc4)
    - Windows:[Jenkinsをインストールする手順(Windows)](https://itsakura.com/tool-jenkins-install)

## プラグインの設定
`Jenkinsの管理`→`プラグインの管理`から
`Unity3d pulgin`をインストールします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/5dadfd16-2c4f-a1cf-8080-b24e0937a2fe.png)

プラグインのインストールができたらUnityのパスを設定します。
`Jenkinsの管理`→`Global Tool Configuration`から
`Unity3d`の`Unity3d追加`ボタンを押し、パスを設定します
ここではバージョンごとに異なったパスを設定することができます
MacとWindowsでパスの設定の仕方が異なるので、ご注意ください。

### JenkinsがMacの場合
`Unity.app`まで指定する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/217d40d3-fb6c-ff72-a005-931cf242758c.png)


### JenkinsがWindowsの場合
フォルダの指定まででOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/241d9f59-a237-a3b0-831f-be296503e8ff.png)

## Jobの設定
`新規ジョブ作成`からJob名を設定し、`フリースタイル・プロジェクトのビルド`でJobを作成します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/4f3bd569-709d-c092-324b-6cf918797494.png)


ビルド手順の追加から`Invoke Unity3d Editor`を選択します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/1c151678-ce21-a7f2-281a-aace8b02cabb.png)

ここで使うUnityを指定し、`Editor command line arguments`にビルドで使用するコマンドを入れます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/c5cdf212-3df1-55e2-f6ed-6d1f226021e3.png)

先ほどと同じコマンドを入れるとこんな感じになります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/42cfd042-f2ea-2623-d66a-c0e6446870d2.png)

この中では環境変数を使用することが可能です
私がJenkinsでよく使う環境変数を下の表にまとめました。

|環境変数|意味|
|---|---|
|BUILD_NUMBER|ビルド番号|
|JOB_NAME|Jobの名前|
|BUILD_TAG|`jenkins-${JOB_NAME}-${BUILD_NUMBER}` <br>という形になるので成果物の名前で使うとわかりやすい|
|WORKSPACE|ワークスペースの絶対パス|
|GIT_COMMIT|ビルド時のgitのハッシュ値|

環境変数を使うときは`${WORKSPACE}`のように使うとエラーが少ないです

設定が終わったら`保存`を押して、`ビルドの実行`をすればビルドが作成されます。

あとは`ビルドのパラメータ化`を使って、ビルドごとにパラメータを与えたり
`SCMをポーリング`機能を使ってデイリービルドを作成したりすることが可能です。

# おわりに
- 駆け足でしたがJenkinsでUnityビルドを作成する方法を紹介しました
- ビルドだけでなく、アセットバンドルビルドやテストに応用することも可能です
- Jenkinsには様々なプラグインが用意されているので、そちらと組み合わせることで効率的な開発を行うことができます
- それでは良きUnity&Jenkinsライフを！