【Unity】iOS と macOS の NativePlugin のビルド方法

## 本記事について

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

CoreBluetoothForUnity では以下の手順で iOS と macOS の NativePlugin をビルドしています。

1. プロジェクト直下で `make plugins/build` を実行
2. UnityEditor を再起動 (プラグイン再読み込みのために必要)

`make plugins/build` でやってること、作り方について紹介します。

## 前提

本記事のやり方は [keijiro さんの MEMO](https://github.com/keijiro/Memo/blob/main/Pages/UnityNativePlugin.md#swift-%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F-unity-%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3%E9%96%8B%E7%99%BA) に書かれている方法で作られています。

本記事の内容は以下の記事の内容から派生しています。

https://qiita.com/Teach/items/a547e1383680c8372339

## 環境

- Unity 2022.3
- CoreBluetoothForUnity 0.4.3

Xcode Project (というかワークスペース)は以下です。

https://github.com/teach310/CoreBluetoothForUnity/tree/0.4.3/Plugins/CoreBluetoothForUnity

![xcode_screen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/37872d3b-2f74-e4ca-600f-87c49adcea4e.png)

workspace には CB4UNativeApp と CoreBluetoothForUnity の2つが含まれています。

CB4UNativeApp はテスト用のアプリのため、ビルドには関係がありません。
workspace 内の CoreBluetoothForUnity は SwiftPackage で、ここから NativePlugin をビルドします。

## ビルドで出力するファイル

keijiro さんの MEMO にある通り、
macOS 向けには `.dylib`、iOS 向けには `.framework` を出力します。

[対応拡張子](https://docs.unity3d.com/ja/2022.3/Manual/PluginInspector.html)

## dylib の出力方法

コマンドは keijiro さんの[サンプル](https://github.com/keijiro/UnitySwiftPluginTest/blob/main/Plugin/build.sh#L4)からそのまま拝借しています。

```
swift build -c release --arch arm64 --arch x86_64
```

このコマンドで dylib を生成するためには Package.swift でライブラリタイプを `.dynamic` にする必要があります。

https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.3/Plugins/CoreBluetoothForUnity/Package.swift#L13

[LibraryType](https://developer.apple.com/documentation/packagedescription/product/library/librarytype)

参考

https://www.reddit.com/r/SwiftPM/comments/jfr7bo/how_can_i_compile_swift_package_to_dylib/

## framework の出力方法

こちらのコマンドは keijiro さんの[サンプル](https://github.com/keijiro/UnitySwiftPluginTest/blob/main/Plugin/build.sh#L7-L8)を参考にしつつ、改変したものになっています。

```
framework/build:
	xcodebuild -scheme $(BASE_NAME) -workspace $(WORKSPACE) -configuration Release -sdk iphoneos \
	 -destination generic/platform=iOS -derivedDataPath ./$(BUILD_DIR)/Framework build
```

- workspaceはなくても動くけど、あったほうがいいかなと思って入れています
- ENABLE_BITCODE は非推奨のパラメータのため削除
- 末尾に action を明示するために build を追加
- 出力先はFramework専用のディレクトリにする

そして、こちらのコマンドも同様に SwiftPackage のライブラリタイプを `.dynamic` にしないと生成されません。

### xcframework の中のファイルとの比較

[こちらの記事](https://qiita.com/Teach/items/a547e1383680c8372339#ios-%E3%81%A7-helloworld)では xcframework を生成し、その中の framework を取り出していました。今回は別の方法になっています。

これらは同じファイルかと思いきや framework のサイズが違いました。(75%程度に縮みました)
xcodebuild のオプションでも容量が変わるかを検証してみました。

```
xcframeworkの中のframework 426KB

- sdk指定なし
(生成されたフォルダの合計容量　86.4MB)
framework 649KB

- sdk指定あり
(生成されたフォルダの合計サイズ　19.3MB)
framework 324 KB
sdk指定したほうが容量減ってる。

- destination指定
容量変わらず
```

sdk を iphoneos にするとどうなるか
=> 最新バージョンが使われるようです。

ログ

```
Build settings from command line:
    SDKROOT = iphoneos17.0
```

結論としては xcodebuild で sdk を指定するのが一番容量の小さい framework が生成できるようです。(理由はわかってないです)

### Makefile でまとめて実行

やり方はそのまま Makefile をみていただくのが良いかと思います。

#### Xcode Project の Makefile

https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.3/Plugins/CoreBluetoothForUnity/Makefile

.env ファイルに Native Plugin を配置する絶対パスを記述しておき、ビルドしたらそのディレクトリにコピーするようにしています。

#### リポジトリ直下の Makefile

https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.3/Makefile#L29-L32

`make -C` によってサブディレクトリの Makefile のコマンドを実行しています。

## おわりに

本記事では NativePlugin のビルド方法について紹介しました。
特に library type を dynamic にしないといけないことにたどり着くまでに時間がかかりました。最終的には概ね理想通りのものができたためよかったかなと思っています。
もしもっと良いやり方をご存知でしたら教えていただけると嬉しいです！