docfx で Unity Package のAPI リファレンスを作成する

## 本記事について

この記事は [CoreBluetoothForUnity Advent Calendar 2023](https://qiita.com/advent-calendar/2023/core-bluetooth-for-unity) の25日目の記事です。

CoreBluetoothForUnity では GitHub Actions で docfx を利用した API リファレンスを作成しています。

https://teach310.github.io/CoreBluetoothForUnity/api/CoreBluetooth.html

こちら CI 上で docfx を使うためにはいくつかの工夫が必要でした。
本記事ではその方法について説明します。

## 環境

- CoreBluetoothForUnity 0.4.7
- Mac M2
- dotnet 7.0.403

## 前提

- [docfx Quick Start](https://dotnet.github.io/docfx/index.html)

## docfx のインストール

バージョン固定するためにローカルインストールします。

プロジェクト直下で以下を実行

```
$ dotnet new tool-manifest
$ dotnet tool install docfx 
```

これで `dotnet docfx` で実行できるようになります。

## ドキュメント用のフォルダを作成

```
$ dotnet docfx init -q -o docs
```

フォルダ名は UniTask や [公式の ml-agents](https://github.com/Unity-Technologies/ml-agents/tree/develop/docs) で　`docs` にしているためそれに倣いました。

```
$ dotnet docfx docs/docfx.json --serve
```

でローカルで確認できるかと思います。

## Unity Package の API リファレンスを作成する際の課題

Unity のプリコンパイル済みアセンブリに依存している場合、docfx で API リファレンスを作成する際にはそのアセンブリがある必要があります。

具体的には `using UnityEngine;` している場合は依存しています。

また、これ以外でも依存している場合があり、 CoreBluetoothForUnity においては
`AOT.MonoPInvokeCallback` アトリビュートが Unity のプリコンパイル済みアセンブリに含まれていました。

ローカルでドキュメントを生成する分には問題ありませんが、CI 上でドキュメントを生成する場合には Unity のプリコンパイル済みアセンブリが基本ないため、用意する or 使わないのどちらかにする必要があります。

CoreBluetoothForUnity では後者を選択しました。

## 生成対象の指定について

https://dotnet.github.io/docfx/docs/dotnet-api-docs.html#generate-from-assemblies

docfx.json の metadata > src で指定します。
この指定方法には3択ありそうでした。

- assembly (.dll, .exe)
- projects or solutions (.csproj, .sln)
- File Mappings による .cs 指定 (.cs)

CoreBluetoothForUnity では対象ファイルを**省く**ことができる3つ目の方法を使いました。

具体的には以下のように設定しています。

[docfx.json](https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.7/docs/docfx.json)

```docfx.json
{
  "metadata": [
    {
      "src": [
        {
          "files": [
            "Packages/com.teach310.core-bluetooth-for-unity/Runtime/**/*.cs"
          ],
          "exclude": [
            "Packages/com.teach310.core-bluetooth-for-unity/Runtime/Initializer.cs",
            "Packages/com.teach310.core-bluetooth-for-unity/Runtime/NativeCallbacks/*.cs"
          ],
          "src": ".."
        }
      ],
```

## インタフェース切って依存しないようにする

「Unity に依存しているクラス」と「依存を解決するクラス」の両方を除外することによって Unity に依存しないようにすることができます。

https://github.com/teach310/CoreBluetoothForUnity/pull/68

`ServiceLocator` への登録は `RuntimeInitializeOnLoadMethod` で一気に行います。

[Initializer.cs](https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.7/Packages/com.teach310.core-bluetooth-for-unity/Runtime/Initializer.cs)

```Initializer.cs
using UnityEngine;

namespace CoreBluetooth
{
    internal class Initializer
    {
        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
        static void Initialize()
        {
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS
            ServiceLocator.Instance.Register<INativeCentralManagerCallbacks>(new NativeCentralManagerCallbacks());
            ServiceLocator.Instance.Register<INativePeripheralManagerCallbacks>(new NativePeripheralManagerCallbacks());
            ServiceLocator.Instance.Register<INativePeripheralCallbacks>(new NativePeripheralCallbacks());
#endif
        }
    }
}

```

そして、この `Initializer` を `docfx.json` で除外することで生成対象から外すことができます。

## Actions

main ブランチに push されたらビルドして GitHub Pages にデプロイしています。

https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.7/.github/workflows/publish_docs.yml

https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.7/Makefile#L34-L40

## おわりに

本記事では docfx を使って CI　上でUnity Package の API リファレンスを作成し、GitHub Pages にデプロイする方法について説明しました。
API リファレンスを作成するためにスクリプトの構造変えるのはいかがなものか...と思いながらやっていましたが、できはしたのでよかったです。
もっと楽に生成できるような方法があれば教えてください！