GitHub ActionsセルフホステッドランナーでUnityビルドをやってみる

# そもそもGitHub Actionsってなに

CI/CD ツールの一種で、GitHub上で色々と設定すれば、自動でビルドとかしてくれるサービスです


- そもそもCI/CDとは
    
    用語としては
    
    継続的インテグレーション (Continuous Integration)
    
    継続的デリバリー (Continuous Delivery)
    
    この二つの略称です。
    
    ### ゲーム (Unity) におけるCI
    
    簡単に言えば継続的に自動でビルドをすること
    
    ### ゲーム (Unity) におけるCD
    
    たぶんソシャゲとか継続的にリリースするやつで使うはず...
    
    厳密に知りたい人はこれみてね
    
    https://ja.wikipedia.org/wiki/継続的インテグレーション
    
    https://ja.wikipedia.org/wiki/継続的デリバリー

### GitHub Actions 便利ポイント

みんな大好きyamlで書ける (jsonみたいなやつ)

ふだん使っているGitHubで自動化も出来るようになる

# 実際にやってみた

これから先は実際にGitHub ActionsでUnityのビルドをやってみます

## GitHub上の環境構築

**まず良い感じのUnityプロジェクトのリポジトリを用意します**

:::note warn
このとき正常にビルドできることを確認したプロジェクトを選んだ方が
今後問題の特定がしやすいので良いと思います
:::

**用意したリポジトリを開き、画面上部の Actions タブを開きます**

![0641840c551d782cf16c0a46a52b685d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551653/166365d0-c299-b5f2-e038-ad29c97581d9.png)

**開くとこのような画面になるので、この中の set up a workflow yourself → をクリックします**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551653/651651bd-98fc-e1f1-b96c-70718a5947ce.png)

- この画面内にある他のタブって何？
    
    有名なサービスや言語などは、公式がテンプレートを出していたりする
    
    概要はそれぞれ見出しに書いてあるので、
    
    自身の作成する予定のGithub Actionsのフローがテンプレートにある場合は、
    
    それを使うのもアリだと思います

**そして表示された画面で、GitHub Actionsの内容を書いていくことになります**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551653/490f59b6-199c-45d0-8a0c-24e64361fcfe.png)

ローカル (vscode) で作成したい人向け
https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-github-actions

## Self-HostedRunnerの構築 (GitHub上での操作)

Self-HostRunnerだったりSelf-HostedRunnersだったりすることもあります

### そもそもSelf-HostedRunnerとは

自身で用意した、GitHub Actionsの実行に使う環境のこと

↓ Self-HostedRunnerについての資料

https://docs.github.com/ja/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners

ほとんど英語だけど、公式のリファレンス

https://tech.dentsusoken.com/entry/2022/07/04/Gtihub_Actions%E3%81%A7%E3%82%AA%E3%83%B3%E3%83%97%E3%83%AC%E3%83%9F%E3%82%B9%E7%92%B0%E5%A2%83%E3%81%AECI/CD%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95#:~:text=%E3%81%93%E3%81%AE%E3%80%81self%2Dhosted%20runner%E3%81%AF,%E3%82%82%E5%8F%AF%E8%83%BD%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82

デフォルトのGitHubが提供する環境ではなく、Self-HostedRunner使う理由としては、

- Unityなどの特殊な環境を利用するから
- 外部Assetなどを利用している際の処理が楽だから
- 無料で利用出来る分が決まっているから
- 使う事の出来るOSやマシンのスペックが決まっているから
- 大規模開発の場合、料金の節約になるから

これらの理由が挙げられると思います

**まず用意したリポジトリを開き、画面上部の Settings タブを開きます**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551653/ed122fbc-e4f2-09dc-c5e6-7dd726616f52.png)


**そして画面左側のサイドバーから Actions の Runners タブを開きます**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551653/0dc8addd-efbc-7c04-3c5d-d71edbff737a.png)


**そして右上の New self-hosted runner 押します**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551653/7d2bad14-e6d7-3a80-a220-cc3f96eec707.png)


**するとこの画面になるので、使う環境に合わせたOSとプロセッサーアーキテクチャを選択します**

**私はWindowsのノートPCで作成するので、Windowsとx64を選択します**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551653/0f2f1822-06ce-09a0-c203-a94f3e785ab6.png)


**次の流れとしてはこれらのコマンドを実行していきます**

**それぞれのコマンドが何をするかは、英語ですがコメントが入っています**

![名称未設定のデザイン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551653/23829711-407b-49f1-6fa2-c2694e818b28.png)


## Self-HostedRunnerの構築 (ローカルPCでの操作)

今回は直接PowerShellにコマンドを打ち込まずに、PowerShell Scriptsとbatファイルで作成します

**それぞれのコマンド詳細は解説しません**

### ランナーのダウンロードと解凍

ダウンロードなので作成時しか使用しませんが、PowerShell Scriptsを利用しています

~~学習目的なので…許してほしいです~~

```powershell
# ランナーのダウンロード
Invoke-WebRequest -Uri https://github.com/actions/runner/releases/download/v2.319.1/actions-runner-win-x64-2.319.1.zip -OutFile actions-runner-win-x64-2.319.1.zip

# ダウンロードしたファイルのチェックサムを確認
if((Get-FileHash -Path actions-runner-win-x64-2.319.1.zip -Algorithm SHA256).Hash.ToUpper() -ne '1c78c51d20b817fb639e0b0ab564cf0469d083ad543ca3d0d7a2cdad5723f3a7'.ToUpper()){ throw 'Computed checksum did not match' }

# パスの変数を設定
$currentDir = (Get-Location).Path
$zipDir = "$currentDir\actions-runner-win-x64-2.319.1.zip"
# ランナーの解凍
Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::ExtractToDirectory($zipDir, $currentDir)
```

上のPowerShell Scriptsをbatファイル又は、PowerShell上で実行します

### ランナーの構成設定

ここのtoken部分とurlの部分を同じディレクトリ内のテキストファイルに記述して、べた書きしないようにしています

![名称未設定のデザイン (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551653/3304157b-49b6-5717-a833-f4431ba15771.png)


二つのコマンドの内、上のランナーの設定のみを行います

```powershell
# 環境変数の取得と設定
Get-Content env.txt | ForEach-Object {
	$parts = $_ -split '='
	if ($parts.Length -eq 2) {
		[System.Environment]::SetEnvironmentVariable($parts[0], $parts[1])
	}
}

# ランナーを作成し、構成を開始する
./config.cmd --url $env:GITHUB_REPOSITORY_URL --token $env:GITHUB_ACCESS_TOKEN
```

### ランナーの実行

PowerShell上で下記のコマンドを実行するか、エクスプローラーからrun.cmdを実行すると

ランナーが待機状態になります

```powershell
./run.cmd
```

### UnityのCLI(Command Line Interface)ビルド

Unity公式のCLIを利用して、Unityを開かずにビルドを実行することが出来ます

必要な手順として、

1. C#でビルドスクリプトを書く (BuildSettingsやコマンドライン引数などを読み込むため)
2. Editorのpathを調べる
3. コマンドを実行する

それぞれ解説します

### C#でビルドスクリプトを書く

公式のリファレンスも用意されているので、メソッドの詳細などはそれぞれ確認してください

https://docs.unity3d.com/Manual/embedded-linux-build-command-line.html

ビルドスクリプトではビルド設定を行っていて、それぞれの処理はコメントを参照してください

※ ビルドスクリプトのclass名とメソッド名はCLIでのビルド時に使用するので、分かりやすい名前にしてください

<details><summary>C#ビルドスクリプト</summary>
    
```csharp
using UnityEditor;
using UnityEngine;
using System.Linq;

public class BuildCommand
{
    [MenuItem("Assets/Build Application")]
    public static void Build()
    {
        //プラットフォーム、オプション
        var isDevelopment = true;
        var platform = BuildTarget.StandaloneWindows;

        // 出力名とか
        var exeName = PlayerSettings.productName;
        var ext = ".exe";
        string outpath = default;

        // ビルド対象シーンリスト
        var scenes = EditorBuildSettings.scenes
            .Where(scene => scene.enabled)
            .Select(scene => scene.path)
            .ToArray();

        //コマンドライン引数をパース
                //NOTE: iOSはプロジェクトが生成されるだけなので、別途処理する
        var args = System.Environment.GetCommandLineArgs();
        for (var i = 0; i < args.Length; i++)
        {
            switch (args[i])
            {
                case "-projectPath":
                    outpath = args[i + 1] + "\\Build";
                    break;
                case "-devmode":
                    isDevelopment = args[i + 1] == "true";
                    break;
                case "-platform":
                    switch(args[i + 1])
                    {
                        case "Android":
                            platform = BuildTarget.Android;
                            ext = ".apk";
                            break;

                        case "Windows":
                            platform = BuildTarget.StandaloneWindows;
                            ext = ".exe";
                            break;

                                                case "WebGL":
                            platform = BuildTarget.WebGL;
                            PlayerSettings.WebGL.decompressionFallback = true; //動かない環境があるのでチェック入れる
                            ext = "";
                            break;

                        case "Switch":
                            platform = BuildTarget.Switch;
                            ext = "";
                            break;
                    }
                    break;
            }
        }

        //ビルドオプションの成型
        var option = new BuildPlayerOptions
        {
            scenes = scenes,
            locationPathName = outpath + "\\" + exeName + ext
        };
        
        if (isDevelopment)
        {
            //optionsはビットフラグなので、|で追加していくことができる
            option.options = BuildOptions.Development | BuildOptions.AllowDebugging;
        }
        option.target = platform; //ビルドターゲットを設定. 今回はWin64

        // 実行
        var report = BuildPipeline.BuildPlayer(option);

        // 結果出力
        if (report.summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded)
        {
            Debug.Log("BUILD SUCCESS");
            EditorApplication.Exit(0);
        }
        else
        {
            Debug.LogError("BUILD FAILED");

            foreach(var step in report.steps)
            {
                Debug.Log(step.ToString());
            }

            Debug.LogError("Error Count: " + report.summary.totalErrors);
            EditorApplication.Exit(1);
        }
    }
}
```
</details>   

### Editorのpathを調べる

pathを調べる方法としては、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551653/cd1af9ac-8276-c2c2-58b1-3a3bfc8c8814.png)

