BuildManifestでUnity Cloud Buildのビルド情報を上手に管理しよう！

　この物語はフィクションです。

　非プログラマー　「なんかバグってる！直して！」

　プログラマー　　「どれどれ？バージョンは？」

　非プログラマー　「えーっと、ちょっと前のやつ！」

　プログラマー　　（ちょっと前っていつのだよ？何回ビルドしたと思ってんだよ！？）


# はじめに

　先ほどの物語はフィクションではありますが、モバイルゲームアプリの開発現場において、このような不幸な経験がある方も多いのではないでしょうか？

　解決策の一つはバージョンをゲームアプリ内に表記して、それを報告してもらうことです。（もちろん端末のアプリ管理機能を使って調べてもらうのもありですね。）

　しかし、これはビルドごとにバージョンを適切に更新している必要があります。

　また、もし製品版やデバック版、ステージング版など複数のビルド設定がある場合、不具合が発生したのがどのビルド版なのかを把握する必要もあります。

　前述の`フィクション`の物語が起きないようにするための、解決方法・対処方法はさまざまなものが考えられますね。

　もしあなたが[Unity Cloud Build](https://unity3d.com/jp/services/cloud-build)を使っているのであれば、私のおすすめは、[`CloudBuildManifest`](https://developer.cloud.unity3d.com/support/guides/manifest/)を用いて、ビルドターゲットとビルド番号をアプリ内に表記するという方法です。

　この投稿では、Unity Cloud Buildの`Cloud BUild Manifest`とそれを用いたTipsを紹介します。

# CloudBuildManifestとは？

　CloudBuildManifestについて説明します。

　[Unity Cloud Build](https://unity3d.com/jp/services/cloud-build)を行うと、`Assets/__UnityCloud__/Resources`以下に`UnityCloudBuildManifest.json`というJSON形式のテキストファイルが自動で置かれます。

　生成されるJSONの例を示します。

```json
{
    "scmCommitId":"abcdefghijklmnopqrstuvwxwz01234567890123",
    "scmBranch":"master",
    "buildNumber":"3",
    "buildStartTime":"1/1/2017 11:11:11 PM",
    "projectId":"cloud_build_example",
    "bundleId":"com.teammurosta.cloud_build_example",
    "unityVersion":"5.5.0f3",
    "xcodeVersion":"6.3.1",
    "cloudBuildTargetName":"default-ios",
}
```

　それぞれの要素は次の通りです。

* scmCommitId:　Unity Cloud Build によってビルドされた commit、または changelist
* scmBranch:　ビルドされたブランチ名
* buildNumber:　このビルドに関連する Unity Cloud Build 番号
* buildStartTime:　ビルドプロセスが始まったときの UTC timestamp
* projectId:　Unity Cloud Build プロジェクト識別子
* bundleId:　(iOS/Androidのみ) bundleIdentifier は Unity Cloud Build 内で設定されます
* unityVersion:　Unity Cloud Build がビルド作成に使用した Unity のバージョン
* xcodeVersion:　(iOS のみ) ビルドに使用される XCode のバージョン
* cloudBuildTargetName:　ビルドされたプロジェクトビルドターゲットの名前。

　この生成されるJSON（CloudBuildManifest）の要素には、ビルドしたコミットのリビジョン、ビルドに使ったUnityやXcodeのバージョン、ビルド設定であるビルドターゲット名がありますね。

　これらの情報をゲームアプリ内で表示し、それを報告してもらえばいろいろ解決しそうですね。

　私のおすすめは、`cloudBuildTargetName`と`buildNumber`を併記してゲームアプリ内に表示する方法です。


# Unity Package Manager経由でインストール

このCloudBuildManifestを取得するライブラリを、Unity Package形式でGitHubに公開しています。

https://github.com/RyotaMurohoshi/CloudBuildManifest

インストールするには、Unity 2019.1以上のUnityでかつ、Gitがインストールされている環境において、次の一文を`Packages/manifest.json`内の`dependencies`に追加してください。

```
"com.ryotamurohoshi.cloudbuildmanifest": "https://github.com/RyotaMurohoshi/CloudBuildManifest.git#TAG_YOU_WANT_TO_USE",
```

`TAG_YOU_WANT_TO_USE`は、利用したいGitHub上のTagを指定してください。

使い方は次の通りです。

```csharp
string buildInfoText;
if (BuildManifest.HasBuildManifest())
{
    var buildManifest = BuildManifest.Load();
    buildInfoText = $"{buildManifest.CloudBuildTargetName} #{buildManifest.BuildNumber}";
}
else
{
    buildInfoText = "Not Cloud Build";
}
```



# コード内容

もしUnity 2019.1よりも古いUnityを使っているなどで、上記のライブラリが使えない場合は、つぎのソースコードをプロジェクトに入れてください。


```csharp

using System;
using UnityEngine;

#pragma warning disable 649
namespace CloudBuildManifest {
    /// <summary>Class <c>BuildManifest</c> provides build information for Unity Cloud Build.</summary>
    [Serializable]
    public class BuildManifest
    {
        /// <summary>
        /// If binary is built with Unity Cloud Build, static method <c>Load</c> returns the BuildManifest instance.
        /// If binary is not built with Unity Cloud Build, static method <c>Load</c> returns null.
        /// </summary>
        /// <returns>The BuildManifest instance or null.</returns>
        public static BuildManifest Load()
        {
            var json = LoadManifestAsset();
            if (json == null)
            {
                return null;
            }
            else
            {
                return JsonUtility.FromJson<BuildManifest>(json.text);
            }
        }

        /// <summary>
        /// If binary is built with Unity Cloud Build, static method <c>HasBuildManifest</c> returns true.
        /// If binary is not built with Unity Cloud Build, static method <c>HasBuildManifest</c> returns false.
        /// </summary>
        /// <returns>boolean</returns>
        public static bool HasBuildManifest()
        {
            return LoadManifestAsset() != null;
        }

        private static TextAsset LoadManifestAsset()
        {
            return Resources.Load<TextAsset>("UnityCloudBuildManifest.json");
        }

        [SerializeField]
        string scmCommitId;

        /// <value>Property <c>ScmCommitId</c> represents the commit or changelist that was built.</value>
        public string ScmCommitId { get { return scmCommitId; } }

        [SerializeField]
        string scmBranch;
        /// <value>Property <c>ScmBranch</c> represents the name of the branch that was built.</value>
        public string ScmBranch { get { return scmBranch; } }

        [SerializeField]
        string buildNumber;
        /// <value>Property <c>BuildNumber</c> represents The Unity Cloud Build “build number” corresponding to this build.</value>
        public string BuildNumber { get { return buildNumber; } }

        [SerializeField]
        string buildStartTime;
        /// <value>Property <c>BuildStartTime</c> represents The UTC timestamp when the build process was started.</value>
        public string BuildStartTime { get { return buildStartTime; } }

        [SerializeField]
        string projectId;
        /// <value>Property <c>ProjectId</c> represents The Unity project identifier.</value>
        public string ProjectId { get { return projectId; } }

        [SerializeField]
        string bundleId;
        /// <value>Property <c>BundleId</c> represents The bundleIdentifier configured in Unity Cloud Build (iOS and Android only).</value>
        public string BundleId { get { return bundleId; } }

        [SerializeField]
        string unityVersion;
        /// <value>Property <c>UnityVersion</c> represents The version of Unity that Unity Cloud Build used to create the build.</value>
        public string UnityVersion { get { return unityVersion; } }

        [SerializeField]
        string xcodeVersion;
        /// <value>Property <c>XCodeVersion</c> represents The version of XCode used to build the project (iOS only).</value>
        public string XCodeVersion { get { return xcodeVersion; } }

        [SerializeField]
        string cloudBuildTargetName;
        /// <value>Property <c>CloudBuildTargetName</c> represents the name of the build target that was built.</value>
        public string CloudBuildTargetName { get { return cloudBuildTargetName; } }
    }
}
#pragma warning restore 649
```

# ScriptableObjectも生成される

　実は組み込みでCloud Build Manifestを司るクラスが存在します。

　`UnityEngine.CloudBuild.BuildManifestObject`というScriptableObjectを継承したクラスです。

　このクラスはすこし癖があります。`UNITY_CLOUD_BUILD`ディレクティブ内でしか使えないのです。

　この`BuildManifestObject`を利用したコードを次に示します。

```csharp
using UnityEngine;
using UnityEngine.UI;
#if UNITY_CLOUD_BUILD
using UnityEngine.CloudBuild;
#endif

public class ManifestExample : MonoBehaviour
{
    [SerializeField]
    Text text;

    void Start()
    {
#if UNITY_CLOUD_BUILD
        var manifest = Resources.Load<BuildManifestObject>("UnityCloudBuildManifest.scriptable");
        text.text = manifest.ToJson();
#endif
    }
}
```

　`UNITY_CLOUD_BUILD`というディレクティブに注目してください。これはCloud Buildで有効になります。

　BuildManifestObjectクラスのScriptableObjectが、`Assets/__UnityCloud__/Resources`以下に`UnityCloudBuildManifest.scriptable`という名前で生成されます。

　BuildManifestObjectは、Cloud Buildでないと使えないクラスですので、`UNITY_CLOUD_BUILD`ディレクティブとともに使う必要があるという点に注意してください。

　[ドキュメント](https://developer.cloud.unity3d.com/support/guides/manifest/)によると、`BuildManifestObject`には次のメンバがあるようです。

```csharp

namespace UnityEngine.CloudBuild
{
    public class BuildManifestObject : ScriptableObject
    {
        // Tries to get a manifest value - returns true if key was found and could be cast to type T, false otherwise.
        public bool TryGetValue<T>(string key, out T result);

        // Retrieve a manifest value or throw an exception if the given key isn't found.
        public T GetValue<T>(string key);

        // Sets the value for a given key.
        public void SetValue(string key, object value);

        // Copy values from a dictionary. ToString() will be called on dictionary values before being stored.
        public void SetValues(Dictionary<string, object> sourceDict);

        // Remove all key/value pairs
        public void ClearValues();

        // Returns a Dictionary that represents the current BuildManifestObject
        public Dictionary<string, object> ToDictionary();

        // Returns a JSON formatted string that represents the current BuildManifestObject
        public string ToJson();

        // Returns an INI formatted string that represents the current BuildManifestObject
        public override string ToString();
    }
}
```

　[公式ドキュメント](https://developer.cloud.unity3d.com/support/guides/manifest/)には

> JSON TextAsset はまだ書き出されていないため、BuildManifestObject は、Unity Cloud Build によってに呼び出されるエクスポート前メソッドのオプショナルパラメーターとしても使用されます。

　とあります。

　ビルド前処理などのEditor拡張のコードなどでは、Textアセットではなく、こちらの`BuildManifestObject`を使う必要があるようです。


# さいごに

　[`CloudBuildManifest`](https://developer.cloud.unity3d.com/support/guides/manifest/)を用いれば、

　「あれ、これどのバージョンだっけ！？」

　ということが避けられそうですね！