【Unity + GitHub Actions】UnityPackageの生成からリリースまですべて自動化する

# 実現する事
対象ブランチにコミットするだけで下記の処理を自動で行います。

1. コミット情報を元にバージョンを決定 (Semantic Versioning)
2. バージョンを[package.json](https://docs.unity3d.com/ja/2019.4/Manual/upm-manifestPkg.html)に書き込み
3. コミットにバージョンのタグを設定
4. unityPackageを生成
5. GitHubのReleasesページにコミットメッセージと合わせて出力

下記のような感じになります。
自分でバージョンすら入力しないのでめちゃくちゃ楽になりました。

![スクリーンショット 2021-05-24 17.29.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/26559836-c108-2dd7-45c5-19bfe532d702.png)


# [手順1] Unityのアクティベーション
GitHub ActionsでUnityを動かすにはアクティベーションが必要です。
[こちら](https://qiita.com/TakenokoTech/items/4b22e9f1c14570306bb2#activation)を参考にさせていただきました。

### [注意点]
- アクティベーションは最初の1度のみ必要です。
- 今回の方法は、Personalライセンスの例です。Plus/Proライセンスの場合、GitHub上にUnityのID、パスワードを保存する為多少リスクがあります。UnityPackageを出力する用途であればPersonalライセンスを作った方が早いかもしれません。詳しくは[こちら](https://unity-ci.com/docs/github/activation)
- 今回の方法は、Unity2019.x用のやり方です。Unity2020.xでも使いたい場合は別途アクティベーションする必要があるようです。

### 1. [GitHub] アクティベーションファイルを取得

リポジトリのActionsタブ > set up a workflow yourselfをクリック
![スクリーンショット 2021-05-25 14.04.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/876fb324-66f3-af84-e936-2692ed457b69.png)

スクリプトの編集画面になるのでactivation.ymlという名前でスクリプトを追加

![スクリーンショット 2021-05-26 16.20.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/1b3ee800-f1bb-b370-05cb-a5f865823f11.png)

追加するスクリプト activation.yml

```yml
name: Acquire activation file
on: workflow_dispatch

jobs:
  activation:
    name: Request manual activation file 🔑
    runs-on: ubuntu-latest
    steps:
      # Request manual activation file
      - name: Request manual activation file
        id: getManualLicenseFile
        uses: game-ci/unity-request-activation-file@v2
      # Upload artifact (Unity_v20XX.X.XXXX.alf)
      - name: Expose as artifact
        uses: actions/upload-artifact@v2
        with:
          name: ${{ steps.getManualLicenseFile.outputs.filePath }}
          path: ${{ steps.getManualLicenseFile.outputs.filePath }}
```

完了後、Start commit → commit changesで保存します。
メッセージは何も書かなくても大丈夫です。

![スクリーンショット 2021-05-26 0.24.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/46a2de45-8032-80ad-bce7-acef790f464b.png)

Actionsに戻ると実行できるようになっているので「Run workflow」で実行 (2分程かかります)

![スクリーンショット 2021-05-26 16.25.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/28191397-34fa-12a1-aa7e-75ee3e9cf072.png)

workflowをクリックして下記の画面まで進めます。完了するとArtifactsに「Unity_v2019.x.xf1.aif」というファイルが出力されているのでダウンロードします。

![スクリーンショット 2021-05-26 16.29.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/30975ed6-d3f2-da92-7550-9d01da0e3a40.png)

### 2. [Unity] アクティベーション
- [license.unity3d.com](https://license.unity3d.com/manual)にアクセス
- さきほどダウンロードしたUnity_v2019.x.xf1.aifを指定してアクティベートする
- 最終的に出力されるUnity_v2019.x.xf1.alf.zipをダウンロード

### 3. [GitHub] Secretsにダウンロードしたライセンスを登録
- Unity_v2019.x.xf1.alf.zipを解凍
- 解凍したUnity_v2019.x.ulfをテキストエディタで開いてコピーする
- リポジトリのSettings > SecretにUNITY_LICENSEという名前でコピーしたものをペーストして登録
![スクリーンショット 2021-05-26 15.17.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/12a3ba7c-141f-2626-6e1e-e1c489cd6f19.png)

# [手順2] パッケージを生成するスクリプトを設置しコミット

下記スクリプトをAssets/Editor/に設置
パス : Assets/Editor/PackageExporter.cs

設置後、Unity上部のメニュー 「Tools > Export Unitypackage Test」
で問題無く出力できるかテストしてみてください。

問題無ければコミットしてください。

**[変更箇所]**
- RootDirectory: "パッケージまでのディレクトリ名を指定"
例) "Assets/Plugins/IDReference"
- FileName: "プラグイン名を指定"(FileName.unitypackageという名前で出力されます)
例) "IDReference"

#### PackageExporter.cs

```c#
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;

public static class PackageExporter
{
    public const string RootDirectory = "Assets/Plugins/IDReference";
    public const string FileName = "IDReference";

    /// <summary>
    /// パッケージの書き出し(エディタ上でのテスト用)
    /// メニュー 「Tools > Export Unitypackage Test」をクリックで実行
    /// </summary>
    [MenuItem("Tools/Export Unitypackage Test")]
    public static void ExportTestOnEditor()
    {
        var exportPath = EditorUtility.SaveFilePanel
        (
            "保存先を選択",
            Application.dataPath,
            $"{FileName}.unitypackage",
            "unitypackage"
        );
        
        CreatePackage(RootDirectory, exportPath);
    }

    /// <summary>
    /// パッケージの書き出し
    /// GitHub Actionsから呼ばれる
    /// </summary>
    public static void Export()
    {
        CreatePackage(RootDirectory, $"build/{FileName}.unitypackage");
    }

    public static void CreatePackage(string rootDirectory, string exportPath)
    {
        SafeCreateDirectory(exportPath);
        var assetsPaths = GetAllAssetsAtPath(rootDirectory);
        AssetDatabase.ExportPackage(assetsPaths, exportPath, ExportPackageOptions.Default);
        Debug.Log($"Export complete: {Path.GetFullPath(exportPath)}\nExport below files:\n{string.Join("\n", assetsPaths)}");
    }

    public static DirectoryInfo SafeCreateDirectory( string path )
    {
        var diParent = Directory.GetParent(path);
        if ( diParent == null || Directory.Exists( diParent.FullName ) )
        {
            return null;
        }
        return Directory.CreateDirectory( diParent.FullName );
    }

    public static string[] GetAllAssetsAtPath(string root)
    {
        return Directory.GetFiles(root, "*", SearchOption.AllDirectories)
            .Where(x => !string.IsNullOrEmpty(x))
            .Where(x => !x.EndsWith(".meta"))
            .Where(x => x != ".DS_Store")
            .ToArray();
    }
}
```

# [手順3] Actionsに.ymlファイルを追加

リポジトリのActionsタブ > set up a workflow yourselfをクリック
![スクリーンショット 2021-05-25 14.04.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/876fb324-66f3-af84-e936-2692ed457b69.png)

もしすでにworkflowがある場合は、下記のような表示になります。new workflowをクリック
![スクリーンショット 2021-05-26 16.23.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/a9a07d2d-70ef-281c-a489-a7e37c8d6e99.png)


スクリプトの編集画面になるのでスクリプトを追加

![スクリーンショット 2021-05-25 14.11.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/9a9857c9-1e3a-e3fd-2009-f02471a874d5.png)

完了後、Start commit → commit changesで保存します。
メッセージは何も書かなくても大丈夫です。

![スクリーンショット 2021-05-26 0.24.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/46a2de45-8032-80ad-bce7-acef790f464b.png)



上記の手順で下記2つのスクリプトを追加します。

### update-package.yml
**[変更箇所]**
branches:
   - 対象のブランチを指定　例) main
TARGET_FILE: 自分のパッケージのpackage.jsonがあるパスを指定
例) ./Assets/Plugins/IDReference/package.json


```yml
name: Update package.json

on:
  push:
    branches:
      - main

env:
  TARGET_FILE: ./Assets/Plugins/IDReference/package.json

jobs:
  update-packagejson:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      # Checkout
      - uses: actions/checkout@v2
      - name: Output package.json (Before)
        run: cat ${{ env.TARGET_FILE}}
      # Cache
      - name: Cache
        uses: actions/cache@v2
        with:
          path: Library
          key: Library-Package
          restore-keys: Library-
      # Get version
      - uses: actions/checkout@v2
      - name: Get next version 
        id: tag_version
        uses: mathieudutour/github-tag-action@v5.5
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          dry_run: true
      # Update
      - name: Update package.json to version ${{ steps.tag_version.outputs.new_version }}
        run: |
          sed -i -e "s/\(\"version\":\) \"\(.*\)\",/\1 \"${{ steps.tag_version.outputs.new_version }}\",/" ${{ env.TARGET_FILE }}
      # Check
      - name: Check update
        id: check_update
        run: |
          cat ${{ env.TARGET_FILE}}
          git diff --exit-code || echo "::set-output name=changed::1"
      # Commit
      - name: Commit files
        id: commit
        if: steps.check_update.outputs.changed == '1'
        run: |
          git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"
          git commit -m "Update package.json to ${{ steps.tag_version.outputs.new_version }}" -a
      # Push
      - name: Push changes
        if: steps.check_update.outputs.changed == '1'
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          branch: ${{ github.ref }}
          tags: true
```

### release.yml
**[変更箇所]**
FILE_NAME: "パッケージ名を指定" 例) "IDReference"
BUILD_METHOD: "パッケージを生成するメソッドをnamespaceから指定"
例) "PackageExporter.Export"

**[注意点]**
Assetsフォルダがリポジトリのルートに無い場合は、後述している「[プロジェクトのルートにAssetsフォルダが無い場合](#プロジェクトのルートにassetsフォルダが無い場合)」を参照してください。

```yml
name: Release Unity Package

on:
  workflow_run:
    workflows:
      - "Update package.json"
    types:
      - completed

env:
  FILE_NAME: "IDReference"
  BUILD_METHOD: "PackageExporter.Export"

jobs:
  release:
    name: Create Release
    runs-on: ubuntu-latest
    timeout-minutes: 15
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    strategy:
      fail-fast: false
    steps:
      # Checkout
      - name: Checkout repository
        uses: actions/checkout@v2
        with:
          lfs: true
      # Cache
      - name: Cache
        uses: actions/cache@v2
        with:
          path: Library
          key: Library-${{ hashFiles('Assets/**', 'Packages/**', 'ProjectSettings/**') }}
          restore-keys: |
            Library-
      # Build
      - name: Build package
        id: build
        uses: game-ci/unity-builder@v2
        env:
         UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
        with:
          buildName: ${{ env.FILE_NAME }}.unitypackage
          targetPlatform: StandaloneLinux64
          buildMethod: ${{ env.BUILD_METHOD }}
          versioning: None
      # Check .meta
      - name: Check all .meta is commited
        run: |
          if git ls-files --others --exclude-standard -t | grep --regexp='[.]meta$'; then
            echo "Detected .meta file generated. Do you forgot commit a .meta file?"
            exit 1
          else
            echo "Great, all .meta files are commited."
          fi
        working-directory: Assets
      # Versioning
      - name: Bump version and push tag
        id: tag_version
        uses: mathieudutour/github-tag-action@v5.5
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
      # Create Release
      - name: Create Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ steps.tag_version.outputs.new_tag }}
          release_name: ${{ steps.tag_version.outputs.new_tag }}
          body: ${{ steps.tag_version.outputs.changelog }}
          draft: false
          prerelease: false
      # Upload Assets
      - name: Upload Release Asset
        id: upload-release-asset 
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }} 
          asset_path: build/${{ env.FILE_NAME }}.unitypackage
          asset_name: ${{ env.FILE_NAME }}_${{ steps.tag_version.outputs.new_tag }}.unitypackage
          asset_content_type: application/zip
```

以上で完了です。適当にコミットしてテストしてみてください。

# コミット時のメッセージのルール

![img.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/c8f5fe8c-db23-2873-5139-c604e247a986.png)

### タイプをつける

コミット時のメッセージに`fix:`などの決められたタイプをつける事でリリースノートに表示されます。言い替えると表示されたくないものは、 タイプをつけなければ良いです。

**コミットメッセージとリリースノートの出力例**

| コミットメッセージ | リリースノート |
|:-:|:-:|
| fix: Test | Test(59e71d0) |
| Update reslease.yml | 表示されない |

もちろんリリース後もリリースノートの修正はできるので何か問題あれば書き換えればいいや程度の認識で問題無いと思います。

### タイプの書式
タイプ(スコープ): 修正または追加内容
タイプ: 修正または追加内容
例) fix(Enemy): 移動速度の修正

#### 使えるタイプ一覧

| タイプ | 説明 |
|:-:|:-:|
| feat: | 新機能の追加 |
| fix: | バグの修正 |
| docs: | ドキュメントの修正 |
| style: | スタイルの調整(空白の削除など) |
| refactor: | バグでもなく機能も追加しないコードの修正 |
| perf: | パフォーマンスの向上に伴う修正

#### 特殊なタイプ
`BREAKING CHANGE:` 破壊的な変更があった場合、記入する特殊なタイプ
例)
perf: ライブラリを一新
BREAKING CHANGE: 前バージョンとは互換性がありません。

# How to的なもの

### Unityバージョンを設定したい

今回のコードではUnityのバージョンを指定していません。指定していない場合はコミットしたUnityバージョンを使用します。
明示的に指定する場合は、release.ymlの`Build package`の`with:`に`unityVersion: Unityのバージョン`を指定してください。
例) 2019.4.26f1


```yml
# Build
- name: Build package
  id: build
  uses: game-ci/unity-builder@v2
  env:
   UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
  with:
    unityVersion: 2019.4.26f1 ← 追加
    buildName: ${{ env.FILE_NAME }}.unitypackage
```

### 利用可能なUnityのバージョンを確認したい
下記で確認できます。見る感じ新バージョンへの対応は早いようです。

https://unity-ci.com/docs/docker/versions

### 一時的にWorkflowを動かないようにしたい

Disable workflowで一時的に動かないようにできます。
![スクリーンショット 2021-05-26 0.33.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/76c20e9d-e576-fce0-33f1-308bc1af0055.png)


### プロジェクトのルートにAssetsフォルダが無い場合

release.ymlの`Build package`の`with:`に`buildsPath: Assetsまでのパスを指定`
例) src/Unity

```yml
# Build
- name: Build package
  id: build
  uses: game-ci/unity-builder@v2
  env:
   UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
  with:
    buildsPath: src/Unity ← 追加
    buildName: ${{ env.FILE_NAME }}.unitypackage
```


### 手動でも実行したい
`workflow_dispatch`を記述する事で簡単に使えます。

https://docs.github.com/ja/actions/reference/events-that-trigger-workflows#manual-events

update-package.ymlを下記のように変更


```yml
on:
  push:
    branches:
      - main

    workflow_dispatch ←追加
```

Run workflowボタンが表示されて手動実行できるようになります。
引数を渡す事も可能です。

![スクリーンショット 2021-05-26 15.05.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/b0a0c842-7a0d-5076-50e8-d7c4d699929e.png)


### テストを実行したい

https://game.ci/docs/github/test-runner

EdiorModeテストとPlayModeテストを同時に実行する例

