GitHub ActionsでUnityでunitypackage生成とビルド&実機(Linux)ユニットテストを実行する

以前にCircleCIでUnityをテスト/ビルドする、或いは.unitypackageを作るまで、それとCIや実機でUnityのユニットテストを実行してSlackに通知するなどするという記事を書いたのですが、時代はGitHub Actionsということで、私も全体的にCircleCIからGitHub Actionsに移行を始めてまして、それに伴ってビルドスクリプトも最新化したので、紹介します。コンフィグ作成にあたっては【Unity】GitHub Actions v2でUnity Test Runnerを走らせて、結果をSlackに報告する【入門】UnityをGitHub Actionsで動かす際にライセンス認証周りで注意するべき点も参考にしました。

実際のコンフィグは ZLogger/.github/workflows にありますが、Unityの部分だけ取り出して実行可能な形式にすると

name: Build-Debug

on:
  push:
    branches:
      - "**"
    tags:
      - "!*" # not a tag push
  pull_request:
    types:
      - opened
      - synchronize

jobs:
  build-unity:
    strategy:
      matrix:
        unity: ['2019.3.9f1', '2020.1.0b5']
        include:
          - unity: 2019.3.9f1
            license: UNITY_2019_3
          - unity: 2020.1.0b5
            license: UNITY_2020_1
    runs-on: ubuntu-latest
    container:
      # with linux-il2cpp. image from https://hub.docker.com/r/gableroux/unity3d/tags
      image: gableroux/unity3d:${{ matrix.unity }}-linux-il2cpp
    steps:
      - run: apt update && apt install git -y
      - uses: actions/checkout@v2
      # create unity activation file and store to artifacts.
      - run: /opt/Unity/Editor/Unity -quit -batchmode -nographics -logFile -createManualActivationFile || exit 0
      - uses: actions/upload-artifact@v1
        with:
          name: Unity_v${{ matrix.unity }}.alf
          path: ./Unity_v${{ matrix.unity }}.alf
      # activate Unity from manual license file(ulf)
      - run: echo -n "$UNITY_LICENSE" >> .Unity.ulf
        env:
          UNITY_LICENSE: ${{ secrets[matrix.license] }}
      - name: Activate Unity, always returns a success. But if a subsequent run fails, the activation may have failed(if succeeded, shows `Next license update check is after` and not shows other message(like GUID != GUID). If fails not). In that case, upload the artifact's .alf file to https://license.unity3d.com/manual to get the .ulf file and set it to secrets.
        run: /opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile -manualLicenseFile .Unity.ulf || exit 0

      # Execute scripts: RuntimeUnitTestToolkit
      - name: Build UnitTest(Linux64, mono)
        run: /opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile -projectPath . -executeMethod UnitTestBuilder.BuildUnitTest /headless /ScriptBackend mono /BuildTarget StandaloneLinux64
        working-directory: src/ZLogger.Unity
      - name: Execute UnitTest
        run: ./src/ZLogger.Unity/bin/UnitTest/StandaloneLinux64_Mono2x/test

      # Execute scripts: Export Package
      - name: Export unitypackage
        run: /opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile -projectPath . -executeMethod PackageExporter.Export
        working-directory: src/ZLogger.Unity

      # Store artifacts.      
      - uses: actions/upload-artifact@v1
        with:
          name: ZLogger.Unity.unitypackage
          path: ./src/ZLogger.Unity/ZLogger.Unity.unitypackage

となっています。微妙に長いね!(ショボいシンタックスハイライターの影響でインデントが腐ってて読みづらいのでworkflows/build-debug.ymlを見ていただいたほうがいいです)

さて、とりあえずUnityにおいての第一関門は認証を通すことなのですが、ここは -createManualActivationFile して -manualLicenFile に投げる、というやり方を採用します(他にも幾つかやり方はある)。Unityのインストール等に関しては、インストール済みのコンテナイメージを使って、コンテナでビルドします。使用できるイメージ一覧は DockerHub - gableroux/unity3d/tagsから選べますが、ここではIL2CPPビルドが実行できると謳ってる、かつ最新の2019.3.9f1-linux-il2cppと2020.1.0b5-linux-il2cppを使うことにしました。マトリックスビルドかけるなら、もっと古いのあたりも入れたほうがいいといえばいいんですが、LinuxでIL2CPPビルド可能なのは2019.3からなのでshoganai。

