Unity3Dのビルド設定により動的にScripting Define Symbolsを変更

Unityではユーザーが `IPreprocessBuildWithReport` インターフェースを実装したスクリプトを書くことで、スクリプトのコンパイルを含めたビルドが始まる前の挙動を変更できる。

```csharp:Assets/Editor/Preprocessor.cs
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build;Reporting;

namespace Editor {
    public class Preprocessor : IPreprocessBuildWithReport {
        public int callbackOrder => 0;

        public void OnPreprocessBuild(BuildReport report)
        {
            // Do your stuff
        }
    }
}
```

そして、UnityのエディタAPIを使うと、Scripting Define Symbolsを変更できる。  
Scripting Define Symbolsはターゲットグループ(e.g. iOS, Android, Standalone,...)ごとに決まっているので、
設定時にはどのグループかの指定が必要になる。
例えばStandalone (macOS/Windows/Linux)に対して `DE` `FI` `NE` を定義したかったら

```csharp
PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, "DE;FI;NE")
```

と記述する。

これらを組み合わせれば、動的にScripting Define Symbolsをビルド時に変更できるが、それを実現するにあたって変な勘違いをしていたので共有

# 正しいやり方

`BuildReport`クラスからビルド設定を取得することができる。
**`BuildReport.summary.options`プロパティはビットフラグ**となっている。
`&`演算子で調べたい設定の`const`をぶつけて、結果が`nonzero`になることを確認することで設定が有効かを調べられる。

```csharp:これなら動作する
public void OnPreprocessBuild(BuildReport report)
{
    var isDev = (report.summary.options & BuildOptions.Development) != 0;

    // Set scripting define symbol for 'Standalone (macOS/Windows/Linux)'
    PlayerSettings.SetScriptingDefineSymbolsForGroup(
        BuildTargetGroup.Standalone, isDev ? "DE;FI;NE" : ""
    );
}
```



# 間違ったやり方

ちなみに、UnityにはDevelopment Buildを検出するためのDefine、`DEVELOPMENT_BUILD` が存在する。これを使おうとして

```csharp:これでは動作しない
public void OnPreprocessBuild(BuildReport report)
{
#if DEVELOPMENT_BUILD
PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, "DE;FI;NE")
#else
PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, "")
#endif
}
```

とやっても**動作しない**。というのも、このSymbolは**「ビルドが開始しないと正しい状態にならない」**からである。このメソッドはビルド前に呼ばれるから、*Development BuildをONにした直後は、`DEVELOPMENT_BUILD`は未定義*という矛盾した状態が発生してしまう。


# 運用中のコード

場合によっては、Editorで動かす時用にビルド前からScripting Define Symbolsを入れている可能性もある。そんな状況にも対応するために、`GetScriptingDefineSymbolsForGroup` を利用し、最初からあるSymbolを一時退避させたり、上乗せしたりすることができる。
実際に個人プロジェクトで運用しているコードがこちら - ここでは、ビルド前に存在していたSymbolは上乗せしている。:

```csharp:DefineSwitcher

using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;

namespace Editor
{
    public class DefineSwitcher : IPreprocessBuildWithReport, IPostprocessBuildWithReport
    {
        public int callbackOrder => 0;

        private const string DebugDefines = "";
        private const string ReleaseDefines = "RELEASE_DEFINE;DISABLE_CHEATS";

        private string _previousDefines = "";

        /// <summary>
        /// Run before a build (including any script compilation)
        /// </summary>
        /// <param name="report">Get config with this</param>
        public void OnPreprocessBuild(BuildReport report)
        {
            _previousDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone);

            var isDev = (report.summary.options & BuildOptions.Development) != 0;

            // Set scripting define symbol for 'Standalone (macOS/Windows/Linux)'
            PlayerSettings.SetScriptingDefineSymbolsForGroup(
                BuildTargetGroup.Standalone, _previousDefines + ";" + (isDev ? DebugDefines : ReleaseDefines)
            );
        }

        /// <summary>
        /// Run after a build (including actual app / exe export)
        /// </summary>
        /// <param name="report">Get config with this</param>
        public void OnPostprocessBuild(BuildReport report)
        {
            // Restore the previous defines
            PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, _previousDefines);
        }
    }
}
```