```yml
name: Test project

on: workflow_dispatch

jobs:
  testAllModes:
    name: Test in ${{ matrix.testMode }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        testMode:
          - playmode
          - editmode
    steps:
      - uses: actions/checkout@v2
        with:
          lfs: true
      - uses: actions/cache@v2
        with:
          path: ${{ matrix.projectPath }}/Library
          key: Library-${{ matrix.projectPath }}
          restore-keys: |
            Library-
      - uses: game-ci/unity-test-runner@v2
        id: tests
        env:
          UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
        with:
          projectPath: ${{ matrix.projectPath }}
          testMode: ${{ matrix.testMode }}
          artifactsPath: ${{ matrix.testMode }}-artifacts
          githubToken: ${{ secrets.GITHUB_TOKEN }}
          checkName: ${{ matrix.testMode }} Test Results
      - uses: actions/upload-artifact@v2
        if: always()
        with:
          name: Test results for ${{ matrix.testMode }}
          path: ${{ steps.tests.outputs.artifactsPath }}
```

完了後、Artifactsに結果が出力されます。

![スクリーンショット 2021-05-26 15.38.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/e985a8e9-3f74-80ab-a31b-cda0641d4309.png)


### 完了時にSlackに通知したい
下記を利用すると楽です。
https://github.com/8398a7/action-slack

**1. SlackのWebhook URLを取得**
ブラウザでSlackにログイン後、下記URLにアクセスし取得

https://slack.com/services/new/incoming-webhook

**2. リポジトリのSettings > SecretにSLACK_WEBHOOK_URLという名前でWebhook URLを登録**
![スクリーンショット 2021-05-26 15.17.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/12a3ba7c-141f-2626-6e1e-e1c489cd6f19.png)

**3. release.ymlの最後に下記コードを追加**

```yml
  - uses: 8398a7/action-slack@v3
    with:
      status: ${{ job.status }}
      fields: repo,message,commit,author,action,eventName,ref,workflow,job,took # selectable (default: repo,message)
    env:
      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required
    if: always() # Pick up events even if the job fails or is canceled.
```


### あと何分GitHub Actionsが無料で使えるか知りたい

GitHub 右上アイコン > Setting
![スクリーンショット 2021-05-26 15.59.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/803c7c23-b8c9-0e80-50a1-93792da01d92.png)

Billing&Planタブ > Usage this month > GitHub Actions

![スクリーンショット 2021-05-26 16.00.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/7859ca81-70a6-9c6d-e178-a42bf40a5759.png)

### GitHub Actionsについて効率良く学びたい

HTMLやJSのように、リポジトリのワークフローの内容は誰でも見る事ができます。
僕は、MessagePipeの[build-release.yml](https://github.com/Cysharp/MessagePipe/blob/master/.github/workflows/build-release.yml)を大変参考にさせていただきました。
自分がよく見る好きなリポジトリを覗いてみてください。


# 各機能の詳細

- [GitHub Actionの構文](https://docs.github.com/ja/actions/reference/workflow-syntax-for-github-actions)
- [Unityのビルド game-ci/unity-builder](https://github.com/game-ci/unity-builder)
- [バージョンの管理 mathieudutour/github-tag-action](https://github.com/mathieudutour/github-tag-action)
- [リリース actions/create-release](https://github.com/actions/create-release)


# 参考
- [GitHub ActionsでUnityでunitypackage生成とビルド＆実機(Linux)ユニットテストを実行する
](http://neue.cc/2020/04/22_591.html)
- [Github Actions で UnityPackage をビルドしたら快適だった話](https://qiita.com/TakenokoTech/items/4b22e9f1c14570306bb2)
- [Cysharp
/
MessagePipe](https://github.com/Cysharp/MessagePipe)
- [【Unity】GitHub Actions v2でUnity Test Runnerを走らせて、結果をSlackに報告する【入門】](https://qiita.com/pCYSl5EDgo/items/690dd56ffb0fcf64e70b)
- [GitHub Marketplace · Actions to improve your workflow](https://github.com/marketplace?type=actions)