matrix組むのは、特にアセット作っている人にとっては重要で、というのも新しいUnityサポートしたら古いUnityで使えないAPIを使っちゃってビルドエラーとか、たまによくやるんですよね、うっかり。if-def囲み忘れとか、逆に囲みすぎとか。というわけで、可能なら最低サポートバージョンから、マイナーバージョン毎ぐらいに組むのがいいと思います。そういう意味では、私は今のところ2018.3を最低サポートバージョンにしているので、2018.3, 2018.4もmatrixに組んだほうがいいのですがIL2CPPビルドのために以下略。

さて、認証ですが、この辺で処理しています。

# create unity activation file and store to artifacts.
- run: /opt/Unity/Editor/Unity -quit -batchmode -nographics -logFile -createManualActivationFile || exit 0
- uses: actions/upload-artifact@v1
    with:
      name: Unity_v${{ matrix.unity }}.alf
      path: ./Unity_v${{ matrix.unity }}.alf
# activate Unity from manual license file(ulf)
- run: echo -n "$UNITY_LICENSE" >> .Unity.ulf
    env:
      UNITY_LICENSE: ${{ secrets[matrix.license] }}
- name: Activate Unity, always returns a success. But if a subsequent run fails, the activation may have failed(if succeeded, shows `Next license update check is after` and not shows other message(like GUID != GUID). If fails not). In that case, upload the artifact's .alf file to https://license.unity3d.com/manual to get the .ulf file and set it to secrets.
  run: /opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile -manualLicenseFile .Unity.ulf || exit 0

基本的に認証の流れは .alf を作る -> .alf を https://license.unity3d.com/manual にアップロードして .ulf ファイルをダウンロード。そのulfファイル(中身はXMLテキスト)をGitHub ActionsのSettings -> Secretsに設定する、ということなのですが、.alfを手元で作るのは面倒なのでCIに作ってもらって、artifactsにあげてます。

初回実行時は.ulfがないので絶対に後続の実行は失敗します(Activate処理だけはエラーにならないので、その先で認証できなかったといってコケます)。ので、ActionsのArtifactsのところからalfをダウンロードして、.ulfを作ります。それをテキストエディタで開いてSecretsのところに適切な名前で保存すればOK。名前との関連付けは

matrix:
unity: ['2019.3.9f1', '2020.1.0b5']
include:
    - unity: 2019.3.9f1
      license: UNITY_2019_3
    - unity: 2020.1.0b5
      license: UNITY_2020_1

で設定してますが、ここでは2019.3.9f1用はUNITY_2019_3, 2020.1.0b5用はUNITY_2020_1にしました。2019_3といいつつ、コンテナイメージ毎に新しい認証ファイルがいるので、2019.3.10f1に変えたらSecretは設定しなおしです。面倒くさい。shoganai。

ユニットテストの実行はCysharp/RuntimeUnitTestToolkitを使用します。これはUnity Test Runnerで書いたユニットテストからCUI/GUIでの実行シーンをビルド時に動的に生成するもので、まぁまぁ便利です。特にCUIでのテスト実行はCI用ですね、結果をそのまま出力で見れたり、エラーがあったらそのままエラーにしてくれたりするので非常に楽。それ以外に私はエディタからWindowsでのIL2CPPビルド実行を多用しています(よくIL2CPPで引っかかるライブラリを作っているので)

設定は -executeMethod UnitTestBuilder.BuildUnitTest /headless /ScriptBackend mono /BuildTarget StandaloneLinux64 といったものを書けばOK。そうすると bin/UnitTest/StandaloneLinux64_Mono2x/test に成果物ができてるので、すぐに実行すれば、CIでのテスト実行ということになります。

# Execute scripts: RuntimeUnitTestToolkit
- name: Build UnitTest(Linux64, mono)
    run: /opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile -projectPath . -executeMethod UnitTestBuilder.BuildUnitTest /headless /ScriptBackend mono /BuildTarget StandaloneLinux64
    working-directory: src/ZLogger.Unity
- name: Execute UnitTest
    run: ./src/ZLogger.Unity/bin/UnitTest/StandaloneLinux64_Mono2x/test

ここで /ScriptBackend mono/ScriptBackend IL2CPP にするとIL2CPPビルドになるので、やったーCIでIL2CPPのテストができるぞー!と思ったんですが、現在のコンテナイメージだとなんか謎エラーでビルドに失敗するので、一旦は諦めました。誰か成功させてください。何かとIL2CPPで引っかかるライブラリを作ってるので、できればここでテストしたいんですけどねえ。

最後に.unitypackageの作成ですが、これはリポジトリに仕込んである生成メソッドをキックして、artifactsにアップロードします。