UnityHubから → インストール → 使用するUnityバージョンの右端の歯車から、エクスプローラーで表示

そして自動化のためにbatファイルなどにビルドコマンドを記述して実行します

```batchfile
@echo off
@REM 環境変数の定義
set UNITY_VERSION=2022.3.5f1
set UNITY_EDITOR_PATH=UnityEditorのpath
set PROJECT_PATH=UnityProjectのpath

@REM 出力フォルダを処理のたびに削除する
if exist "%PROJECT_PATH%\Build" (
	rmdir /s /q "%PROJECT_PATH%\Build"
)

@REM Unityビルドコマンドを実行する
@REM -executeMethodの引数は自身が作成したビルドスクリプトのclass名とメソッド名にしてください
"%UNITY_EDITOR_PATH%%UNITY_VERSION%\Editor\Unity.exe" -batchmode -quit -projectPath "%PROJECT_PATH%" -executeMethod BuildCommand.Build -platform Windows -devmode true -outputPath "%OUTPUT_PATH%"
pause
```

## GitHub Actionsを作成してみる

**今回はSelf-HostedRunnerでUnityをビルドするという記述しかしません**

今回書いたGitHub ActionsのWorkflowです

```yaml
# このGitHub Actionsの名前定義
name: Unity Build
# このGitHub Actionsが実行されるトリガーを定義
on:
    # 手動のみで実行する場合はworkflow_dispatchを指定
    workflow_dispatch: {}
jobs:
  build:
    # Self-hosted runnerを使用時にはruns-on: self-hostedを指定する    
    runs-on: self-hosted
    steps:
      - name: Checkout
        # このGitHub Actionsが実行される前に必要なアクションを指定 (今回はチェックアウトを行うアクション)
        uses: actions/checkout@v2
        with:
          # クローンする際にローカルリポジトリをクリーンにするかどうかを指定
          clean: false
          # LFSを使用するかどうかを指定
          lfs: false
      - name: Build
        # 指定したcmdファイルを実行する
        run: "Unityビルドするbatファイルなどのpath"
```

このWorkflowでは、二つの処理を行っています

1. 実行時に選択したブランチをチェックアウトする
2. Unityビルドを実行する

これを実行することで指定したpathにビルド成果物が生成されていると思います！

# 動かないときのチェックシート

### Self-HostedRunner編

- [ ]  tokenは正しいですか？ ページを開きなおしたりすると更新されている場合があります
- [ ]  テキストファイルなどに定義した環境変数の名前は一致していますか？
- [ ]  PowerShell Scripts内のコマンド間に余分な空白などは入っていませんか？
- [ ]  そもそもその空白は半角ですか？
- [ ]  pathは正しいですか？

### UnityCLIビルド編

- [ ]  そもそもUnityでコンパイルエラーやビルドエラーはありませんか？
- [ ]  ビルド実行するマシンに足りていないアセットなどはありませんか？
- [ ]  EditorやProjectのpathは正しいですか？
- [ ]  Projectのバージョンは指定したバージョンと一致していますか？
- [ ]  ビルドスクリプトのclass名とメソッド名がCLIのビルドコマンドの引数と一致していますか？

# 参考資料

GitHub Actions

https://qiita.com/s3i7h/items/b50ceb0008edc3c0312e

https://zenn.dev/yahagi_day/articles/6b915d0efe005a

https://qiita.com/kura_cvr/items/872cae5620a7be8a60ad

UnityCLI

https://zenn.dev/mattak/articles/5dfa1a2369ebc0

https://qiita.com/sango/items/474efb4c016a136c84ce

https://docs.unity.com/embeddedlinux/en/manual/how-to-build-using-cli

self-hosted runner

https://qiita.com/h_tyokinuhata/items/7a9297f75d0513572f4a

https://zenn.dev/hironobuu/articles/e85418948e896f