# Execute scripts: Export Package
- name: Export unitypackage
    run: /opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile -projectPath . -executeMethod PackageExporter.Export
    working-directory: src/ZLogger.Unity

# Store artifacts.      
- uses: actions/upload-artifact@v1
    with:
    name: ZLogger.Unity.unitypackage
    path: ./src/ZLogger.Unity/ZLogger.Unity.unitypackage
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;

public static class PackageExporter
{
    [MenuItem("Tools/Export Unitypackage")]
    public static void Export()
    {
        var version = Environment.GetEnvironmentVariable("UNITY_PACKAGE_VERSION");

        // configure
        var root = "Scripts/ZLogger";
        var fileName = string.IsNullOrEmpty(version) ? "ZLogger.Unity.unitypackage" : $"ZLogger.Unity.{version}.unitypackage";
        var exportPath = "./" + fileName;

        var path = Path.Combine(Application.dataPath, root);
        var assets = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)
            .Where(x => Path.GetExtension(x) == ".cs" || Path.GetExtension(x) == ".meta" || Path.GetExtension(x) == ".asmdef")
            .Where(x => Path.GetFileNameWithoutExtension(x) != "_InternalVisibleTo")
            .Select(x => "Assets" + x.Replace(Application.dataPath, "").Replace(@"\", "/"))
            .ToArray();

        UnityEngine.Debug.Log("Export below files" + Environment.NewLine + string.Join(Environment.NewLine, assets));

        var dir = new FileInfo(exportPath).Directory;
        if (!dir.Exists) dir.Create();
        AssetDatabase.ExportPackage(
            assets,
            exportPath,
            ExportPackageOptions.Default);

        UnityEngine.Debug.Log("Export complete: " + Path.GetFullPath(exportPath));
    }
}

と、まぁこれでいい感じに?テストとパッケージ生成ができるようになりました!

なお、リリースビルドは別ワークフローのymlになっているので、マトリックスビルドとalfの処理とテストを省いてます(本当はマトリックスもテストもしたほうがよくて、全部通ったらartifact生成とかにしたほうがいいんですが、まぁ多少ザルでもいいでしょう)。

  build-unity:
    strategy:
      matrix:
        unity: ['2019.3.9f1']
        include:
          - unity: 2019.3.9f1
            license: UNITY_2019_3
    runs-on: ubuntu-latest
    container:
      # with linux-il2cpp. image from https://hub.docker.com/r/gableroux/unity3d/tags
      image: gableroux/unity3d:${{ matrix.unity }}-linux-il2cpp
    steps:
    - run: apt update && apt install git -y
    - uses: actions/checkout@v2
    - run: echo -n "$UNITY_LICENSE" >> .Unity.ulf
      env:
        UNITY_LICENSE: ${{ secrets[matrix.license] }}
    - run: /opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile -manualLicenseFile .Unity.ulf || exit 0

    # Execute scripts: Export Package
    - name: Export unitypackage
      run: /opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile -projectPath . -executeMethod PackageExporter.Export
      working-directory: src/ZLogger.Unity

    # Store artifacts.
    - uses: actions/upload-artifact@v1
      with:
        name: ZLogger.Unity.unitypackage
        path: ./src/ZLogger.Unity/ZLogger.Unity.unitypackage

まとめ

GitHub Actions、最初は抵抗感あったんですが、というかCIの設定というのが、かなり嫌いな種類のエンジニアリングなので、一度覚えてコピペで済ませてるCircleCIから引っ越すのに腰が重かったんですが、まぁやってみればなんとかなる(社内でわちゃくちゃと手伝ってもらったお陰でもありますが)し、やってみれば、割といいんじゃないのかな、と思えるようになりました。CircleCIもいいんですが、最近のUI変更などがイケてない感じだったりで好感度下がっていたので、ぎっはぶActions、いいんじゃないでしょーか。

とりあえずDOOM Eternalが超絶面白いのでやっておくといいです。YouTube - Doom Eternal - Cultist Base Master LevelがDoom Eternalの圧倒的なスピード感と暴力を表現しててとてもいいので、ぜひ見て買ってくださいな。今年のGame of the Yearなので。久しぶりにゲーム超面白い……!と思った。ただたんに(クラシックなFPS的に)スピード速くするだけじゃこうならないんですよねえ(実際、前作のDOOM(2016)をEternal後にやると、あまり面白く感じない)、近年ではバトルロイヤルも発明でしたが、DOOM EternalもFPSを、ゲームを進化させるゲームデザインの発明ですね、とにかく良い。めっちゃ良い。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive